We’re hiring! We’re looking for a (Technical) Project Manager. Join the team →

Run AI in the Browser: A Practical Guide to Transformers.js

Feature image: Run AI in the Browser: A Practical Guide to Transformers.js

Imagine you need to integrate AI into your web app. Let's say you want to quickly translate text. Preferably, you'd like to use something free and open source (you don't want any surprise bills at the end of the month). One more thing: people use your app while traveling, so it must continue working when they have poor connections or no connection at all.

How many lines of code do you think that would take?

If your answer is "3," you're right. Take a look:

import { pipeline } from '@huggingface/transformers'

const t = await pipeline('translation', 'Xenova/opus-mt-en-es', { device: 'webgpu' })

await t('Hello, world!') // [{ translation_text: '¡Hola, mundo!' }]

That's Transformers.js, a library that lets you run AI models directly in the browser.

Wait... what? No API calls? No backend? Exactly. Here's what happens:

  • The first time the app loads, the model is downloaded and cached.
  • From then on, the app uses the cached model for inference.
  • Inference is 100% client-side: no server, no API keys, and no data ever leaves the user's device.

This offline-first strategy works especially well with small, task-specific models. For example, the model used in the snippet above, opus-mt-en-es, translates text from English to Spanish. It's about 115 MB: small compared to most local models, but still a real download the first time around, so keep that in mind.

And there you have it! Your AI-powered translation feature is ready.

But how does Transformers.js work under the hood? Which models are available? And what are the trade-offs compared to traditional AI providers?

In this article, we'll answer all of those questions. Let's dive in!

Overview

What Is Transformers.js?

Transformers.js is a JavaScript library from Hugging Face that lets you run pre-trained AI models 100% locally in the browser. It started as a personal project by Joshua Lochner, who needed to run a spam classifier as a browser extension and found nothing that could do it. He took the "fine, I'll do it myself" approach and now the library logs over 1.7 million unique monthly users.

The library mirrors the Python transformers API, so the same task names, the same pipeline abstraction, and the same model IDs from the Hugging Face Hub work in JavaScript. At the time of writing, it supports 27 tasks across four categories: text, vision, audio, and multimodal. It covers 155 model architectures, with more than 1.8k models already converted and ready to use on the Hub.

Models that run in your browser, but also in Node, Bun, or Deno. Plus Web Workers, browser extensions, Electron apps, and serverless edge functions.

Why Run AI in the Browser?

You might be wondering: "But... why? Isn't it easier and relatively cheap to integrate with an AI provider like OpenAI or Anthropic?"

It's true that connecting to a third-party API is easier than ever. However, running models directly in the browser has concrete advantages:

  • Privacy. Your users' data never leaves their device. If data privacy is a concern, or you simply don't want AI providers to have access to your users' text or images, local AI is an excellent alternative.
  • Zero cost. You don't have to worry about how many tokens your users consume, or whether someone with too much free time decides to use your chatbot as a free ChatGPT replacement. The bill is always zero.
  • No network needed. The model runs on the user's own hardware. Beyond the initial download, you can forget about the network. Maybe your users are spread all around the world? Well, latency is not an issue. Do they have slow connections, or periods where they lose internet access? They can keep using your AI features without interruption.
  • Speed. Speaking of latency, this is ideal for real-time applications. Imagine a model that recognizes objects in a live video feed. Using a remote model means streaming the video to a server, waiting for inference, and sending the results back to the browser. With Transformers.js, all of that happens locally, so there's no network round trip.

How It Works Under the Hood

Transformers.js doesn't implement its own machine learning framework. Instead, it acts as a thin layer on top of two existing technologies.

  • First, the model itself. Most AI models are trained using frameworks like PyTorch or TensorFlow. Browsers can't execute those models directly, so they first need to be converted to ONNX (Open Neural Network Exchange), a standard format designed to run across different platforms and runtimes. The Hugging Face Hub already hosts around 2,500 ONNX models ready to use. If you've trained your own model, Hugging Face's optimum library can convert it for you.
  • Second, the inference engine. Once the model has been downloaded, Transformers.js delegates execution to ONNX Runtime Web. This is the component that actually performs inference. Depending on what the browser and device support, it can run the model using WebAssembly on the CPU, WebGPU for GPU acceleration, or WebNN, a browser API that can leverage dedicated AI hardware such as NPUs when available.

Transformers.js doesn't execute models itself. It downloads the model, prepares the inputs and outputs, and lets ONNX Runtime Web handle the heavy lifting.

Don't worry if this sounds complicated at first. As a developer, you don't need to deal with any of these details. You simply call pipeline(), and the library takes care of the rest.

Browser Support

Browser support has improved dramatically over the past few years.

Chrome and other Chromium-based browsers offer full WebGPU support, giving Transformers.js access to GPU acceleration. Firefox goes a step further by using Transformers.js as part of its built-in AI Runtime. And with Safari 26 adding WebGPU support across macOS, iOS, iPadOS, and visionOS, GPU-accelerated AI is now available across the entire Apple ecosystem.

In other words, browser-based AI is no longer limited to a few experimental browsers.

Transformers.js in Action

You can install the library using your favorite package manager:

npm install @huggingface/transformers

Then, import pipeline wherever you need it:

import { pipeline } from '@huggingface/transformers'

The pipeline() function is the main entry point to the library. It loads the requested model, downloading it if necessary, caches it using the browser's storage APIs, and returns a function you can use to run inference. We call it using await because loading the model is asynchronous. On the first run, this will wait for the model to download. After that, the cached files are reused, from the browser's Cache API or IndexedDB, making startup much faster.

The first argument specifies the task you want to perform:

const classifier = await pipeline('sentiment-analysis')

Transformers.js supports a wide range of tasks, including:

Most of the time, selecting the task is all you need. If you don't specify a model, Transformers.js automatically downloads a sensible default from the Hugging Face Hub.

However, as we did in our initial snippet, you can explicitly choose which model to use by passing its name as the second argument:

const classifier = await pipeline(
  'sentiment-analysis',
  'Xenova/distilbert-base-uncased-finetuned-sst-2-english'
)

Model names follow the same format as their Hugging Face Hub repositories: <author>/<model>. In this case, we're loading a DistilBERT model fine-tuned for sentiment analysis.

The Hugging Face Hub contains thousands of compatible models. Some are tiny and optimized for running in the browser, while others are larger and offer better accuracy at the cost of longer download times and higher memory usage.

Choosing the right model is usually a trade-off between size, speed, and quality. For browser applications, smaller, task-specific models tend to provide the best user experience.

Running Inference

Now that the model is loaded, running inference is as simple as calling the function with your input:

const result = await classifier('I love Transformers.js!')
// [{ label: 'POSITIVE', score: 0.9998... }]

Here's what's happening:

  • classifier is just an async function returned by pipeline().
  • When you call it, your input is automatically tokenized and converted into the format expected by the model.
  • The model runs inference locally using ONNX Runtime Web.
  • The raw output is decoded into a JavaScript object that's easy to work with.

From your perspective, it's just a function call. Under the hood, Transformers.js handles preprocessing, inference, and postprocessing for you.

The third pipeline argument is an optional configuration object. For example, with our sentiment analysis classifier:

const classifier = await pipeline(
  'sentiment-analysis',
  'Xenova/distilbert-base-uncased-finetuned-sst-2-english',
  {
    device: 'webgpu',
    dtype: 'q8'
  }
)

Here, we're making two changes:

  • device: 'webgpu' tells Transformers.js to use the GPU when available instead of running the model on the CPU. This can significantly improve performance for supported devices.
  • dtype: 'q8' loads a quantized version of the model, which uses 8-bit numbers instead of the full precision version. The result is a smaller model that requires less memory and usually loads faster, with a small trade-off in accuracy.

If you don't provide this third argument, Transformers.js automatically chooses sensible defaults based on the environment. However, for browser applications, options like WebGPU and quantized models can make a big difference, especially when running larger models on resource-constrained devices.

Building a Grammar Checker

Alright! Let's build something practical. Usually, I find myself with a ChatGPT tab open to tidy up sentences. I know, overkill: I don't need a state-of-the-art model to fix grammar and syntax. Can I replace that with my own mini app, using Transformers.js? Totally!

Let's create a grammar checker that takes a sentence, runs it through a model in the browser, and returns a corrected version.

The Model

We need a model that understands text and can generate a corrected version.

Xenova/LaMini-Flan-T5-783M is a good fit. It's an instruction-following text-to-text model, which means we can hand it a plain-English instruction along with the text and it'll do the job.

We're going to build this as a single HTML file (no frameworks, no build step), so we can import Transformers.js straight from a CDN:

import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4';

const corrector = await pipeline(
  'text2text-generation',
  'Xenova/LaMini-Flan-T5-783M'
);

The Prompt

Since this is an instruction-following model, we don't need anything fancy. We just tell it what to do and give it the text:

const text = 'she dont know nothing about it';
const prompt = 'Fix all grammar, spelling, and punctuation errors in this text: ';

const output = await corrector(prompt + text, {
  max_length: 200,
  do_sample: false
});

const corrected = output[0].generated_text.trim();
// "She doesn't know anything about it."

Here's what's happening:

  • The prompt is a plain instruction. The same model can handle other text-to-text tasks (like summarizing, rewriting, translating) just by changing what we ask.
  • do_sample: false makes the output deterministic. For a grammar checker, we don't want creativity.

In practice:

she dont know nothing about it
→ She doesn't know anything about it.

their going to there house
→ They're going to their house.

Yes, I've readed the document
→ Yes, I've read the document.

Pretty good! It fixes the double negative, the homophones, and the tense mistake, capitalizing and punctuating along the way.

However, there are a few trade-offs we should keep in mind.

Honest Expectations

It's not Grammarly

Our little app won't match Grammarly when it comes to nuance. It handles agreement errors, basic tense mistakes, homophones, and missing punctuation reliably. But it has limits:

me and him was going to the park
→ I and him were going to the park.

i has went to the store and buyed to much apple
→ I have gone to the store and bought too much apple.

In the first example, it fixes the verb (waswere) but mangles the pronouns (I and him should be He and I). In the second, it fixes the tenses but leaves too much apple (should be too many apples). And if you throw a sentence with several tangled errors at it, the output can drift or lose part of the meaning entirely. But for a quick grammar check before you hit send... it gets the job done.

Initial Download

This model is around 800 MB, so the first run requires a substantial download. It's cached afterward, but that initial download would require a fast connection.

Performance

By default, inference runs on the CPU, taking a couple of seconds per correction on a typical laptop. On devices with WebGPU, it's noticeably faster.

Wrapping It in a Tiny App

This minimalist version is just an HTML file with a textarea and a button.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Grammar Checker</title>
</head>
<body>
  <h1>Grammar Checker</h1>

  <textarea id="input" rows="5">she dont know nothing about it</textarea>
  <button id="check">Check grammar</button>

  <p id="status"></p>
  <div id="output"></div>
</body>
</html>

Nice. Now we can add the logic in a script of type module:

<script type="module">
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4';

const $ = id => document.querySelector(id);
const [input, button, status, output] = ['#input', '#check', '#status', '#output'].map($);

let corrector;

button.onclick = async () => {
  button.disabled = true;
  output.textContent = '';

  corrector ??= await (async () => {
    status.textContent = 'Loading model…';
    return pipeline('text2text-generation', 'Xenova/LaMini-Flan-T5-783M');
  })();

  status.textContent = 'Checking…';

  const [{ generated_text }] = await corrector(
    `Fix all grammar, spelling, and punctuation errors in this text: ${input.value}`,
    { max_length: 200, do_sample: false }
  );

  output.textContent = generated_text.trim().replace(/^["']|["']$/g, '');
  status.textContent = '';
  button.disabled = false;
};
</script>

That's the whole app! A few things worth calling out:

  • We keep the corrector in a variable and only build it once.
  • Loading the model is asynchronous, so the click handler is async and we await both the pipeline() call and the correction.
  • We disable the button while a check is running, so a second click can't kick off a second run before the first finishes.

Save it as grammar-checker.html and serve it with any static file server (browsers won't run ES module imports over file://):

npx serve

Our mini app is done! No backend, no build tooling, no API keys, no per-token bill. Not bad!

In Closing

We started with a three-line translation snippet and ended with a working grammar checker running 100% in the browser, no conversation history traveling to a server and back. Just a small model, cached locally, doing one thing well.

If you want to learn more about Transformers.js and the incredible applications it enables, check out this presentation by its creator, Joshua Lochner: Transformers.js: State-of-the-Art Machine Learning for the Web.

We hope this article sparked some ideas! Think about the places where you can integrate AI directly into your users' browsers. If you build something with it, make sure to let us know.

Until next time!

Get our latest insights in your inbox:

By submitting this form, you acknowledge our Privacy Notice.

Hey, let’s talk.

By submitting this form, you acknowledge our Privacy Notice.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Thank you!

We appreciate your interest. We will get right back to you.