Published: June 23, 2026 (guest post by Thomas Steiner, Developer Relations Engineer, Chrome team at Google)
Transformers.js gives web developers an easy way to run transformer-based models in the browser via task-specific pipelines. For example, an automatic speech recognition (ASR) pipeline can be created and run like this:
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0';
const asr = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny.en', { device: 'webgpu' });
const result = await asr('jfk.wav');
console.log(result);
The cache problem: duplicate downloads and storage
When running the example in-browser, Transformers.js downloads and caches model resources and Wasm files using the Cache API so reloads are fast. However, popular models such as Xenova/whisper-tiny.en (the Transformers.js default ASR model) may be requested by many different sites. In a toy simulation, serving the same app from a different origin caused an additional 177 MB of duplicate download and storage visible in Chrome DevTools' Application panel.
Shared runtime binaries make it worse: different pipelines often depend on the same Wasm runtime. For example, the ONNX Runtime file ort-wasm-simd-threaded.asyncify.wasm is 4,733 kB and used by unrelated models. If a second origin loads a page, the browser downloads and caches that Wasm file again.
Why this happens: cache isolation
Browsers isolate caches for security and privacy. In Chrome, cached resources are keyed not only by resource URL but also by a Network Isolation Key composed of the top-level site and the current-frame site. Thus identical resource URLs can produce different cache keys when requested from different origins, preventing cache hits and causing duplicate downloads and storage. This is the problem the Cross-Origin Storage proposal targets.
What Cross-Origin Storage (COS) proposes
The Cross-Origin Storage API proposes a navigator.crossOriginStorage interface that lets web apps store and retrieve large files across origin boundaries using cryptographic hashes as identifiers rather than URLs. Because files are identified by hash, the same ort-wasm-simd-threaded.asyncify.wasm file downloaded while visiting one origin is recognized as identical to the same file requested by another origin.
Basic flow example:
const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4' };
try {
const handle = await navigator.crossOriginStorage.requestFileHandle(hash);
const fileBlob = await handle.getFile(); // Cache hit
} catch {
const fileBlob = await fetch('https://cdn.jsdelivr.net/.../ort-wasm-simd-threaded.asyncify.wasm').then(r => r.blob());
const handle = await navigator.crossOriginStorage.requestFileHandle(hash, { create: true, origins: '*' });
const writableStream = await handle.createWritable();
await writableStream.write(fileBlob);
await writableStream.close();
}
If the resource is present in COS you receive a FileSystemFileHandle and can read the Blob via getFile(). If not, you fetch it from the network and write it to COS for future callers — which could be the same app or an unrelated app on another origin.
Visibility control
COS provides fine-grained control over who can find a stored file using the origins option:
- origins: '*' makes the file globally discoverable by hash — suitable for public AI models and shared Wasm runtimes.
- origins: ['https://write.example.com', 'https://calculate.example.com'] restricts access to listed origins — useful for proprietary models used across a company’s sites.
- Omitting origins limits availability to same-site origins — a reasonable default for resources shared within an organization but not publicly discoverable.
Visibility can be upgraded but not downgraded: a resource made globally available cannot later be restricted by re-storing it with a narrower origins list. Conversely, a restricted resource can be widened by another site calling requestFileHandle() with create: true and broader origins; the browser requires the caller to write the full file through the returned handle to avoid side-channel detection.
Integrity by design
When writing a file to COS the browser verifies the declared hash; if the data doesn't match the hash the write fails. This enforces integrity automatically: apps reading from COS get the exact bytes they expect, regardless of whether the source was the official Hugging Face CDN or a mirror.
Privacy considerations
Shared cross-origin caches could allow probing for the presence of particular files by hash, potentially leaking browsing history information. COS mitigates this via:
- The origins field: developers are encouraged not to store proprietary probeable resources with origins: '*'.
- Availability gating: the browser may suppress confirmations for files that have only been encountered on a small number of origins. A file seen on only one or two sites could act as a cross-site identifier, so the browser may respond as if the file were absent even if it exists on disk. The Chrome team is considering additional mitigations and is still finalizing concrete rules.
Because an error can mean either "not stored" or "stored but not revealed", applications should always fall back to fetching from the network.
Practical impact for Transformers.js
In the toy scenario, the 4,733 kB ort-wasm-simd-threaded.asyncify.wasm runtime and the 177 MB of duplicated Whisper model weights only need to cross the network once when COS is used. The first app stores the runtime and model under their SHA-256 hashes with origins: '*', and subsequent apps on other origins instantly find them in COS. Transformers.js has piloted COS at the library level.
Transformers.js pilot and usage
Pull request #1549 added an experimental COS cache backend behind an opt-in flag. Enabling it is a single line before pipeline setup:
import { env, pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0";
env.experimental_useCrossOriginStorage = true;
With this flag, Transformers.js resolves SHA-256 hashes for each Xet-tracked model file by fetching the raw Xet pointer and extracting the oid sha256: field, then uses that hash as the key for navigator.crossOriginStorage. If the model is already in COS, it's served without a network round-trip; otherwise the library downloads and stores it for the next caller.
Model flexibility
Transformers.js's Model Registry supports flexible selection: if an app can work with any of several Whisper variants (for example Xenova/whisper-tiny.en, whisper-medium.en, or Xenova/whisper-large-v3), the registry can probe COS for associated files and decide which model to use. ModelRegistry.is_pipeline_cached() integrates with COS and the Cache API to make this ergonomic.
Try COS today
COS is not natively implemented in browsers yet, but you can experiment by installing the Cross-Origin Storage extension, which injects a navigator.crossOriginStorage polyfill. With the extension installed you can reproduce the end-to-end flow and observe that a model previously causing 177 MB of re-download is now served from COS in milliseconds. The extension's popup can show shared resources and their SHA-256 hash (for example 950978b1dbcbf250335358c1236053ba19a7f7849b33dc777f4421b72b7626fa) along with the origins that have that file in COS.
Call to action
If you're building a Transformers.js app, enable env.experimental_useCrossOriginStorage = true before your first pipeline() call and install the extension to remove duplicate downloads from the Network tab. Opting in is risk-free: if COS is unavailable the code falls back to the Cache API path. Other projects experimenting with COS include WebLLM (opt-in) and wllama (automatic PR).
The Chrome team is considering native implementation of COS in the browser and welcomes feedback on the proposal. The Cross-Origin Storage repository is the place to file issues, express support, or open PRs.



