Skip to content

Summarizer API

Building block for the Web’s Built-in Summarizer API. String-mode summarization with session reuse, sentence-aware output cleaning, streaming, and pluggable result caching. The React adapter lives at @web-ai-sdk/summarizer/react; see useSummarizer.

import { summarize } from "@web-ai-sdk/summarizer";
const result = await summarize({
input: longArticleText,
language: "en",
type: "key-points",
length: "short",
onUpdate: (text) => render(text),
});
console.log(result.output, result.cached);

result.output is the cleaned summary text, or null when the input is empty. result.cached tells you whether the response came from the cache without invoking the model.

interface SummarizeOptions {
input: string;
language: string;
supportedLanguages?: readonly string[];
type?: "tldr" | "key-points" | "teaser" | "headline";
length?: "short" | "medium" | "long";
format?: "plain-text" | "markdown";
preference?: "auto" | "speed" | "capability";
sharedContext?: string;
monitor?: (m: CreateMonitor) => void;
cache?: "session" | "local" | { get, set };
cacheKey?: string;
onUpdate?: (text: string) => void;
signal?: AbortSignal;
}
NameTypeDescription
input requiredstringText to summarize. Empty / whitespace resolves to { output: null }.
language requiredstringBCP-47 language for input + output hints. Falls back to omitting hints if unsupported.
supportedLanguagesreadonly string[]Languages the model supports for hints. Hints are sent when language is in this set; omitted otherwise. Defaults to ["en", "es", "ja"].
type"tldr" | "key-points" | "teaser" | "headline"Summary shape. Defaults to "tldr".
length"short" | "medium" | "long"Length preset. Defaults to "medium".
format"plain-text" | "markdown"Output format. Defaults to "plain-text".
preference"auto" | "speed" | "capability"Performance preference hint. Defaults to "auto". See Performance preference.
sharedContextstringNative sharedContext string (a hint about who/what the summary is for).
monitor(m) => voidObserve the first-call model download (~1.7 GB on a fresh profile).
cache"session" | "local" | { get, set }Opt-in result cache.
cacheKeystringOverride the default cache key (defaults to JSON.stringify([pathname, trimmed input, normalizedLanguage, languageHints, type, length, format, preference, sharedContext]); normalizedLanguage is language’s lowercase primary subtag (e.g. pt-BRpt), and languageHints is a boolean for whether that language is in supportedLanguages).
onUpdate(text: string) => voidStreaming update callback. Receives the cumulative buffer, not deltas.
signalAbortSignalAbort signal.
interface SummarizeResult {
output: string | null;
cached: boolean;
}
  1. Trim and cache check. Whitespace-only input short-circuits to null. If a cache is configured and has the key, return immediately.
  2. Cache Summarizer.create() sessions by JSON-stringified options. First call pays the ~1-3s cold start; later same-config calls reuse the warm session.
  3. Stream summarizeStreaming() when the instance supports it, falling back to one-shot summarize(). Cleaned chunks are pushed to onUpdate as they arrive (cumulative buffer, not deltas).
  4. Optionally cache the final summary when you pass a cache. Off by default; opt in for revisits in the same tab to render instantly.

Session cache (internal, in-memory, always on): a bounded LRU of Summarizer instances so the second call with the same { language, type, length, sharedContext, … } reuses the warm session. Cold-start is ~1-3s; warm sessions are sub-second. Cleared on full page reload.

Result cache (opt-in): off by default; every call hits the model. Pass cache: "session" for sessionStorage, cache: "local" for localStorage, or any { get, set }-shaped object to memoize the final summary string by cacheKey.

import { summarize } from "@web-ai-sdk/summarizer";
// Off by default; every call hits the model.
summarize({ language: "en", input: text });
// Per-tab caching via sessionStorage
summarize({ language: "en", input: text, cache: "session" });
// Persistent caching across tabs
summarize({ language: "en", input: text, cache: "local" });

result.cached tells you which path served the response, so you can render a “From cache” hint or skip a re-fetch.

When the underlying Summarizer instance exposes summarizeStreaming(), the library uses it and pushes cleaned chunks to onUpdate as they arrive (cumulative buffer, not deltas). Otherwise it falls back to one-shot summarize(). Either way, result.output is the final cleaned text.

preference is a hint about the speed/quality tradeoff the browser should make when it picks the underlying model. It maps straight to the native Summarizer.create() option:

  • "auto" (default) — the browser balances execution speed with summarization capability, and may adjust based on the environment, system constraints, or context.
  • "speed" — prioritizes low latency and fast execution. This can route to a smaller, faster model, which may produce less nuanced or simpler summaries.
  • "capability" — prioritizes comprehensiveness and coherence, capturing subtler context at the cost of higher latency.
// Fast path for short, low-stakes summaries (e.g. list previews).
summarize({ language: "en", input: text, preference: "speed", length: "short" });
// Quality path for a single, carefully written summary.
summarize({ language: "en", input: text, preference: "capability" });

The default is "auto" to match the platform default; "speed" and "capability" are explicit opt-ins.

Model tiering for "speed" and "capability" is experimental: enable the Summarizer API performance preference flag, restart your browser, and reload — otherwise the browser may accept the hint without switching models.

The faster model behind "speed" does not serve every configuration. Some combinations of language, type, length, and format are not available on the speed path, and there is no promise that the browser silently upgrades you to a more capable model when they aren’t — the configuration can instead be reported as unavailable. When that happens, summarize() throws SummarizerUnavailableError, the same way it does for any unavailable configuration.

This wrapper forwards preference into the availability() probe, so the probe and the session create() agree: if availability() reports the "speed" configuration as unavailable, summarize() fails fast instead of creating a session that can’t serve the request. Because of this, treat "speed" as an opt-in you guard — be ready to fall back to "auto" on unavailability (or pre-check with checkAvailability({ preference: "speed", … })):

import { summarize, SummarizerUnavailableError } from "@web-ai-sdk/summarizer";
const opts = { language: "en", input: text, type: "tldr", length: "short" } as const;
try {
await summarize({ ...opts, preference: "speed" });
} catch (err) {
if (err instanceof SummarizerUnavailableError) {
await summarize({ ...opts, preference: "auto" });
} else throw err;
}

The wrapper strips wrapping quotes / whitespace and collapses internal whitespace on every result regardless of type. Anything beyond that — e.g. trimming the trailing period from a type: "headline" result so it reads as a label rather than a sentence — is your concern. A one-line regex after the call covers the headline case.

The Web’s Built-in Summarizer (Chrome 138+, Edge 138+) only accepts expectedInputLanguages / outputLanguage for ["en", "es", "ja"]. Pass any other language as language: "pt" and the library omits those hints. The model still runs, but you steer it via sharedContext instead:

summarize({
language: "pt",
input: text,
sharedContext: "Resuma o artigo em português em 2-3 frases curtas.",
});

When Chrome adds more accepted languages, pass them explicitly via supportedLanguages: ["en", "es", "ja", "pt"] and the hints fire through.

The vanilla summarize() throws SummarizerUnavailableError when the API is missing. Callers branch explicitly:

import { summarize, SummarizerUnavailableError } from "@web-ai-sdk/summarizer";
try {
const result = await summarize({ language: "en", input: text });
} catch (err) {
if (err instanceof SummarizerUnavailableError) {
return;
}
throw err;
}

AbortSignal is supported. Aborting mid-stream resolves cleanly; an opt-in result cache is not written for aborted runs.