@huggingface/transformers
Transformers.js runs Hugging Face models directly in the browser or in Node with no inference server: it mirrors the Python transformers pipeline API and executes ONNX-converted models through ONNX Runtime, on CPU via WASM or on GPU via WebGPU. It covers text classification, embeddings, translation, speech recognition, image tasks and small text-generation models, with quantized weights (q4/q8/fp16) to keep downloads and memory workable.
The most practical way to run real models in a browser today, and excellent for embeddings, Whisper and classification. Be honest about physics: model downloads and experimental WebGPU mean it complements a server-side stack more often than it replaces one.
Use it if
- You want client-side inference for privacy or cost reasons: embeddings, sentiment, OCR-ish tasks or Whisper transcription without sending user data to a server
- You already know the Python transformers pipeline API and want near-identical code in JavaScript
- You need offline-capable ML in an Electron app, browser extension or PWA where a hosted API is not an option
- You want cheap embeddings in a Node script without standing up Python infrastructure
- Your model is large: weights ship to the client, so even quantized mid-size models mean hundreds of megabytes of download and real RAM pressure; anything chat-grade belongs on a server
- You need dependable GPU acceleration everywhere: the README itself warns WebGPU is still experimental in many browsers, and the WASM fallback is CPU-slow for heavy models
- You need training or fine-tuning; this is inference only
- You need the newest architectures on release day: models must be converted to ONNX and supported by the library, so coverage trails the Python package
Setup reality
npm i @huggingface/transformers and the first pipeline call just works, because models and WASM binaries are pulled from CDNs at runtime. That default is also the trap: production apps should pin and self-host models (env.localModelPath, env.allowRemoteModels = false) and the .wasm files, or your app silently depends on two external CDNs. First-load latency is real since models are tens to hundreds of MB, so you cache in IndexedDB and show progress callbacks. Bundlers need care with web workers, and WebGPU behavior differs per browser.
Patterns
Run a sentiment analysis pipelinesentiment-pipeline
import { pipeline } from '@huggingface/transformers'
const pipe = await pipeline('sentiment-analysis')
const out = await pipe('I love transformers!')
// [{ label: 'POSITIVE', score: 0.999... }]The first call downloads the model; construct pipelines once and reuse them, never per request.
Use a specific model from the Hubcustom-model
const pipe = await pipeline(
'sentiment-analysis',
'Xenova/bert-base-multilingual-uncased-sentiment'
)Only models tagged transformers.js on the Hub ship the ONNX weights this library needs; arbitrary PyTorch repos will not load.
Run on GPU via WebGPUwebgpu-inference
const pipe = await pipeline('sentiment-analysis',
'Xenova/distilbert-base-uncased-finetuned-sst-2-english',
{ device: 'webgpu' }
)WebGPU is still experimental in many browsers per the project's own warning; feature-detect navigator.gpu and fall back to WASM.
Shrink a model with quantizationquantized-dtype
const pipe = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct',
{ dtype: 'q4' }
)Defaults differ by backend (q8 on WASM, fp32 on WebGPU); q4 cuts download and memory at some accuracy cost, and available dtypes vary per model.
Generate sentence embeddingstext-embeddings
const extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2')
const output = await extractor(['first text', 'second text'], {
pooling: 'mean',
normalize: true,
})
const vectors = output.tolist() // 2 x 384Without pooling and normalize you get raw token-level tensors, not comparable sentence vectors.
Transcribe audio with Whisperspeech-to-text
const transcriber = await pipeline(
'automatic-speech-recognition',
'Xenova/whisper-tiny.en'
)
const result = await transcriber(audioUrl)
// { text: '...' }Input audio must be 16kHz mono; in the browser decode with AudioContext({ sampleRate: 16000 }) before passing raw samples.
Chat-style text generation with messageschat-generation
const generator = await pipeline('text-generation',
'HuggingFaceTB/SmolLM2-135M-Instruct', { dtype: 'q4' })
const messages = [
{ role: 'system', content: 'You are terse.' },
{ role: 'user', content: 'Why is the sky blue?' },
]
const out = await generator(messages, { max_new_tokens: 128 })
console.log(out[0].generated_text.at(-1).content)Passing a messages array applies the model's chat template automatically; stick to sub-1B models in browsers.
Serve models from your own originself-host-models
import { env } from '@huggingface/transformers'
env.allowRemoteModels = false
env.localModelPath = '/models/'
env.backends.onnx.wasm.wasmPaths = '/wasm/'The defaults fetch models from the Hub and WASM from a CDN at runtime; production apps should pin and self-host both.
Show model download progressprogress-callback
const pipe = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
progress_callback: (p) => {
if (p.status === 'progress') {
console.log(`${p.file}: ${Math.round(p.progress)}%`)
}
},
})Models are tens to hundreds of MB on first load; a UI without a progress indicator looks frozen.
Keep inference off the main threadweb-worker
// worker.js
import { pipeline } from '@huggingface/transformers'
let pipePromise = pipeline('sentiment-analysis')
self.onmessage = async (e) => {
const pipe = await pipePromise
self.postMessage(await pipe(e.data))
}
// main.js
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
worker.postMessage('great library')WASM inference blocks whatever thread it runs on; anything beyond trivial models belongs in a worker to keep the page responsive.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| onnxruntime-web | npm | You have your own ONNX model and want direct control without the pipeline and model-hub layer |
| @tensorflow/tfjs | npm | You are in the TensorFlow ecosystem or need in-browser training, not just inference |
| @mlc-ai/web-llm | npm | Your goal is specifically chat-style LLM inference in the browser with WebGPU |