Platform Guide
TagLib-Wasm works across all major JavaScript runtimes. The API is the same everywhere — the only meaningful difference is how your platform accesses files.
Filesystem vs. Buffer Platforms
| 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");
});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.
Deno
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.
Deno Compile
TagLib-Wasm supports deno compile for building standalone executables. See Deno Compile for details on embedding the Wasm binary.
Node.js
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 |
Bun
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.
Browser
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);Bundler required
TagLib-Wasm uses ES modules. Use Vite, Webpack, Parcel, or another bundler that can resolve taglib-wasm and serve the .wasm file.
Web Workers
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.
SharedWorker (shared-worker.ts)
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()?.length,
});
} catch (err) {
port.postMessage({ error: (err as Error).message });
}
});
port.start();
});Main thread (main.ts)
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);
});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
Cloudflare Workers
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()?.length,
});
},
};Limitations
- Memory limit — 128MB per request
- Buffer-only — no filesystem access
See Cloudflare Workers Guide for detailed configuration.
Electron
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).
Main Process (filesystem)
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().length,
};
});
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);
});
});Renderer Process (via IPC)
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.
Cross-Platform Code
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);Next Steps
- Quick Start for a full walkthrough
- API Reference for all available methods
- Runtime Compatibility for detailed platform support matrix