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 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)

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

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)

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


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


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