loading
Note:
How enforcement is turned on Nothing here enforces anything on its own. Enforcement comes from a response header - Content-Security-Policy: require-trusted-types-for 'script'; trusted-types my-policy - at which point assigning a plain string to innerHTML, script.src or eval throws, and only a value produced by a listed policy goes through. Start with the -Report-Only header and OnViolation; every violation is a call site to fix before you switch it on.
Warning:
Rules are declared, not written as callbacks A policy's transform has to run synchronously, and every call back into .NET is asynchronous - so a C# callback cannot be a policy. TrustedTypePolicyOptions declares what a hand-written policy usually does instead: sanitize the HTML (through the browser's own Sanitizer, optionally one you configured), and allow script URLs only from prefixes you named.

Is it there, and is it on?

IsSupported / IsEnforced

Two different questions. IsSupported says window.trustedTypes exists; IsEnforced says a CSP is actually making plain strings fail. Nothing in the platform reports the second, so it is asked by assigning a plain string to a sink on a detached element and seeing whether it throws - the element is never in the document, so the probe has no effect. It comes back null rather than false when a 'default' policy is registered, because that policy would rescue the probe under enforcement too.

C#
@inject Bit.Butil.TrustedTypes trustedTypes

var available = await trustedTypes.IsSupported();
bool? enforced = await trustedTypes.IsEnforced();   // null = a 'default' policy hides the answer
Live sample
support check output
Results will appear here when you interact with the samples.

Create a policy

CreatePolicy / HasPolicy / GetPolicyNames

A policy name has to be listed in the CSP's trusted-types directive, and can only be created once per document - so a false here is a configuration fact rather than an error. The name 'default' is special: the browser falls back to it for any string assigned to a sink without a policy, which is how existing code keeps working under enforcement.

@inject Bit.Butil.TrustedTypes trustedTypes

@code {
    // False when the name is not listed in the CSP's trusted-types directive, or when this document
    // has already created it - a policy can only be created once. Both are configuration facts
    // rather than errors, so this is worth doing once and remembering.
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender is false) return;

        var created = await trustedTypes.CreatePolicy("app-html", new TrustedTypePolicyOptions
        {
            SanitizeHtml = true,
            AllowedScriptUrlPrefixes = ["https://cdn.example.com/", "/_content/"],
        });
    }
}
Live sample
policy output
Results will appear here when you interact with the samples.

Produce a value

CreateHtml / CreateScriptUrl

The trusted value itself never crosses to .NET - it would arrive as its string and lose exactly the type that makes it trusted - so what comes back is only the resulting text, for display or comparison. Note the script URL: one that matches an allowed prefix comes back, anything else is refused.

C#
var html = await trustedTypes.CreateHtml("app-html", userMarkup);
var src = await trustedTypes.CreateScriptUrl("app-html", "https://cdn.example.com/lib.js");
var refused = await trustedTypes.CreateScriptUrl("app-html", "https://evil.example/x.js");  // null
Live sample
create output
Results will appear here when you interact with the samples.

Write through the policy

SetHtml / SetScriptSrc

The point of having a policy: under enforcement this succeeds where assigning a string to innerHTML throws. The markup is created and assigned in one call, because the trusted value cannot be handed back to C# in between.

C#
await trustedTypes.SetHtml(preview, "app-html", userMarkup);
await trustedTypes.SetScriptSrc(scriptElement, "app-html", "https://cdn.example.com/lib.js");
Live sample
The markup written through the policy will appear here.
write output
Results will appear here when you interact with the samples.

Find what still writes strings

OnViolation

Where a rollout starts. Serve the report-only header, subscribe here, and every violation names a sink still being written to as a plain string, with the file and line that did it. Nothing will be reported on this page unless you are serving that header.

@implements IAsyncDisposable
@inject Bit.Butil.TrustedTypes trustedTypes
@inject ILogger<TrustedTypes> logger

@code {
    private ButilSubscription? _subscription;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender is false) return;

        _subscription = await trustedTypes.OnViolation(violation =>
        {
            logger.LogWarning("{Directive} at {File}:{Line} - {Sample}",
                violation.Directive, violation.SourceFile, violation.LineNumber, violation.Sample);
        });
    }

    public async ValueTask DisposeAsync()
    {
        if (_subscription is not null) await _subscription.DisposeAsync();
    }
}
Live sample
violation output
Results will appear here when you interact with the samples.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes window.trustedTypes. Returns default (false) during prerender/SSR instead of throwing.
IsEnforced
ValueTask<bool?> IsEnforced()
Whether a CSP is actually enforcing Trusted Types on this document. Null when a 'default' policy makes the probe unable to tell, and during prerender/SSR.
CreatePolicy
ValueTask<bool> CreatePolicy(string name, TrustedTypePolicyOptions? options = null, SanitizerHandle? sanitizer = null)
Creates a policy. False when the CSP doesn't list the name, the name is taken, or the runtime has no Trusted Types.
HasPolicy
ValueTask<bool> HasPolicy(string name)
True when a policy of this name was created through this API.
GetPolicyNames
ValueTask<string[]> GetPolicyNames()
The names of the policies created through this API.
CreateHtml
ValueTask<string?> CreateHtml(string policyName, string html)
Runs markup through a policy and returns the resulting text. Null when there is no such policy or it refused.
CreateScriptUrl
ValueTask<string?> CreateScriptUrl(string policyName, string url)
Runs a script URL through a policy. Null when the URL isn't one the policy allows.
SetHtml
ValueTask<bool> SetHtml(ElementReference element, string policyName, string html)
Writes markup into an element through a policy - the call that works under enforcement.
SetScriptSrc
ValueTask<bool> SetScriptSrc(ElementReference scriptElement, string policyName, string url)
Sets a script element's src through a policy.
OnViolation
ValueTask<ButilSubscription> OnViolation(Action<TrustedTypeViolation> handler)
Reports every Trusted Types violation on the document. Dispose the subscription to stop.
DisposeAsync
ValueTask DisposeAsync()
On scope/circuit teardown, detaches any violation listener whose subscription was never disposed. The policies themselves are kept: a name cannot be created twice, so dropping them would leave a re-created scope unable to get them back.
TrustedTypePolicyOptions
SanitizeHtml | AllowedScriptUrlPrefixes | AllowScript
What a policy allows. Sanitizing is on and script URLs are refused by default.
An unhandled error has occurred. Reload 🗙