Collect CSP violations with the report-to directive, which names an endpoint declared in a Reporting-Endpoints header. The older report-uri directive is deprecated, but MDN says you can send both until report-to is widely supported. Browsers that understand report-to ignore report-uri.
The two mechanisms
report-uri | report-to | |
|---|---|---|
| Status | Deprecated | Current |
| Value | A URL | An endpoint name |
| Where the URL lives | In the policy | In the Reporting-Endpoints header |
MDN states that report-to is intended to replace report-uri, and that in browsers supporting report-to, the report-uri directive is ignored.
Setting it up
The mapping between an endpoint name and its URL is defined in a separate header. MDN notes that non-secure endpoints are ignored, so the URL must be HTTPS.
Reporting-Endpoints: csp-endpoint="https://reports.example.com/csp"
Content-Security-Policy: default-src 'self'; report-to csp-endpoint
For wider support during the transition, MDN shows both directives together:
Content-Security-Policy: default-src 'self'; report-uri https://reports.example.com/csp; report-to csp-endpoint
What a report looks like
With report-to, reports are JSON sent by POST with a Content-Type of application/reports+json. Each carries a type of csp-violation and a body with fields including the blocked URL, the effective directive, the document URL, the source file, line and column, the disposition (enforce or report) and the original policy. The sample field holds a short excerpt of the offending code when available.
Treat these as untrusted input. They arrive from browsers anyone can drive, so a public endpoint can receive junk. Validate size and shape, rate limit, and never render the fields as HTML in a dashboard.
Handling the volume
Reports can arrive in large numbers on a busy site. Decide up front what you will keep. A simple approach is to store only the effective directive, the blocked URL host, the document path and a count per day, and to sample the rest. Group by blocked host first: a new host that appears suddenly is more interesting than a million repeats of an old one.
Browser extensions are the usual noise. Reports whose source file is not on your domain and whose blocked URL is a scheme such as an extension URL can be filtered out of alerts, though it is worth keeping a count in case the pattern changes.
A rollout that avoids noise
- Start with
Content-Security-Policy-Report-Only. MDN says both a report-only and an enforcing policy can be present, and each is honoured. Violations from the report-only one are sent but not blocked. - Group reports by effective directive and blocked URL. Expect a long tail from browser extensions, which is normal.
- Fix real first-party issues first: inline handlers, forgotten third-party tags.
- Promote the policy to enforcing. Keep a report-only copy of the next, tighter version running beside it.
- Alert on new blocked hosts, not on raw volume.
Report-only policies cannot be delivered in a meta element, and the report-to directive is not supported there either, so reporting needs real response headers. Static sites on hosts that do not allow custom headers lose this option.
For a longer walk through enforcement, see CSP report-only rollout steps.
Quick check
Can I use only report-uri today?
It is deprecated, so plan to move. Sending both is the transition path MDN describes.
Why do I get reports for scripts I never added?
Often browser extensions or injected ads. Filter by source file and blocked URL before assuming a compromise.
Safeguard's policies and gates can block risky dependencies at pull request time, which reduces how many unexpected scripts reach the browser in the first place. Try a free scan at gold.safeguard.sh/scan.
Sources
- Content-Security-Policy report-to directive, MDN
- Content-Security-Policy report-uri directive, MDN
- Reporting-Endpoints header, MDN
- Content Security Policy guide, MDN
Last reviewed: 19 October 2026.