Skip to main content

Introduction: AI in the browser

Ok. Somewhere in the last year, your users' browsers quietly grew a language model. Chrome's built-in AI ships Gemini Nano — a real, instruction-tuned model, a few gigabytes on disk — on every desktop install that clears the hardware bar, and hands it to your page as plain JavaScript globals. No backend. No API key. No per-token bill, and not one byte of user data leaves the machine. This lesson is the map: what built-in AI actually is, the API family you'll wire across the course, and the one four-verb pattern every single one of them shares.

What you'll build

  • Trace what "built-in AI" actually is: Gemini Nano, shipped inside Chrome, exposed as web APIs.
  • Map the API family to the lessons that go deep on each one.
  • Run the one lifecycle every API reuses: availability()create() → use → destroy().
  • Ship a first on-device prompt that streams a reply with no backend, no key, and no bill.
Prerequisites

Desktop Chrome (Windows, macOS, Linux, or ChromeOS on a Chromebook Plus) with built-in AI available. If availability() comes back unavailable, start with Setup & the availability lifecycle; for the full version-and-flag picture, keep the compatibility matrix open in a tab.

What "built-in AI" actually means

So what is it. Not a cloud endpoint you hit with a key. Not a wrapper around someone's REST API. Not a model you bundle and ship yourself.

Built-in AI is a language model — Gemini Nano, a few billion parameters — sitting on the user's disk, managed by Chrome, exposed to your page through bare browser globals like LanguageModel. You reach for it the way you reach for fetch or localStorage. The model runs on the user's own hardware — the GPU where there's the VRAM for it, the CPU otherwise. The inference never leaves the tab.

Which means the three numbers that used to define whether an AI feature was even worth building — cost per call, round-trip latency, data-egress risk — all collapse to the same value.

Zero.

The API family

Here's the whole surface on one screen. One general-purpose Prompt API, a set of higher-level task APIs, an embeddings layer, and an agentic layer — each row linking to the lesson that goes deep.

APIWhat it doesLesson
Prompt API (LanguageModel)Chat-style prompting with streaming and structured outputPrompt API
SummarizerCompress long text to a TL;DR, key points, or a headlineSummarizer
Translator + LanguageDetectorDetect the language, then translate across dozens of language pairsTranslator + Language Detector
Writer / RewriterDraft new text, or change an existing text's tone, length, or formalityWriter & Rewriter
ProofreaderGrammar and spelling fixes, with positionsProofreader
EmbeddingsTurn text into vectors for on-device search and similarityEmbeddings
MultimodalFeed images to the model alongside textMultimodal
WebMCPExpose your page's actions as tools an agent can callWebMCP
Generative UILet the model render UI through MCP appsGenerative UI
MCP clientDrive external MCP tools from the browserMCP client
Observability & tracingSee what the model actually did, call by callObservability & tracing
EvaluationMeasure whether the output is any goodEvaluation

One model sits behind almost every row — Gemini Nano, sized to whatever hardware you're on. The task APIs aren't separate models either; they're LoRA adapters — a task-specific layer bolted onto the same base weights — so you download the base once and the rest of the table lights up. Break that one download and they all go down together. Embeddings is the lone exception: it runs its own small model, embeddinggemma-300m, on a separate download.

Why on-device — and the honest cost

So why run a model locally when GPT-5, Claude, and Gemini Ultra are all sitting in the cloud, smarter than Nano will ever be? Four reasons.

Privacy — the text never leaves the browser process, which collapses an entire category of compliance work: no data-processing agreement with an LLM vendor, no data-residency engineering, no third-party-processor disclosure for the AI step. For a fintech or a healthtech product, that legal saving dwarfs the inference saving.

Cost — a cloud call runs $0.001 to $0.10, and a local call runs $0. That's not a discount. That's a different economic model, where "summarize this on every keystroke" goes from a line item to a for loop.

Latency — no network round trip. A fraction of a second to warm a session, then tokens stream at conversational speed.

Offline — the plane, the train, the dead-zone office. The feature just works.

Now, let's be honest about the bill. It's a one-time, multi-GB model download that blocks the very first create() until it finishes. It's desktop only — Windows 10/11, macOS 13+, Linux, or ChromeOS on a Chromebook Plus (Platform 16389.0.0+); no Android, no iOS. And it's hardware-dependent: a machine under the bar returns unavailable and stays there, and there's no flag you can flip to fix that for the user. Pick the tool for the job — Nano for the 80% of asks that don't need cloud-scale reasoning, the cloud for the rest. Most teams badly overestimate how often "the rest" shows up.

The one pattern every API shares

Learn this once and you've learned all of them. Every built-in AI API — Prompt, Summarizer, Translator, the lot — walks the same four steps: ask whether it's available, create an instance, use it, then hand the memory back.

