PHP JavaScript errors

JavaScript error tracking captures client-side errors and reports them through your own application, so nothing in the browser talks to Ranetrace directly. It is off by default (javascript_errors.enabled, environment variable RANETRACE_JAVASCRIPT_ERRORS_ENABLED).

Unlike the Laravel package, this SDK registers no routes. There are three things to do: turn the feature on, mount the relay somewhere, and emit the snippet.

Using Laravel? See JavaScript Errors instead, where the route and the Blade directive are provided for you.

1. Turn it on

RANETRACE_ENABLED=true
RANETRACE_JAVASCRIPT_ERRORS_ENABLED=true

JavaScript error tracking runs where your users are, so enable it in production, not just locally.

2. Mount the relay

The relay is the endpoint the browser posts to. You choose its URL; the SDK only handles the request once it arrives. In a plain PHP front controller that is a branch near the top:

<?php

require __DIR__.'/../vendor/autoload.php';

use Ranetrace\Php\Ranetrace;

$ranetrace = Ranetrace::init(['key' => getenv('RANETRACE_KEY') ?: '']);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/ranetrace/js-errors') {
    $ranetrace->relay()->handle();

    exit;
}

// ... the rest of your routing

handle() is the adapter for hosts without a request abstraction. It reads the JSON body from php://input and the request context from $_SERVER, sets the status code and a JSON content type (skipping the headers if your application already sent some), and echoes the response body. It never throws.

If your application has its own request and response objects, use handleRequest() instead and build the response yourself:

$response = $ranetrace->relay()->handleRequest($serverArray, $decodedJsonBody);

// $response->status is the HTTP status code
// $response->body is the response body as an array
// $response->toJson() is that body, encoded

To group a visitor's errors within one visit, put your own per-visit id in the server array under RANETRACE_SESSION_ID. It is hashed before it is stored, never kept raw:

$_SERVER['RANETRACE_SESSION_ID'] = session_id();

You must rate limit this endpoint

The relay is a public, unauthenticated POST endpoint, and this SDK does not rate limit it. The Laravel package could put throttle:60,1 in front of its route; a framework-agnostic package has no shared counter store and will not invent one.

Put a limit in front of the path you mounted, in your framework, your web server or your CDN. Without one, a single browser tab in a crash loop can fill your buffer. Sixty requests per minute per client is the limit the Laravel package uses and a sensible starting point. The javascript_errors.throttle config value exists for parity with that package and is not applied here.

The same-origin check

Because there is no session and no CSRF token, the relay verifies the request's origin instead. When the request carries an Origin header (or, failing that, a Referer), its host must either match the request's own Host, or appear in your allowlist. A request with neither header is allowed, because those are same-origin navigations and server-to-server calls, and rejecting them would drop legitimate reports.

That covers the normal case, where your pages and your relay are on the same host, with no configuration. If your frontend is served from another origin, list it:

RANETRACE_JAVASCRIPT_ERRORS_ALLOWED_ORIGINS=https://app.example.com,https://checkout.example.com

