Chrome Built-in AI APIs for Translator and Summarizer
Chrome now exposes task-focused built-in AI APIs that can run inference in browser contexts with explicit capability checks and model download flow. This guide focuses on two APIs: Translator and Summarizer, implemented through two reference tools I built for validation.
The engineering value is straightforward: low-latency UX for short text tasks, improved privacy for user-generated content, and cleaner progressive enhancement patterns than a mandatory server-side AI dependency.
Guide Scope
This is an implementation-first breakdown: support constraints, runtime checks, API creation patterns, and fallback architecture.
Current Support Snapshot (as of February 19, 2026)
Translator API
Stable in Chrome 138+ with browser-provided expert translation models.
Summarizer API
Stable in Chrome 138+ with Gemini Nano downloads and device constraints.
Context Limits
Desktop web pages/extensions supported; Android and Web Worker support are currently unavailable.
- Translator API is available in Chrome 138+ stable and uses browser-provided expert models.
- Summarizer API is available in Chrome 138+ stable and uses Gemini Nano model downloads.
- Desktop support applies to web pages and extensions. Mobile support is currently not available for these APIs.
- APIs are exposed to top-level windows and same-origin iframes; Web Worker support is currently not available.
- Summarizer has additional device/runtime constraints (OS, memory, storage, and compute profile).
- Feature availability is runtime-dependent and can return unavailable, downloadable, downloading, or available states.
- Browser support can change quickly, so always check the latest Chrome docs before shipping.
| API | Chrome | Desktop | Android | Web Worker | Model Path |
|---|---|---|---|---|---|
| Translator | 138+ | Yes | No | No | Built-in expert model |
| Summarizer | 138+ | Yes | No | No | Gemini Nano download |
Implementation Flow You Should Use
- Run feature detection before showing controls.
- Call availability checks to understand whether the model is already present or needs download.
- Create API instances from explicit user interaction handlers.
- Dispose API objects after use to avoid unnecessary memory retention in long-lived sessions.
- Show progress/loading states and provide robust fallback UX for unsupported browsers.
Implementation Checklist
- Detect API presence with `in window` checks.
- Branch UI by availability state before triggering create().
- Attach download progress monitor for first-run model fetches.
- Destroy translator/summarizer instances after completion.
Working Demo
Use this embedded demo to test both APIs in your current browser. It defaults to streaming output and includes a toggle to switch between streaming and sync modes. If the browser/runtime is unsupported, availability will show as unavailable and actions stay disabled.
This demo only runs where the APIs are exposed. If unsupported, use the same fallback path described in the architecture section.
export function canUseTranslator() {
return typeof window !== "undefined" && "Translator" in window;
}
export function canUseSummarizer() {
return typeof window !== "undefined" && "Summarizer" in window;
}
export async function getTranslatorAvailability() {
if (!canUseTranslator()) return "unavailable";
return Translator.availability({
sourceLanguage: "en",
targetLanguage: "es",
});
}
export async function getSummarizerAvailability() {
if (!canUseSummarizer()) return "unavailable";
return Summarizer.availability();
}Gate create() with user activation
Run create() only from direct user actions (button click, form submit) to avoid activation-related failures.
export async function createSummarizerSafely() {
if (!("Summarizer" in window)) throw new Error("unsupported");
if (!navigator.userActivation?.isActive) {
throw new Error("create() must run from a user gesture");
}
return Summarizer.create({
type: "key-points",
format: "markdown",
length: "medium",
});
}Handling Streaming vs Sync Responses
Both APIs should be integrated with two output modes: sync andstreaming. The best pattern is a response-mode switch with automatic fallback to sync when streaming methods are unavailable.
- Use streaming for progressive rendering and faster perceived response.
- Use sync for simpler flow control and deterministic full outputs.
- Keep sync fallback enabled even when streaming mode is selected.
type OutputMode = "streaming" | "sync";
async function runWithOutputMode(
mode: OutputMode,
runSync: () => Promise<string>,
runStream:
| (() => Promise<AsyncIterable<string> | ReadableStream<string> | string>)
| undefined,
onNext: (value: string) => void,
) {
if (mode === "streaming" && runStream) {
const source = await runStream();
if (typeof source === "string") {
onNext(source);
return;
}
let merged = "";
if (Symbol.asyncIterator in source) {
for await (const chunk of source as AsyncIterable<string>) {
merged += chunk;
onNext(merged);
}
return;
}
if ("getReader" in source) {
const reader = (source as ReadableStream<string>).getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
merged += value;
onNext(merged);
}
return;
} finally {
reader.releaseLock();
}
}
}
onNext(await runSync());
}Translator API in practice
- Use source/target language values explicitly on create().
- Track model download progress for first-run transparency.
- Support both `translate()` and `translateStreaming()` response paths.
- Dispose translator instances immediately after output generation.
export async function translateText(
input: string,
targetLanguage: string,
mode: "streaming" | "sync",
onNext: (value: string) => void,
) {
const translator = await Translator.create({
sourceLanguage: "en",
targetLanguage,
monitor(monitor) {
monitor.addEventListener("downloadprogress", (event) => {
console.log("translator download", event.loaded);
});
},
});
try {
await runWithOutputMode(
mode,
() => translator.translate(input),
typeof translator.translateStreaming === "function"
? () => translator.translateStreaming!(input)
: undefined,
onNext,
);
} finally {
translator.destroy?.();
}
}Summarizer API in practice
- Choose summarization type/format/length based on product use-case.
- Monitor first-run model download states.
- Support both `summarize()` and `summarizeStreaming()` response paths.
- Destroy summarizer instances once summary text is returned.
export async function summarizeText(
input: string,
mode: "streaming" | "sync",
onNext: (value: string) => void,
) {
const summarizer = await Summarizer.create({
type: "key-points",
format: "markdown",
length: "medium",
monitor(monitor) {
monitor.addEventListener("downloadprogress", (event) => {
console.log("summarizer download", event.loaded);
});
},
});
try {
await runWithOutputMode(
mode,
() => summarizer.summarize(input),
typeof summarizer.summarizeStreaming === "function"
? () => summarizer.summarizeStreaming!(input)
: undefined,
onNext,
);
} finally {
summarizer.destroy?.();
}
}Fallback Strategy for Real Users
- If API support is missing, disable action buttons and show compatibility guidance.
- Keep the input editable so users can still copy text to alternate tools.
- Avoid hard failures; unsupported browsers should still see a usable interface.
- Log support and availability states so you can monitor real-world adoption.
- For broad compatibility, ship a hybrid path that can switch to server-side inference.
type AiPath = "browser" | "server";
export async function resolveAiPath() {
const hasSummarizer = typeof window !== "undefined" && "Summarizer" in window;
if (!hasSummarizer) return "server" as AiPath;
const availability = await Summarizer.availability();
if (availability === "available" || availability === "downloadable") {
return "browser" as AiPath;
}
return "server" as AiPath;
}