A PROPOSAL FOR THE WEB

Models belongin everybrowser.

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.

Read the proposal
Translucent blue model layers supported by a shared navy platform, connected by fine lines.
OPEN MODELS.AT HOME IN THE BROWSER.
APPS REQUEST THE MODELUSERS CONTROL ACCESSBROWSERS MANAGE EXECUTION

Your app depends
on its model.

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.

[ 01 ]

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.

[ 02 ]

Shared weights,separate permissions.

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.

[ 03 ]

Access youcontrol.

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.

Choose a model.
Ask to use it.

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.

YOUR-APP.JS
// 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();
  }
});
Your page requests it. The user approves it. The browser runs it.

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.

Start with a direct call. Use a session when it helps.

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.

Clearly labeled capabilities.

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.

A model ID points to a release.

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.

THE RIGHT MODEL FOR THE TASK

Open an example to see the code. The model IDs are placeholders; replace them with the specific model IDs your app uses.

Get to the key points.Turn a long article, report, or document into a short summary.generate()
SUMMARIZE.JS
// 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();
  }
});
Keep the input within the model’s context window. Split longer documents into sections.

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.

Read the receipt.Extract the merchant, date, and total with a model that supports images and structured output.output.schema
RECEIPT.JS
// 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();
  }
});
A completed response matches the schema. Your app should still let people review the extracted values.

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.

Search by meaning.Use the same embedding model to index and search notes, so the vectors stay compatible.embed()
SEARCH.JS
// 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();
  }
});
For a saved index, store the returned model ID with the vectors. Compare only vectors from the same model.

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.

A capable web.
A clear boundary.

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.

01

Consent before discovery

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.

02

Access has boundaries

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.

03

Your device has limits

The browser sets quotas and resource policies. Access to a model is useful, but it is not an unlimited claim on compute.

04

Shared models, separate data

Shared model weights do not mean shared conversations. Each site keeps its data private, and model execution stays isolated.

DRAFT FOR DISCUSSION

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.

A BETTER WEB IS A SHARED PROJECT

Help make this
a web capability.

What would you build? What would need to change?
Bring a use case, implementation concern, or question
to the WICG discussion.

Comment on the proposal

Discussion on GitHub · WICG proposals #306

Read the full explainer