Feature-detect first, because the global is simply undefined on http:// pages and on browsers that don't ship it. Then availability() returns exactly one of four states: unavailable, downloadable, downloading, or available. Then create() — declaring the languages you'll use with expectedInputs and expectedOutputs, so the model knows what's coming in and what goes back out. Then you prompt. Then you destroy() — the garbage collector will reclaim an abandoned session eventually, but destroy() hands the memory back sooner.

hello.js
if (typeof LanguageModel === 'undefined') {
// Not desktop Chrome, or not a secure context (https / localhost).
console.log('Built-in AI is not available here.');
} else {
const status = await LanguageModel.availability();
// "unavailable" | "downloadable" | "downloading" | "available"
if (status !== 'unavailable') {
// First run downloads the model. In real code pass a `monitor` — a create()
// callback that reports download progress — and start create() from a user
// gesture, since kicking off that download needs one. The demo does both.
const session = await LanguageModel.create({
expectedInputs: [{ type: 'text', languages: ['en'] }],
expectedOutputs: [{ type: 'text', languages: ['en'] }],
});
// Stream the reply — chunks are deltas, so append them.
const stream = session.promptStreaming('In one sentence: where does this model run?');
let reply = '';
for await (const chunk of stream) reply += chunk;
console.log(reply); // e.g. "It runs locally, on the user's own device."
session.destroy();
}
}

Four verbs. Every lesson after this one is a variation on those four.

Try it

Run it locally: open 01-introduction/index.html from the chrome-ai-course repo in desktop Chrome. Or use the hosted demo: windowai.danduh.me.

Expected: the page reports whether Gemini Nano is available; click the button and it streams a one-sentence definition of on-device AI, then frees the session.

Requires: desktop Chrome with Gemini Nano available — check your browser at windowai.danduh.me/status, or work through Setup & the availability lifecycle.

Gotchas & troubleshooting

Three things bite everyone on first contact. They're the same lesson wearing different hats: the model is a real, heavy, local resource, not an HTTP endpoint.

The global is undefined

Symptom: your feature-detect fails and nothing runs.

Cause: you're not on desktop Chrome, or the page is served over http://. These globals only exist in a secure context — https or localhost.

Fix: open the page in desktop Chrome over https or localhost, and always guard with typeof LanguageModel !== 'undefined' before you touch it.

The first call hangs for a minute

Symptom: create() sits there and never resolves.

Cause: the first create() triggers the one-time multi-gigabyte model download, and it won't resolve until the download finishes.

Fix: pass a monitor and render e.loaded * 100 as a progress bar (e.loaded is a 0..1 fraction; e.total is always 1). Show progress; don't block the UI on a frozen button.

availability() says unavailable and never changes

Symptom: the state is stuck on unavailable no matter what you do.

Cause: the device is under the hardware bar, on-device AI is switched off in Settings, or an enterprise policy blocks it.

Fix: this isn't a bug you can code around — degrade gracefully with a message and a cloud fallback. Don't poll availability() in a loop hoping it flips: it's read-only and never starts a download, so the poll just spins for nothing.

How the course is structured

The course goes zero to superhero, and it climbs in layers.

Foundations first: Setup & the availability lifecycle gets the model on disk and drills the state machine you just met.

Then the core: the Prompt API, structured output and tool calling, and image input. That's the raw model plus everything you can make it do.

Then the task APIs — the constrained, more reliable ones. Summarizer, Translator plus Language Detector, Writer, Rewriter, Proofreader. Compress, translate, draft, fix. Each one rides a task-specific LoRA adapter on the same Nano base, so it holds its shape better than asking the raw model nicely.

Then the semantic layer: embeddings for search and similarity, all on-device.

Then the agentic layer, which is where it gets loud. WebMCP turns your page into a tool surface, generative UI lets the model render components, and an in-browser MCP client drives external tools.

Then the part everyone skips and later regrets: the production tail. Observability and tracing so you can see what the model actually did, evaluation so you know whether it's any good, and finally shipping and compatibility so it survives a real browser matrix.

Foundations, core, tasks, embeddings, agents, then proof it works. That's the climb.

Recap

So, the shape of it:

  • Built-in AI is Gemini Nano — a real model shipped inside Chrome, reached through bare globals like LanguageModel, running on-device with no backend, no key, and no data egress.
  • The API family is small: one Prompt API, a set of task APIs, embeddings, and an agentic/MCP layer — almost all of it over one model, Gemini Nano (embeddings are the exception, on their own small model).
  • On-device buys you privacy, zero marginal cost, low latency, and offline — and costs you a one-time multi-GB download, desktop-only reach, and hardware variance.
  • Every API reuses one pattern: availability()create() → use → destroy().

You've been renting a model your users already own.

Next steps


Next: Setup & the availability lifecycle