The model youtested against.
The best model for scanning a receipt, transcribing audio, and searching documents may differ. A model ID lets a developer ask for the same model and release in any browser.
A PROPOSAL FOR THE WEB
The model your app was built for.
With user permission. In the browser.
navigator.models is a proposed browser API for web apps that need a particular open-weight model on-device.

A workflow built around one model may not work with another. Apps should be able to ask for the model they built and tested against, regardless of which browser someone uses.
Web Models makes that request explicit. The page names a model ID. The browser runs that model or tells the page it can’t. There is no quiet fallback.
The best model for scanning a receipt, transcribing audio, and searching documents may differ. A model ID lets a developer ask for the same model and release in any browser.
Some models take gigabytes to download and plenty of memory to run. The browser can share their weights across sites, so every site does not need to download and store another copy.
An app asks for a model, and the browser asks the user. They can see and revoke access for each site and model. The browser also keeps an eye on setup, execution, and resource limits.
Every top-level inference call on navigator.models names one model ID.
Your app picks the model and calls the API. The user grants access. The browser runs it.
// Request the model your app was built and tested against.
const model = '<model-id>';
button.addEventListener('click', async () => {
if (!('models' in navigator)) return showFallback();
try {
const { status } = await navigator.models.requestAccess(model);
if (status !== 'granted') return showFallback();
const result = await navigator.models.generate(model, {
messages: [{ role: 'user', content: 'Explain the web in one sentence.' }],
});
output.textContent = result.data ?? '';
} catch {
showFallback();
}
});This is just illustrative JavaScript. <model-id> is a placeholder. Your app provides button, output, and showFallback(). The first access request has to start with a user gesture. This page does not run a model.
models.generate()Send a prompt and get a complete response.
models.stream()Receive the response as it is generated.
models.embed()Turn text into vectors for search and similarity.
For a one-off task, make a direct call. If you will use the model again, keep it in a session. Your app still owns the conversation history.
Once access is granted, the browser tells you what that model supports. Vision, audio, tools, structured output, reasoning, and embeddings all depend on the model you chose.
Each ID is tied to fixed weights, a tokenizer, and default input and generation settings. A changed release gets a new ID, so developers decide when to move to it. Shared catalogs, model retirement, and cross-browser compatibility tests are still open questions.
Open an example to see the code. The model IDs are placeholders; replace them with the specific model IDs your app uses.
generate()// Placeholder: replace with the ID of your chosen LLM.
const model = '<llm-model-id>';
button.addEventListener('click', async () => {
if (!('models' in navigator)) return showFallback();
try {
const { status } = await navigator.models.requestAccess(model);
if (status !== 'granted') return showFallback();
const result = await navigator.models.generate(model, {
messages: [
{ role: 'system', content: 'Summarize the key points in three bullets.' },
{ role: 'user', content: documentInput.value },
],
});
if (result.finishReason !== 'stop') return showFallback();
output.textContent = result.data ?? '';
} catch {
showFallback();
}
});Here, documentInput is a text area containing the article or document. button, output, and showFallback() are supplied by your app. These examples are illustrative and do not execute on this page.
output.schema// Placeholder: a vision-capable model with structured output.
const model = '<vision-model-id>';
button.addEventListener('click', async () => {
if (!('models' in navigator)) return showFallback();
const image = imageInput.files?.[0];
if (!image) return showFallback();
try {
const { status } = await navigator.models.requestAccess(model);
if (status !== 'granted') return showFallback();
const result = await navigator.models.generate(model, {
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Extract the merchant, date, and total.' },
{ type: 'image', image },
],
}],
output: {
schema: {
type: 'object',
properties: {
merchant: { type: 'string' },
date: { type: 'string' },
total: { type: 'number' },
},
required: ['merchant', 'date', 'total'],
},
},
});
if (result.finishReason !== 'stop') return showFallback();
output.textContent = JSON.stringify(result.data, null, 2);
} catch {
showFallback();
}
});Here, imageInput is a file input for a supported receipt image (up to 10 MiB). button, output, and showFallback() are supplied by your app. These examples are illustrative and do not execute on this page.
embed()// Placeholder: replace with the ID of your chosen embedding model.
const model = '<embedding-model-id>';
button.addEventListener('click', async () => {
if (!('models' in navigator)) return showFallback();
try {
const { status } = await navigator.models.requestAccess(model);
if (status !== 'granted') return showFallback();
const index = await navigator.models.embed(model, notes);
const query = await navigator.models.embed(index.model, queryInput.value);
if (query.model !== index.model) return showFallback();
const matches = notes.map((text, i) => ({
text,
score: cosineSimilarity(query.vectors[0], index.vectors[i]),
})).sort((a, b) => b.score - a.score);
output.textContent = JSON.stringify(matches.slice(0, 5), null, 2);
} catch {
showFallback();
}
});Here, notes is a small, nonempty array of text strings and queryInput is the search field. Split larger collections into batches that fit the API’s limits. button, output, and showFallback() are supplied by your app. These examples are illustrative and do not execute on this page.
An API like this needs more than a way to generate text. It needs permissions, privacy boundaries, and a sense of the device it runs on.
Those are platform concerns.
requestAccess(model)A site can detect the API, but it cannot list the models on a device. It gets details about a model only after access is granted.
A grant applies to one top-level site, one requesting frame origin, and one model ID. Revoking it ends related work and destroys its sessions.
The browser sets quotas and resource policies. Access to a model is useful, but it is not an unlimited claim on compute.
Shared model weights do not mean shared conversations. Each site keeps its data private, and model execution stays isolated.
The proposal is still moving. Model ID governance, compatibility, and whether these capabilities should extend the Prompt API are open questions. The WICG proposal is open for comments.