Chrome's Prompt API is stable: on-device AI for your website, no API key and no per-token bill
Calling a language model from a web page no longer requires an API key, a per-token invoice, or sending user data to a server. Chrome's Prompt API, powered by Gemini Nano, is stable in extensions since Chrome 138 and on
Calling a language model from a web page no longer requires an API key, a per-token invoice, or sending user data to a server. Chrome's Prompt API, powered by Gemini Nano, is stable in extensions since Chrome 138 and on the web since Chrome 148: a page in Chrome can ask the browser to summarize, classify or rewrite, locally. For extension developers, this is a category shift: AI becomes a browser capability, on par with storage or notifications.
It is not magic, though. The model downloads on first use, it can be absent (insufficient hardware, limited storage), and the API surface differs subtly between the web and extensions. Without serious feature detection and a fallback, your "smart" feature becomes a dead button for part of your users.
This guide builds the complete pattern from the official documentation: detect, create a session, stream the response, handle the model download, and fall back cleanly to a backend when Nano is not there. The code is self-contained, testable in the console, and honest about its limits.
Contents:
- What is stable, what is not
- How it works under the hood
- Hands-on: the detect, create, stream pattern
- Web or extension: the differences that matter
- Anti-pattern: calling without checking availability
- Edge cases and failures
- How to measure and verify
- Privacy and architecture questions before you ship
- Designing a reusable, measurable session
- Key takeaways
What is stable, what is not
As of October 22, 2026, the official documentation draws this map:
| API | Extensions | Web |
|---|---|---|
| Prompt API (LanguageModel) | Stable since Chrome 138 | Stable since Chrome 148 |
| Summarizer, Translator, Language Detector | Stable since Chrome 138 | Stable since Chrome 138 |
| Writer, Rewriter, Proofreader | In development | In development |
Sampling parameters (samplingMode) |
Legacy topK/temperature | Origin trial only |
Two clarifications that save debugging hours. First, the current surface is called LanguageModel: old examples based on window.ai date from the 2024 previews and throw errors. Second, expired origin trial permissions (aiLanguageModelOriginTrial) must be removed from extension manifests; the documentation calls this out explicitly.
How it works under the hood
The model doing the work is Gemini Nano, downloaded once by the browser and shared across origins. Your site does not ship the model, Chrome does, which is why there is no key and no billing. Three practical consequences:
- The first call triggers a potentially long download, to be treated as a first-class interface state.
- The exact size varies with browser updates (
chrome://on-device-internalsshows the current value), and if available space drops below 10 GB after download, Chrome removes the model. - Supported languages are limited at this stage: English, Japanese, Spanish, German and French for text inputs and outputs; inputs can also be image or audio, outputs remain text.
Hands-on: the detect, create, stream pattern
The following block is self-contained: paste it into the console of an HTTPS page on Chrome 148+, or into an extension service worker on Chrome 138+. It illustrates the rhythm the API imposes: the same options to availability() and create(), then response streaming.
// prompt-api-pattern.js - paste into the console (Chrome 148+ web, 138+ extension)
async function askOnDevice(question, onToken = console.log) {
// 1. The SAME options go to availability() and create():
// an unsupported modality is only detectable this way.
const options = {
expectedInputs: [{ type: 'text', languages: ['en'] }],
expectedOutputs: [{ type: 'text', languages: ['en'] }]
};
if (!('LanguageModel' in self)) return { source: 'fallback' };
const status = await LanguageModel.availability(options);
if (status === 'unavailable') return { source: 'fallback' };
// 2. create() triggers the download if needed;
// plan a visual indicator while it downloads.
const session = await LanguageModel.create({
...options,
monitor(m) {
m.addEventListener('downloadprogress',
e => console.log(`Model: ${Math.round(e.loaded * 100)}%`));
}
});
// 3. Streaming: each chunk is a delta (only the new tokens,
// the API's current behavior): accumulate them.
let text = '';
const stream = session.promptStreaming(question);
for await (const chunk of stream) { text += chunk; onToken(chunk); }
session.destroy();
return { source: 'on-device', text };
}
const result = await askOnDevice('Summarize in one sentence what the Prompt API is.');
if (result.source === 'fallback') {
console.log('Nano unavailable here: fall back to your cloud backend.');
}
Verification: on a compatible machine, the first call shows download progress, then the streamed answer; on an incompatible one, the code prints the fallback path. If availability() returns downloadable, create() starts the download (user activation may be required).
For typing, the @types/dom-chromium-ai package provides TypeScript definitions for the Prompt API and the other built-in AI APIs.
Web or extension: the differences that matter
| Aspect | Web (Chrome 148+) | Extension (Chrome 138+) |
|---|---|---|
| Sampling |
samplingMode via origin trial only |
topK / temperature supported |
| Download trigger | User activation required | Same, but privileged context |
| Typical use cases | Page summaries, assisted forms | Background processing, side panel |
| Legacy trial permission | Not applicable | Must be removed from the manifest |
For an extension, the older stability window (138) is a real competitive edge: the server-side AI extension market charges for what Chrome offers here for free, offline, without sending the user's page to a third party.
Anti-pattern: calling without checking availability
// Avoid: session created blind
const session = await LanguageModel.create();
const out = await session.prompt('Summarize this page.');
// On a machine without Nano: unhandled exception,
// a surprise download mid-user-action,
// and no way out. The "AI" button becomes a trap.
The correct pattern first tests 'LanguageModel' in self, then availability() with the exact options, and always keeps a way out. Without that, the most modern feature in the product is also the most fragile.
Edge cases and failures
| Situation | Documented behavior | What to do |
|---|---|---|
availability() = unavailable
|
Incompatible hardware or policy | Cloud fallback, hide the feature |
availability() = downloadable
|
Model not on device yet |
create() starts the download, inform the user |
| Storage < 10 GB after download | Chrome removes the model | Expect re-download, do not treat as a bug |
| Language outside en/ja/es/de/fr | Not supported at this stage | Detect language first (Language Detector API) |
temperature on the web |
Not exposed by default |
samplingMode via origin trial, or leave unset |
| Long session left open | Memory retained |
session.destroy() after use, as in the pattern |
| JSON output expected | No schema constraint on text output | Validate and retry application-side |
How to measure and verify
Three quick checks are enough before shipping:
-
chrome://on-device-internalsconfirms the model's presence and current size. - An offline test (airplane mode) must work once the model is downloaded: that is the whole promise.
- Measure time to first token (
performance.now()beforecreate()and at the first stream iteration) on an entry-level machine: that latency, not yours on a MacBook Pro, is what your users will live with.
Privacy and architecture questions before you ship
The Prompt API's appeal (a language model built into the browser, no backend bill, no API key) comes with design questions a quick demo never raises. The first is privacy posture. On-device inference means user text does not have to leave the machine, which is a genuine architectural win for sensitive contexts. But "can stay local" is not the same as "stays local": the moment your feature falls back to a server model for unsupported browsers or heavier tasks, your data-flow diagram has two paths, and your privacy notice must describe both. Decide the fallback policy before the first user prompt, not after the first support ticket.
The second question is capability honesty. A built-in model is a generalist; it will summarize a support ticket and draft a reply, but it is not your fine-tuned domain model and it will not know your product catalog. Features that need your data still need your backend. The healthy pattern is a split brain: the browser model handles user-private, latency-sensitive text work, and your server handles anything that requires your knowledge.
The third question is durability. Browser APIs evolve; the extension surface reached stability earlier than the web surface. Gate the feature, detect availability at runtime, and degrade gracefully. Your users on other browsers will thank you for a good fallback far more than for a badge saying "Chrome only."
Designing a reusable, measurable session
The official documentation provides the two primitives that structure everything else: LanguageModel.availability() to know whether the model is ready, and LanguageModel.create() to open a session. The detail that makes the difference in production hides in an easy-to-miss caution: you must pass availability() exactly the same options as prompt() or promptStreaming(), because some models do not support every modality or language. In other words, availability is tested per configuration, not in absolute terms.
Architecture-wise, three habits pay off. One session per intent rather than one global session: the expectedInputs and expectedOutputs parameters (modalities and languages) are declared at creation, and a ticket-summarization session does not have the same shape as a translation session. An explicit download monitor: the official example shows the downloadprogress event at creation, and an interface that displays model-download progress avoids the "frozen app" feeling on first use. A measured fallback: when availability() says no, switch to your server path while logging the reason; that log is what will tell you, three months from now, whether the local feature deserves to be expanded.
Key takeaways
- The Prompt API is stable: extensions since Chrome 138, web since Chrome 148; it is called
LanguageModel, notwindow.ai. - Gemini Nano is downloaded and shared by Chrome: no key, no bill, but an initial download to handle and possible removal under 10 GB free.
- The golden rule: same options for
availability()andcreate(), and always a cloud fallback for incompatible devices. - Web and extensions do not share the same surface: fine sampling control remains an extension perk (or an origin trial).
- The decisive argument is privacy: on-device inference keeps prompts and page content on the machine, which simplifies many a conversation with the security team. On unsupported devices, what leaves the machine depends on your fallback implementation.
This guide is maintained on the ScrollFlow blog, where it was first published and corrected: Chrome's Prompt API: On-Device AI for Your Websites. Speaking of Chrome extensions: ours, ScrollFlow, records your web pages as video with automatic scrolling, 100% locally. If on-device AI speaks to you, local processing should too: Chrome Web Store.
What are you building with on-device AI? Curious to read your use cases in the comments.
References and further reading
-
Chrome for Developers - Prompt API: official documentation (138/148 stability,
LanguageModel,availability(), sessions, multimodal, languages). https://developer.chrome.com/docs/ai/prompt-api - Chrome for Developers - Get started with built-in AI: overview of the built-in AI APIs and Gemini Nano. https://developer.chrome.com/docs/ai/get-started
- Chrome for Developers - Built-in AI APIs: status of each API (Summarizer, Translator, Writer, etc.). https://developer.chrome.com/docs/ai/built-in-apis
- MDN - LanguageModel.promptStreaming(): streaming behavior and examples (delta chunks, accumulation). https://developer.mozilla.org/en-US/docs/Web/API/LanguageModel/promptStreaming
- npm - @types/dom-chromium-ai: official TypeScript definitions for the built-in AI APIs. https://www.npmjs.com/package/@types/dom-chromium-ai
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.