Entries may be a full origin (https://app.example.com) or a bare authority (app.example.com:8443). Comparison is case-insensitive and ignores a scheme's default port. A rejected request gets a 403 with Origin not allowed.

3. Emit the snippet

Render the capture script before </body>, telling it the URL you mounted the relay on:

<?= $ranetrace->javascriptSnippet(['endpoint' => '/ranetrace/js-errors']) ?>

The endpoint option is required: the SDK cannot know where you mounted the relay, so leaving it out throws an InvalidArgumentException rather than shipping a script that silently posts nowhere.

When the feature is disabled, the call returns an empty string, so it is safe to leave in your layout before you turn it on.

If your pages use a Content Security Policy with nonces, pass yours and it is rendered on the <script> tag:

<?= $ranetrace->javascriptSnippet(['endpoint' => '/ranetrace/js-errors', 'nonce' => $cspNonce]) ?>

What the script captures

  • window errors, caught in the capture phase.
  • Unhandled promise rejections.
  • Console errors, only when capture_console_errors is on (default off).

It also collects breadcrumbs leading up to the error:

Category Recorded
navigation Page loaded
user Clicks (tag, id, class, first 50 characters of text), form submissions (action, method)
http XHR completed or failed, fetch completed or failed

The last max_breadcrumbs breadcrumbs are kept (default 20), because the ones nearest the error are the ones worth having.

Two helpers are exposed for reporting by hand:

try {
    riskyOperation();
} catch (error) {
    window.Ranetrace.captureError(error, { payment_amount: amount });
}

window.Ranetrace.addBreadcrumb('user', 'Clicked retry', { attempt: 2 });

Sampling, ignored errors and deduplication

Sampling. javascript_errors.sample_rate defaults to 1.0, meaning every error is reported. Lower it on a high-traffic site:

RANETRACE_JAVASCRIPT_ERRORS_SAMPLE_RATE=0.25

The rate is applied twice, in the browser and again in the relay. The second check matters: a script cached in someone's browser cannot outvote a rate you lowered this morning.

Ignored errors. Browser noise that is never worth an error record is filtered by default, in the script and again in the relay. Matching is a case-insensitive substring test on the message. The default list:

  • ResizeObserver loop limit exceeded and ResizeObserver loop completed with undelivered notifications
  • Script error. and Script error (cross-origin, no useful detail)
  • Failed to fetch, NetworkError when attempting to fetch resource, Network request failed, Load failed
  • Loading chunk, ChunkLoadError
  • cancelled, canceled, The operation was aborted, AbortError
  • Illegal invocation

Replacing the list replaces it completely, so include the defaults you still want:

$ranetrace = Ranetrace::init([
    'key' => getenv('RANETRACE_KEY') ?: '',
    'javascript_errors' => [
        'enabled' => true,
        'ignored_errors' => ['Script error.', 'ResizeObserver loop limit exceeded', 'Sentry'],
    ],
]);

Deduplication. The script keys errors by message|filename|line|column and skips repeats within a rolling 50-entry cache, which lasts for one page load.

What the relay does with a report

A browser can claim anything, so four fields are set by the server and never read from the payload: the user agent, the environment name, the user id (from your user_resolver) and the hashed session id. The stack trace and the page URL are secret-scrubbed, the breadcrumb list is rebuilt to exactly the four fields it is allowed to have, and oversized context or breadcrumb data is replaced with a short marker rather than trimmed mid-structure.

The responses your endpoint returns:

Status When
200 The report was accepted, ignored by pattern, or sampled out. The body says which.
403 Ranetrace or JavaScript error tracking is disabled, or the origin was not allowed.
422 The posted body failed validation. The body lists the fields.
500 Something went wrong while processing. Nothing is thrown at your application.

Configuration

Environment variable Config key Default What it controls
RANETRACE_JAVASCRIPT_ERRORS_ENABLED javascript_errors.enabled false Turns the feature on. While off, the snippet renders nothing and the relay returns 403.
RANETRACE_JAVASCRIPT_ERRORS_SAMPLE_RATE javascript_errors.sample_rate 1.0 Fraction of errors reported.
RANETRACE_JAVASCRIPT_ERRORS_CAPTURE_CONSOLE_ERRORS javascript_errors.capture_console_errors false Whether console.error calls become error reports.
RANETRACE_JAVASCRIPT_ERRORS_MAX_BREADCRUMBS javascript_errors.max_breadcrumbs 20 How many breadcrumbs are kept with an error.
RANETRACE_JAVASCRIPT_ERRORS_ALLOWED_ORIGINS javascript_errors.allowed_origins [] Extra origins allowed to post to the relay.
RANETRACE_JAVASCRIPT_ERRORS_TIMEOUT javascript_errors.timeout 10 Seconds to wait for the API when a batch is sent.
(config only) javascript_errors.ignored_errors 15 patterns Messages never reported.

Captured errors reach the API on the next flush. See PHP installation.