React helps developers build user interfaces without manually updating the DOM for every change. However, some applications still need to render HTML received from a database, a content management system, or an external API. When that HTML is inserted into the page without proper sanitization, it can expose the application to cross-site scripting (XSS) attacks.
Trusted Types provide a browser-level way to control how potentially dangerous values reach sensitive DOM APIs. When combined with a suitable Content Security Policy (CSP), they help developers identify unsafe DOM updates and reduce the risk of DOM-based XSS.
React 19.3 also includes improved support for the browser Trusted Types API, making it easier to use trusted values with React's DOM rendering features.
This article explains how Trusted Types work, how to use them in a React application, and what to consider before enabling enforcement in production.
What Are Trusted Types?
Trusted Types is a browser security mechanism that helps control which values can reach DOM APIs that interpret strings as HTML or executable code.
Consider an application that displays a comment submitted by a user. A developer might use the following code to render the comment:
const comment = userComment;
document.getElementById("comment").innerHTML = comment;
The problem is that innerHTML interprets its input as HTML rather than displaying it as plain text. If an attacker submits malicious markup, the browser may interpret that markup in an unsafe way.
For example, an application that accepts arbitrary HTML without sanitization could allow an attacker to inject event handlers or other dangerous markup.
Trusted Types introduce objects such as TrustedHTML to help control these operations. Instead of allowing arbitrary strings to reach sensitive DOM APIs, developers can require values to pass through a defined policy first.
Trusted Types do not automatically determine whether content is safe. The application must still apply appropriate sanitization and validation.
Why React Applications Need Trusted Types
React escapes text values rendered through its normal JSX expressions. This makes ordinary text rendering safer than manually inserting HTML into the DOM.
For example:
function UserComment({ comment }) {
return <p>{comment}</p>;
}
If comment contains HTML-like text, React renders it as text instead of treating it as markup.
The situation changes when an application uses dangerouslySetInnerHTML:
function UserComment({ comment }) {
return (
<div
dangerouslySetInnerHTML={{
__html: comment
}}
/>
);
}
This feature tells React to insert HTML directly. If comment comes from an untrusted source, the application could become vulnerable to XSS.
Common situations where developers encounter this problem include:
- Rendering rich text from a content management system.
- Displaying HTML from user-generated comments or descriptions.
- Importing formatted content from external services.
- Integrating legacy JavaScript libraries that manipulate the DOM.
- Updating elements through
innerHTML, outerHTML, or similar APIs.
Trusted Types add another layer of protection around these sensitive operations. They are especially useful when a large application has many components, dependencies, and data sources.
How Trusted Types Work
Trusted Types use policies to control how strings become trusted values.
The main trusted value types are:
| Type |
Purpose |
Example use |
TrustedHTML |
Represents HTML processed by an approved policy |
Inserting sanitized markup |
TrustedScript |
Represents script content approved by a policy |
Working with APIs that accept script values |
TrustedScriptURL |
Represents an approved script URL |
Loading scripts through protected DOM APIs |
For most React applications that display rich HTML, TrustedHTML is the most relevant type.
A policy defines how an input should be transformed before it becomes a trusted value. For HTML, this normally means passing the input through a suitable HTML sanitizer.
The browser can then enforce the requirement that sensitive DOM APIs receive trusted values instead of arbitrary strings.
This creates a clearer security boundary: developers can focus their security reviews on the policies that create trusted values rather than relying entirely on developers to remember which DOM operations are dangerous.
Step 1: Install an HTML Sanitizer
Trusted Types do not replace HTML sanitization. A policy that simply accepts every input and labels it trusted does not provide meaningful protection.
For this example, use DOMPurify, a widely used HTML sanitization library.
Install it in your React project:
npm install dompurify
If the project uses TypeScript and your setup requires separate type declarations, check the package's current TypeScript support before adding another dependency.
The sanitizer removes or neutralizes HTML that falls outside its security rules while preserving supported markup.
For example, an application might want to allow paragraphs, headings, links, and lists while removing script-related markup and unsafe attributes.
The exact sanitization rules should reflect the application's requirements. Do not assume that every HTML element or attribute is appropriate for every application.
Step 2: Create a Trusted Types Policy
Create a small module responsible for sanitizing HTML and producing trusted values.
// trustedHtml.js
import DOMPurify from "dompurify";
const htmlPolicy =
typeof window !== "undefined" &&
window.trustedTypes
? window.trustedTypes.createPolicy("app-html", {
createHTML(input) {
return DOMPurify.sanitize(input);
}
})
: null;
export function createTrustedHTML(input) {
const sanitized = DOMPurify.sanitize(input);
if (htmlPolicy) {
return htmlPolicy.createHTML(sanitized);
}
return sanitized;
}
This module performs two important tasks.
First, DOMPurify sanitizes the input. The application should not rely on the policy name alone to establish safety.
Second, when the browser supports Trusted Types, the module creates a TrustedHTML value through the app-html policy.
The fallback returns sanitized text for environments without the Trusted Types API. This maintains the sanitization step, but it does not provide browser-level Trusted Types enforcement in unsupported environments.
The policy should be created once and reused rather than recreated whenever a component renders.
In a production application, keep policy creation in a dedicated security module. Also ensure that the policy name matches the name permitted by the application's Content Security Policy.
Step 3: Use the Trusted Value in a React Component
After creating the sanitization function, use it when rendering HTML.
import { useMemo } from "react";
import { createTrustedHTML } from "./trustedHtml";
function ArticleContent({ html }) {
const trustedHtml = useMemo(
() => createTrustedHTML(html ?? ""),
[html]
);
return (
<article
className="article-content"
dangerouslySetInnerHTML={{
__html: trustedHtml
}}
/>
);
}
export default ArticleContent;
The component processes the supplied HTML and passes the result to React.
Using useMemo avoids repeating the sanitization work when the html value has not changed. It is a performance optimization, not a security boundary. The sanitization function must remain safe even when called repeatedly.
This example assumes a React version and rendering setup that support passing Trusted Types values through to the DOM. React 19.3 includes improved Trusted Types support. Applications using earlier React versions should verify their exact behavior before enabling enforcement, because string coercion can interfere with trusted values.
If the application only needs to display plain text, prefer ordinary JSX rendering:
function ArticleExcerpt({ text }) {
return <p>{text}</p>;
}
This approach is simpler and avoids introducing an HTML injection path unnecessarily.
Step 4: Enable Trusted Types Through Content Security Policy
Creating a policy does not, by itself, force the entire application to use trusted values. To enforce that requirement, configure the Content Security Policy response header.
For example:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-html
These directives have separate responsibilities:
require-trusted-types-for 'script' enables Trusted Types enforcement for protected injection sinks.
trusted-types app-html limits which named policies application code may create.
The policy name in the header must match the name used in createPolicy().
This is only a Trusted Types example, not a complete production CSP. A real policy should also address script sources, styles, connections, images, and other resources according to the application's architecture.
Do not add a permissive wildcard policy merely to make the application work. Doing so can undermine the control over policy creation that Trusted Types are intended to provide.
Test Before Enforcing the Policy
Enabling enforcement immediately can break existing code, especially in applications with third-party libraries or legacy DOM manipulation.
A safer rollout begins with a report-only policy:
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; trusted-types app-html
Review the resulting violations and identify which code paths still pass raw strings to protected DOM APIs.
Once those paths have been reviewed and corrected, introduce enforcement through the normal deployment process. Monitor browser errors and application behavior after rollout.
Report-only mode helps reveal compatibility problems, but it does not block unsafe operations. It should be treated as a migration step rather than a permanent security control.
Trusted Types vs. Traditional HTML Sanitization
Trusted Types and sanitization solve related but different problems.
| Approach |
Main purpose |
Limitation |
| React's normal JSX rendering |
Escapes text values rendered as text |
Does not make arbitrary HTML safe when inserted through other APIs |
| HTML sanitization |
Removes or neutralizes unsafe markup |
Protection depends on consistently sanitizing data before insertion |
| Trusted Types |
Controls values passed to protected DOM injection sinks |
Requires suitable browser support and policy configuration |
| Content Security Policy |
Enforces browser security rules, including Trusted Types requirements |
An incomplete or overly permissive policy can leave gaps |
The strongest approach is to combine these controls according to the application's needs.
For example, use ordinary JSX for text, sanitize HTML when rich content is required, and enable Trusted Types enforcement to help prevent raw strings from reaching protected DOM APIs.
Best Practices for Production React Applications
1. Avoid HTML Injection When You Do Not Need It
Use JSX text rendering for names, comments, descriptions, and other plain-text values.
Reserve dangerouslySetInnerHTML for cases where the application genuinely needs to render HTML.
2. Keep Sanitization Rules Centralized
Use a shared module instead of allowing every component to define its own sanitization logic.
Centralization makes security reviews easier and reduces the risk of inconsistent rules across the application.
3. Do Not Create a No-Op Policy
This policy is unsafe:
const unsafePolicy = trustedTypes.createPolicy("app-html", {
createHTML(input) {
return input;
}
});
It converts arbitrary input into a trusted value without sanitizing it. Under enforcement, this defeats an important part of the protection.
A trusted value should represent a deliberate security decision, not merely a string that has passed through a function.
4. Review Third-Party Dependencies
Libraries may manipulate the DOM internally. Before enabling enforcement, test important user flows and investigate violations caused by dependencies.
Upgrade or replace incompatible packages where necessary. Avoid creating a broad default policy that silently accepts every string simply to suppress errors.
5. Validate Content at the Appropriate Boundary
HTML sanitization does not replace server-side validation, authorization, or output encoding.
For example, a sanitized comment may still contain content that is inappropriate for a particular business rule. Similarly, a trusted HTML value does not establish that a user is authorized to publish that content.
Keep these security responsibilities separate.
6. Include Trusted Types in Automated Testing
Test components with normal input, empty content, unexpected markup, and malicious-looking strings.
Also test the application with enforcement enabled in a compatible browser. A unit test that only checks the sanitizer's output cannot confirm that the full application works under the deployed CSP.
Common Problems and Troubleshooting
| Problem |
Likely cause |
What to check |
A TypeError occurs during a DOM update |
A raw string reached an enforced injection sink |
Find the failing DOM API and route its input through an approved policy |
createPolicy() fails |
The policy name is not permitted or has already been created |
Check the CSP allowlist and centralize policy creation |
| Rich HTML is missing after sanitization |
The sanitizer removed unsupported markup or attributes |
Review the allowed HTML rules without permitting unsafe content |
| A third-party library stops working |
The library passes strings to protected DOM APIs |
Update the dependency or use a supported integration |
| The application works locally but fails in production |
The production CSP differs from the development configuration |
Inspect response headers and browser console violations |
| Trusted Types enforcement does not appear to work |
The required CSP directive is missing or not applied |
Verify the actual response headers and test in a compatible browser |
When troubleshooting, identify the exact injection sink and the value reaching it. Avoid fixing every error with a default policy that accepts arbitrary strings. That can hide the underlying issue instead of resolving it.
Advantages and Disadvantages of Trusted Types
Advantages
- Helps prevent raw strings from reaching protected DOM injection sinks.
- Makes security-sensitive HTML creation easier to identify and review.
- Works alongside HTML sanitizers and Content Security Policy.
- Supports gradual migration through report-only monitoring.
- Can improve security consistency across larger React applications.
Disadvantages
- Requires careful policy design and CSP configuration.
- Can expose compatibility problems in existing code and third-party dependencies.
- Does not automatically sanitize content or guarantee that a policy is safe.
- Requires browser compatibility testing.
- Adds migration and testing work to applications with many DOM injection paths.
Trusted Types are most useful when an application needs a systematic way to control DOM injection rather than relying only on individual developers to avoid unsafe operations.
Summary
Trusted Types help React developers reduce the risk of DOM-based XSS by controlling how values reach sensitive browser APIs. They are especially valuable when applications render rich HTML from external services, user-generated content, or content management systems.
For a practical implementation, start by avoiding unnecessary HTML injection, use an HTML sanitizer for content that must be rendered as markup, and create a narrowly defined Trusted Types policy. Then introduce Content Security Policy enforcement gradually, beginning with report-only monitoring and resolving compatibility issues before switching to enforcement.
The important point is that Trusted Types complement React's existing protections; they do not replace safe rendering, HTML sanitization, or a carefully designed security policy.
Join the conversation! Your thoughts help the community grow.