Trusted Types turns DOM injection sinks into a compile time decision, and the polyfill is optional
A browser API to prevent DOM-Based Cross Site Scripting in modern web applications.
At a glance
- What is it?
- The Trusted Types specification and polyfill from the W3C web application security group make DOM based cross site scripting a type error rather than a runtime surprise. Native support exists in Chromium based browsers from version 83, and the polyfill adds a tinyfill so the same code can run in browsers that enforce nothing.
- Who is it for?
- Trusted Types fits a web team that controls the sinks where untrusted strings reach innerHTML, script URLs and event handlers, and that is willing to route every such value through a named policy. It does not fit a site that keeps injecting user input into the DOM from third party code, since a policy cannot be created for a name another script already owns.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A spec draft and a polyfill in one repository
The repository hosts two things: the Trusted Types specification draft and the polyfill code. The project description calls it a browser API to prevent DOM-Based Cross Site Scripting in modern web applications, and the specification is published at the w3c.github.io domain with the dist/spec path.
For a web developer arriving for the first time, the project points to other resources rather than asking them to read the spec. There is an introduction for web developers on web.dev with an API description and examples, an explainer document in this repository that answers what problem the API is solving, and the specification draft itself for the formal description. Browser support is documented by a caniuse link, and the note there is specific: the API is available natively in browsers based on Chromium version 83 and up.
That support boundary is the single most important fact for an adopter. Trusted Types is a Chromium feature with a polyfill for everyone else, and the polyfill's role is different in the two situations. In a browser that enforces, the polyfill fills in a missing implementation. In a browser that does not, the polyfill can be reduced to almost nothing and the application still works.
The language is JavaScript, the default branch is main, and the licence field shows a custom licence that GitHub cannot classify, with the terms in the LICENSE file. The package manifest records the licence as W3C-20150513, which is the W3C document licence, and lists the contributors as Krzysztof Kotowicz and Sebastian Lekies with Google Inc. as the author.
api_only defines the API, full adds enforcement based on the CSP header
The polyfill comes in two browser variants, and the difference is the enforcement mechanism.
The api_only variant is the light one. It defines the API, so you can create policies and types, but it does not enforce anything. The full version also enables the type enforcement in the DOM, based on the CSP policy it infers from the current document. That is the key architectural detail: the polyfill reads the Content Security Policy already on the page and derives its enforcement behaviour from it, rather than requiring a separate configuration.
The api_only usage looks like this:
<!-- API only -->
<script src="https://w3c.github.io/trusted-types/dist/es5/trustedtypes.api_only.build.js"></script>
<script>
const p = trustedTypes.createPolicy('foo', ...)
document.body.innerHTML = p.createHTML('foo'); // works
document.body.innerHTML = 'foo'; // but this one works too (no enforcement).
</script>The full variant is loaded with a data-csp attribute that carries the policy the polyfill should infer, and the behaviour changes visibly:
<!-- Full -->
<script src="https://w3c.github.io/trusted-types/dist/es5/trustedtypes.build.js" data-csp="trusted-types foo bar; require-trusted-types-for 'script'"></script>
<script>
trustedTypes.createPolicy('foo', ...);
trustedTypes.createPolicy('unknown', ...); // throws
document.body.innerHTML = 'foo'; // throws
</script>Two errors appear in the enforcing version. Creating a policy for a name that is not in the allowed list throws, and assigning a plain string to innerHTML throws. The allowed names in the data-csp attribute are foo and bar, and the require-trusted-types-for clause names the script sink.
The mechanism is worth naming plainly, because it is the security argument in one line. A DOM injection sink such as innerHTML normally accepts any string, so passing user input to it is a runtime decision nobody notices. With enforcement on, that sink accepts only a value produced by a policy, and a policy is created in one place with an auditable rule about what it will and will not allow. The dangerous assignment moves from runtime to source code review.
The tinyfill is one line and buys you cross browser compatibility
The most interesting thing in the repository is a snippet that is not the polyfill.
Due to the way the API is designed, it is possible to polyfill the most important API surface, the trustedTypes.createPolicy function, with the following snippet:
if(typeof trustedTypes == 'undefined')trustedTypes={createPolicy:(n, rules) => rules};That is the whole tinyfill. It does not enable the enforcement, but it allows the creation of policies that return string values instead of Trusted Types in non-supporting browsers. Since the injection sinks in those browsers accept strings, the values will be accepted unless the policy throws an error.
The consequence is stated in the project text and it is the reason this matters. This tinyfill code allows most applications to work in both Trusted-Type-enforcing and a legacy environment.
So the shape of an application becomes: write policies once, call them everywhere you would have concatenated a string, and let the environment decide whether the sink accepts the result. In Chromium with enforcement, a plain string assignment fails loudly. In Firefox or Safari, the same assignment succeeds because those sinks still take strings. One codebase, two behaviours, and the enforcing behaviour is where you want it.
The catch is equally plain. A policy that throws an error still throws in a non-supporting browser, so the rules inside your policies become your cross browser behaviour. If a policy validates input and rejects it, that validation applies everywhere. If it does not validate and simply passes the string through, then in a non-supporting browser you have written the same unsafe code with extra indirection. The tinyfill removes the compatibility problem, not the security work.
Using it in Node, and the difference between the two npm entry points
The polyfill is published as an npm package called trusted-types, and it supports both CommonJS and ES Modules:
$ npm install trusted-typesThe usage is short:
const tt = require('trusted-types'); // or import { trustedTypes } from 'trusted-types'
tt.createPolicy(...);The package manifest explains the two module targets. The main field points at build/cjs/trustedtypes.module.js and the module field at build/esm/trustedtypes.module.js, so a bundler that understands the module field gets the ES module build and Node's require gets the CommonJS one. The demo directory in the repository includes a nodejs-domino folder, which suggests the polyfill is exercised against a DOM implementation in Node rather than only in a browser, which is how you test a sink enforcement behaviour without a real page.
The build produces four artefacts plus a size report. The build script runs the CommonJS and ESM rollup builds in parallel with the es5 and es6 distribution builds, then runs the size report sequentially. The rollup steps take src/polyfill/module.js as input, the es5 and es6 bundles come from gulp, and the size report is also a gulp task. The repository commits the dist directory, which is why the browser script tags in the project text point at w3c.github.io paths rather than at a CDN that has to build first.
The specification itself is built by a different tool. The spec script runs bikeshed against spec/index.bs to produce build/index.html, and there is a watch variant for that. So the repository builds two different artefacts from two different toolchains, one JavaScript and one HTML, and the browser-facing part is the polyfill while the normative part is the spec.
Building and testing the polyfill yourself
To build the polyfill yourself, Java is required:
$ git clone https://github.com/w3c/webappsec-trusted-types/
$ cd trusted-types
$ npm install
$ npm run buildThe Java requirement comes from the Closure Compiler in the dev dependencies, which is a Java tool. That is the one non-JavaScript dependency in an otherwise standard Node toolchain, and it is what the project means by Java required.
Testing runs through karma:
$ npm testThe test script is karma start --single-run, and the manifest has a localonly variant that restricts the browsers to ChromeHeadless and FirefoxHeadlessDev, plus separate scripts for a headless Chrome run and a Firefox run. The lint scripts cover src/ and tests/ with eslint and the google config, and the fix script applies the changes automatically.
There is also a way to run the polyfill against the web platform test suite, the shared conformance suite for browser APIs, and the project notes that this requires small patches to the suite, with the runner script at tests/platform-tests/platform-tests-runner.sh. The mention of patches is a fair signal about the state of the ecosystem: a browser API in the process of being standardised does not yet sit neatly inside the shared conformance harness.
Cross-browser testing is provided by BrowserStack, and the project page carries a BrowserStack badge for it. A demo page is published as well, at the w3c.github.io trusted-types demo path, for seeing the polyfill in action without setting anything up.
Where Trusted Types does not help, and what the alternatives do differently
Two boundaries are worth being explicit about.
The first is scope. Trusted Types protects the DOM injection sinks, the places where a string becomes markup, a script URL, or an event handler. It does not protect your server, your framework templates, or your data layer. A SQL injection in your backend is unaffected, and so is a server side template injection. If your XSS problem comes from a third party script writing into the DOM on your behalf, a policy you created will not cover that script's assignments unless that script also cooperates with your policies.
The second is the cost. Every place that currently assigns a string to innerHTML has to change, because after enforcement it throws. That is a migration across the codebase, and the project text gives you no tooling for it beyond the API and the tinyfill. The migration cost is proportional to how much string concatenation your front end does, and for a codebase built on a framework that sanitises for you, the work may be small or may be large depending on whether the framework writes through the protected sinks.
The alternative approaches each cover a different slice. Output encoding at the point of insertion protects the sink but relies on getting the escaping right at every call site, and it does nothing for values that arrive already encoded. A sanitiser library parses the untrusted value and produces a safe one, which is what most modern frameworks do, and it is more forgiving because it degrades to pass-through behaviour. A Content Security Policy without Trusted Types blocks inline script and restricts origins, which stops the classic script injection case but does not stop markup injection into innerHTML, since that is not script execution.
Trusted Types is different in kind from all three. It does not sanitise, it does not escape, and it does not rely on a header to block execution. It changes the type of the value, so the unsafe value cannot be passed to the sink at all, and the browser, not your review process, is the thing enforcing it. The cost of that is that it is the strictest of the options and the most invasive to adopt.
Editorial conclusion
Trusted Types fits a web team that controls the sinks where untrusted strings reach innerHTML, script URLs and event handlers, and that is willing to route every such value through a named policy. It does not fit a site that keeps injecting user input into the DOM from third party code, since a policy cannot be created for a name another script already owns. Before turning on enforcement, read the tinyfill approach in the project text, because that one line of code is what lets the same application run in a browser with no enforcement at all.
Frequently asked questions
What is the Trusted Types polyfill and where does it come from?
The repository hosts the Trusted Types specification draft and the polyfill code from the W3C, aimed at preventing DOM-Based Cross Site Scripting in modern web applications. The API is available natively in browsers based on Chromium version 83 and up.
What is the difference between the api_only and full polyfill builds?
The api_only variant defines the API so you can create policies and types, without enforcing anything. The full version also enables type enforcement in the DOM, based on the CSP policy it infers from the current document.
How do I use Trusted Types in Node?
Install the npm package with npm install trusted-types, then require it in CommonJS or import { trustedTypes } in an ES module. The package supports both module systems.
What is the tinyfill in the Trusted Types repository?
It is a one line snippet that polyfills only trustedTypes.createPolicy, so policies return plain strings in browsers with no enforcement. It does not enable enforcement, but it lets most applications run in both an enforcing and a legacy environment.
How do I build and test the Trusted Types polyfill?
Clone the repository, run npm install and npm run build, which requires Java because of the Closure Compiler. Tests run with npm test, which starts karma in single run mode, and the polyfill can also be run against the web platform test suite with small patches to that suite.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/w3c-trusted-types)Was this useful?