TagLib-Wasm works across all major JavaScript runtimes. The API is the same everywhere — the only meaningful difference is how your platform accesses files.
| Platform | File paths | Buffers | Install |
|---|---|---|---|
| Deno | Yes | Yes | import from "@charlesw/taglib-wasm" (JSR) or "npm:taglib-wasm" |
| Node.js | Yes | Yes | npm install taglib-wasm |
| Bun | Yes | Yes | bun add taglib-wasm |
| Browser | No | Yes | Use a bundler (Vite, Webpack, Parcel) |
| Web Workers | No | Yes | Use a bundler (Vite, Webpack, Parcel) |
| Cloudflare Workers | No | Yes | npm install taglib-wasm |
| Electron | Main: Yes / Renderer: No | Yes | npm install taglib-wasm |
Filesystem platforms (Deno, Node.js, Bun) can pass file paths directly. Changes save to disk without extra steps:
// Read from path
const tags = await readTags("song.mp3");
// Write to disk (path in, void out)
await applyTagsToFile("song.mp3", { title: "New Title" });
// edit() with a path saves to disk and returns void
await taglib.edit("song.mp3", (file) => {
file.tag().setTitle("New Title");
});Buffer platforms (Browser, Cloudflare Workers) must provide audio data as a
Uint8Array or ArrayBuffer. Write operations return a modified buffer that
you handle yourself:
// Read from buffer
const tags = await readTags(audioData);
// Write returns a new buffer (buffer in, buffer out)
const modified = await applyTags(audioData, { title: "New Title" });
// edit() with a buffer returns the modified Uint8Array
const modified = await taglib.edit(audioData, (file) => {
file.tag().setTitle("New Title");
});::: tip Both modes work everywhere Filesystem platforms can also accept buffers. This is useful for processing in-memory data on Node.js/Deno/Bun without touching disk. :::
import { applyTagsToFile, readTags } from "@charlesw/taglib-wasm/simple";
// Read tags from file path
const tags = await readTags("song.mp3");
// Update tags on disk
await applyTagsToFile("song.mp3", { title: "New Title", artist: "New Artist" });Run with: deno run --allow-read --allow-write script.ts
Deno requires explicit permissions: --allow-read for reading files,
--allow-write for applyTagsToFile or any operation that saves to disk.
TagLib-Wasm supports deno compile for building standalone executables. See
Deno Compile for details on embedding the Wasm binary.
import { applyTagsToFile, readTags } from "taglib-wasm/simple";
const tags = await readTags("song.mp3");
await applyTagsToFile("song.mp3", { title: "New Title", artist: "New Artist" });Requirements: Node.js v22.6.0 or higher.
| Node.js version | TypeScript support |
|---|---|
| 23.6+ | Native (node script.ts) |
| 22.6+ | node --experimental-strip-types script.ts |
| Older | npx tsx script.ts |
import { applyTagsToFile, readTags } from "taglib-wasm/simple";
const tags = await readTags("song.mp3");
await applyTagsToFile("song.mp3", { title: "New Title", artist: "New Artist" });Run with: bun run script.ts
Bun has native TypeScript support with no additional configuration.
Browsers have no filesystem access. Audio data comes from the
File API,
fetch(), or drag-and-drop — always as an ArrayBuffer or Uint8Array.
import { applyTags, readTags } from "taglib-wasm/simple";
// Get audio data from a file input
const input = document.querySelector('input[type="file"]');
const audioData = new Uint8Array(await input.files[0].arrayBuffer());
// Read
const tags = await readTags(audioData);
// Write — returns modified buffer (you decide what to do with it)
const modified = await applyTags(audioData, { title: "New Title" });Use applyTags (not applyTagsToFile) since there's no file path to write back
to. To let the user save the result:
const blob = new Blob([modified], { type: "audio/mpeg" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "modified.mp3";
a.click();
URL.revokeObjectURL(url);::: tip Bundler required TagLib-Wasm uses ES modules. Use Vite, Webpack, Parcel,
or another bundler that can resolve taglib-wasm and serve the .wasm file.
:::
Use a Web Worker to offload CPU-intensive tag parsing off the main thread,
keeping your UI responsive. The runtime detector automatically recognizes
workers as a "worker" environment and uses the Emscripten backend.
A SharedWorker shares one TagLib instance across all tabs — ideal for apps
that open multiple windows. A dedicated Worker (new Worker()) is simpler
and has broader browser support. Choose whichever fits your architecture.
import { TagLib } from "taglib-wasm";
const taglib = await TagLib.initialize();
self.addEventListener("connect", (e: MessageEvent) => {
const port = (e as MessageEvent).ports[0];
port.addEventListener("message", async (msg: MessageEvent) => {
try {
const audioData = new Uint8Array(msg.data.buffer);
using file = await taglib.open(audioData);
port.postMessage({
title: file.tag().title,
artist: file.tag().artist,
album: file.tag().album,
duration: file.audioProperties()?.duration,
});
} catch (err) {
port.postMessage({ error: (err as Error).message });
}
});
port.start();
});const worker = new SharedWorker(
new URL("./shared-worker.ts", import.meta.url),
{ type: "module" },
);
function readTagsInWorker(file: File): Promise<Record<string, unknown>> {
return new Promise(async (resolve) => {
const buffer = await file.arrayBuffer();
worker.port.onmessage = (e: MessageEvent) => resolve(e.data);
worker.port.postMessage({ buffer }, [buffer]);
});
}
// Usage with a file input
const input = document.querySelector<HTMLInputElement>('input[type="file"]');
input?.addEventListener("change", async () => {
const tags = await readTagsInWorker(input.files![0]);
console.log(tags);
});::: warning Limitations
- Emscripten backend only — WASI and filesystem paths are not available in workers
- Buffer-only — pass audio data as
ArrayBufferorUint8Array - SharedWorker browser support varies — check compatibility
- Full Web Worker support (dedicated workers, transferable buffers) is planned
:::
Workers are buffer-only with constrained memory. Use the same unified API import as every other platform:
import { TagLib } from "taglib-wasm";
export default {
async fetch(request: Request): Promise<Response> {
const taglib = await TagLib.initialize();
const audioData = new Uint8Array(await request.arrayBuffer());
using file = await taglib.open(audioData);
return Response.json({
title: file.tag().title,
artist: file.tag().artist,
duration: file.audioProperties()?.duration,
});
},
};::: warning Limitations
- Memory limit — 128MB per request
- Buffer-only — no filesystem access
:::
See Cloudflare Workers Guide for detailed configuration.
Note: Electron's main process is Node.js. TagLib-Wasm works via the Node.js WASI path — there is no Electron-specific runtime detection or testing. Keep TagLib-Wasm in the main process and expose metadata through IPC.
Electron spans both categories. The main process has filesystem access; the
renderer process does not (unless nodeIntegration is enabled, which is
discouraged for security).
import { TagLib } from "taglib-wasm";
const taglib = await TagLib.initialize();
ipcMain.handle("get-metadata", async (_event, filePath: string) => {
using file = await taglib.open(filePath);
return {
title: file.tag().title,
artist: file.tag().artist,
duration: file.audioProperties().duration,
};
});
ipcMain.handle("update-tags", async (_event, filePath: string, tags) => {
await taglib.edit(filePath, (file) => {
const tag = file.tag();
if (tags.title) tag.setTitle(tags.title);
if (tags.artist) tag.setArtist(tags.artist);
});
});const metadata = await window.api.getMetadata("/path/to/song.mp3");
await window.api.applyTagsToFile("/path/to/song.mp3", { title: "New Title" });Keep TagLib-Wasm in the main process and expose it through IPC handlers. This avoids bundling Wasm into the renderer and keeps file access secure.
The edit() method is designed for code that runs on both filesystem and buffer
platforms. The mutation callback is identical — only the call site differs:
const taglib = await TagLib.initialize();
function setMetadata(file: AudioFile) {
file.tag().setTitle("Title").setArtist("Artist").setYear(2026);
}
// Filesystem platform — saves to disk, returns void
await taglib.edit("/path/to/song.mp3", setMetadata);
// Buffer platform — returns modified Uint8Array
const modified = await taglib.edit(audioData, setMetadata);- Quick Start for a full walkthrough
- API Reference for all available methods
- Runtime Compatibility for detailed platform support matrix