All posts

ReportingObserver, catch deprecations and CSP violations in JavaScript

CentralCSP Team ·

Last update:

Most of the Reporting API ships browser reports off to a server you control. The ReportingObserver API does the opposite: it hands those same reports to your own JavaScript, inside the page, the moment they fire. No response header, no endpoint, no backend. You construct an observer, give it a callback, and the browser starts calling you with deprecation warnings, browser interventions, and (where supported) CSP violations as they happen. It is the in-page counterpart to endpoint reporting, and it slots straight into a front-end error pipeline you already run.

What it observes

ReportingObserver is a constructor available in the page's own JavaScript. You point it at the report types you care about and it delivers matching reports to your callback. It covers:

  • deprecation, an API your page uses is slated for removal.
  • intervention, the browser overrode something your code asked for.
  • CSP violations, where the browser surfaces them through this interface.

These are the same events the browser would otherwise batch and POST to a server. The difference is where you read them: in the page, in real time, with the full report object in hand. The observer surfaces deprecation, intervention, and CSP violation reports; crash reports never reach it and go only to a server endpoint. Of the three it does surface, CSP violation delivery is the least consistent across browsers, so confirm it in your target browsers before depending on it.

Wire one up

You create the observer with a callback and an options object. The callback receives a list of reports and the observer itself. The two options that matter are types, the report types to watch, and buffered, which replays reports that fired before your observer existed so you do not miss early ones during page load.

reporting-observer.ts
const  = new (
  (, ) => {
    for (const  of ) {
      // report.type is "deprecation", "intervention", or "csp-violation"
      // report.body holds the type-specific fields
      ({
        : .,
        : .,
        : .,
      });
    }
  },
  { : ["deprecation", "intervention"], : true }
);

.();

Call observe() to start. The buffered: true flag is what makes this reliable: a deprecation can fire while your bundle is still parsing, and without buffering you would never see it. With it, those early reports are replayed into your first callback.

Fit it into a front-end error pipeline

If you already capture JavaScript errors and unhandled rejections in the browser and ship them to a logging service, ReportingObserver is one more source for that same pipeline. Treat each report like an error event: tag it with the type, attach the source location from report.body, and send it through the channel you already have.

error-pipeline.js
function sendToErrorPipeline(entry) {
  // reuse your existing client logger / beacon
  navigator.sendBeacon("/client-logs", JSON.stringify(entry));
}

window.addEventListener("error", (e) =>
  sendToErrorPipeline({ type: "js-error", message: e.message })
);

new ReportingObserver(
  (reports) => reports.forEach((r) =>
    sendToErrorPipeline({ type: r.type, body: r.body })
  ),
  { types: ["deprecation", "intervention"], buffered: true }
).observe();

Using sendBeacon keeps the report from being dropped when the user navigates away, the same reason the Reporting API delivers out of band. Now a deprecation shows up in the same dashboard as a thrown exception, with the source file and line that triggered it.

It complements endpoint reporting, it does not replace it

ReportingObserver and endpoint-based reporting answer different questions, so most teams want both.

  • The observer runs only while your page is alive and only sees what this session produces. It is great for real-time front-end telemetry and for routing reports into tooling you already own.
  • Endpoint reporting, declared with the Reporting-Endpoints header, keeps working when the page is gone, aggregates across all your traffic, and catches report types the observer does not, like crash reports. That is the authoritative, server-side view.

Use the observer to surface issues fast in your own front-end stack, and use endpoint reporting as the durable record. Set up the endpoint side in how to set up the Reporting API, and read the full catalog of report types on the reports reference. The mechanics of the observer itself are on the ReportingObserver concept page.

Send both views to one place

The observer gives you reports in the browser; the endpoint gives you reports from real traffic. CentralCSP ingests every browser report type on the endpoint side, so your ReportingObserver and your Reporting-Endpoints configuration can feed the same reporting view, with deprecations, interventions, and CSP violations grouped and trended instead of scattered across a console and a log file. You get the real-time front-end signal and the durable, cross-traffic record without building two pipelines.

The report explorer with the type selector open over the table

Next steps

Collect every browser report in one place.

Sources