Dev.to WebDev 🛠 Dev 👁 0 📖 8 min read

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:

  1. What is stable, what is not
  2. How it works under the hood
  3. Hands-on: the detect, create, stream pattern
  4. Web or extension: the differences that matter
  5. Anti-pattern: calling without checking availability
  6. Edge cases and failures
  7. How to measure and verify
  8. Privacy and architecture questions before you ship
  9. Designing a reusable, measurable session
  10. 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-internals shows 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:

  1. chrome://on-device-internals confirms the model's presence and current size.
  2. An offline test (airplane mode) must work once the model is downloaded: that is the whole promise.
  3. Measure time to first token (performance.now() before create() 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, not window.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() and create(), 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

  1. Chrome for Developers - Prompt API: official documentation (138/148 stability, LanguageModel, availability(), sessions, multimodal, languages). https://developer.chrome.com/docs/ai/prompt-api
  2. 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
  3. Chrome for Developers - Built-in AI APIs: status of each API (Summarizer, Translator, Writer, etc.). https://developer.chrome.com/docs/ai/built-in-apis
  4. MDN - LanguageModel.promptStreaming(): streaming behavior and examples (delta chunks, accumulation). https://developer.mozilla.org/en-US/docs/Web/API/LanguageModel/promptStreaming
  5. npm - @types/dom-chromium-ai: official TypeScript definitions for the built-in AI APIs. https://www.npmjs.com/package/@types/dom-chromium-ai
📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.