Trusted Types — Polyfill
W3C polyfill · api_only · full · tinyfill · README.md
Important
How to use the official W3C Trusted Types polyfill when you must support browsers without native trustedTypes, and how that differs from native CSP enforcement.
Table of contents
- Native vs polyfill — what the browser does natively vs
api_only,full, and tinyfill - Which browsers need the polyfill? — when you can omit the script vs when you still need a shim
- Polyfill variants — trade-offs and intent
- Load in the browser (ES5 builds) — CDN
<script>tags for api_only and full - Node.js / bundlers (npm) —
npm install, CommonJS / ESM notes - Pairing with application code — feature-detect pattern, DOMPurify + trusted output
- Building from source (optional) — clone upstream repo,
npm run build - Demo and tests — hosted demo and platform-test paths
- When you can skip the polyfill — native-only support matrix
- Related in this repo —
README.md,playground/ - Links & resources — MDN, GitHub, npm, web.dev, CDN URLs
Native vs polyfill (what changes)
| Capability | Native (supporting browser + CSP) | Polyfill |
|---|---|---|
trustedTypes.createPolicy() |
Yes | api_only / full / tinyfill |
TrustedHTML etc. as real objects |
Yes | api_only / full (not tinyfill—see below) |
| CSP Trusted Types enforcement in the engine | Yes | full polyfill only approximates enforcement using a CSP string it infers (see upstream src/polyfill/full.js). api_only does not stop innerHTML = string. |
| tinyfill | N/A | Only stubs createPolicy; policies return strings; legacy sinks still accept strings—no enforcement. |
Tip
Practical takeaway: Develop and test with real Content-Security-Policy: require-trusted-types-for 'script' on a current browser. Use the polyfill so older browsers do not throw on trustedTypes and still run your sanitizer inside createHTML. Do not assume the polyfill fully replaces the browser’s enforcement model.
Which browsers need the polyfill?
Trusted Types is part of the interoperable web platform in current Chromium-, Firefox-, and Safari-based browsers (MDN documents the API under Baseline as broadly available in 2026—exact first versions change; use MDN browser compatibility or Can I use — Trusted Types for today’s matrix). For those engines, trustedTypes + CSP enforcement are native: you ship headers and policies; you do not need the polyfill for the API to exist or for the browser to enforce Trusted Types.
Note
The polyfill is mainly for older or constrained clients, not for every support matrix. Use the table below as a rule of thumb.
| Situation | Typical approach |
|---|---|
| You only support current evergreen (or stricter) and they all implement TT | Omit the polyfill; rely on native API + CSP. |
You still support older Chrome / Edge / Firefox / Safari without trustedTypes |
Load api_only or tinyfill so createPolicy exists and your sanitizers run; native enforcement does not apply there. |
| Embedded WebViews, enterprise locked builds, or ESR channels that lag stable | Same as row above until those versions leave your matrix. |
| You want DOM-style enforcement in a very old environment without native TT | full polyfill (understand it is not identical to native CSP). |
Tip
Summary: Baseline-style support means the polyfill is optional for modern browsers and mainly for older or constrained clients still in your support list.
Polyfill variants
1. api_only (light)
- Defines the Trusted Types API so you can call
createPolicyand use policy methods.
Warning
innerHTML = 'raw string' still works — there is no DOM enforcement from this build.
Tip
Use api_only when you only need API compatibility and you rely on your own sanitization everywhere, or when native enforcement covers your supported modern browsers and you only need a shim elsewhere.
2. full
- Includes api_only behavior plus attempts type enforcement in the DOM based on a CSP policy inferred from the document (implementation:
src/polyfill/full.js). - In HTML you can pass a policy via
data-cspon the script tag (see example below).
Note
Use full when you must approximate enforcement in environments without native TT. Behavior is not identical to native CSP—read upstream code and test your scenarios.
3. Tinyfill (minimal shim)
- Does not implement enforcement.
- Stubs
trustedTypes.createPolicyso it returns your rules object;createHTMLetc. return plain strings on non-supporting browsers, which legacy sinks accept.
Caution
tinyfill is only a compatibility shim: policies return strings, not real trusted-type objects, and there is no engine-level enforcement.
Documented in the upstream README (Tinyfill section):
if (typeof trustedTypes === "undefined") {
globalThis.trustedTypes = { createPolicy: (_name, rules) => rules };
}
Same idea, slightly more readable:
if (typeof trustedTypes === "undefined") {
globalThis.trustedTypes = {
createPolicy(_name, rules) {
return rules;
},
};
}
Tip
tinyfill allows one codebase to run in enforcing browsers (real TrustedHTML) and legacy browsers (string output), as long as your policy functions always sanitize.
Load in the browser (ES5 builds)
Note
Compiled files live in the upstream repo’s dist/ directory. The ES5 CDN script URLs below match the published webappsec-trusted-types ES5 builds. If a URL 404s, confirm paths in the upstream dist/ tree or the package README.
API only
<script src="https://w3c.github.io/webappsec-trusted-types/dist/es5/trustedtypes.api_only.build.js"></script>
<script>
const p = trustedTypes.createPolicy("foo", {
createHTML: (s) => /* sanitize */ s,
});
document.body.innerHTML = p.createHTML("<b>ok</b>");
document.body.innerHTML = "<b>still allowed without native enforcement</b>";
</script>
Warning
The second innerHTML assignment uses a raw string on purpose: with api_only, that line is not blocked—only native CSP enforcement does that.
Full (with inferred / inline CSP hint)
<script
src="https://w3c.github.io/webappsec-trusted-types/dist/es5/trustedtypes.build.js"
data-csp="trusted-types foo bar; require-trusted-types-for 'script'"
></script>
<script>
trustedTypes.createPolicy("foo", { createHTML: (s) => s });
// trustedTypes.createPolicy("unknown", { ... }); // throws if not in trusted-types list
// document.body.innerHTML = "foo"; // intended to throw under polyfill enforcement
</script>
Important
Load the polyfill before your application code that calls trustedTypes.createPolicy. Prefer first among your scripts if the rest of the bundle assigns to sinks at parse time.
Tip
Also send a real CSP header from your server for browsers that implement Trusted Types natively—the data-csp attribute is for what the full polyfill uses in non-native scenarios (see upstream README).
Node.js / bundlers (npm)
Note
In Node, there is usually no DOM—this path is for SSR tests, JSDOM-style setups, or shared isomorphic helpers. For real enforcement, test in a browser with CSP.
npm install trusted-types
CommonJS (shape per upstream README — Node.js):
const tt = require("trusted-types");
// or: import { trustedTypes } from "trusted-types"
tt.createPolicy("myPolicy", {
createHTML: (input) => sanitize(input),
});
ES modules — check your package version’s exports field; many setups re-export trustedTypes on globalThis after a side-effect import instead of a named export.
Pairing with application code
Tip
With api_only or tinyfill, createPolicy exists even when there is no native enforcement—your sanitizer is still the security boundary on legacy engines.
Feature test (same as without polyfill)
if (globalThis.trustedTypes?.createPolicy) {
const policy = trustedTypes.createPolicy("app", {
createHTML: (s) => DOMPurify.sanitize(s),
});
el.innerHTML = policy.createHTML(userHtml);
}
DOMPurify + Trusted Types
DOMPurify can return a trusted type for direct sink assignment (native + polyfill API where applicable):
import DOMPurify from "dompurify";
el.innerHTML = DOMPurify.sanitize(dirty, { RETURN_TRUSTED_TYPE: true });
Building from source (optional)
Note
Upstream clone and build steps: README — Building.
git clone https://github.com/w3c/trusted-types.git
cd trusted-types
npm install
npm run build
Demo and tests
- Demo: w3c.github.io/trusted-types/demo
- Platform tests: tests/platform-tests in the upstream repo
When you can skip the polyfill
Tip
Same idea as Which browsers need the polyfill?: if your supported browser matrix only includes engines with native Trusted Types and you always send require-trusted-types-for 'script', you may omit the polyfill and rely on the platform.
If you still support older Chromium / WebKit / Firefox without TT, keep api_only or tinyfill until those versions fall out of scope.
Related in this repo
README.md— full patterns and CSP walkthrough; tinyfill is covered in H. Tiny polyfill.playground/— DOM XSS vs Trusted Types /setHTML()demos (node playground/serve.mjs); seeplayground/README.md(not hosted on the static site).
Links & resources
| Topic | Link |
|---|---|
| MDN — Trusted Types API (browser compatibility) | developer.mozilla.org |
| Can I use — Trusted Types | caniuse.com |
| W3C Trusted Types (GitHub) | github.com/w3c/trusted-types |
npm — package trusted-types |
npmjs.com |
| web.dev — Trusted Types | web.dev |
Polyfill source — full enforcement (full.js) |
GitHub |
Polyfill dist/ tree (CDN / build output) |
GitHub |
| Upstream README — Tinyfill | GitHub |
| Upstream README — Node.js | GitHub |
| Upstream README — Building | GitHub |
| Demo (hosted) | w3c.github.io/trusted-types/demo |
Platform tests — tests/platform-tests |
GitHub |
ES5 CDN — api_only |
trustedtypes.api_only.build.js |
ES5 CDN — full |
trustedtypes.build.js |