Error Tracking
StorePilot captures JavaScript and PHP errors from your WordPress site and centralises them in the Platform dashboard.
How Errors Are Captured
JavaScript Errors
The plugin injects a tracking script into every page. The script listens for:
window.onerror— uncaught exceptionswindow.onunhandledrejection— unhandled Promise rejections
When an error fires, the script sends a payload to the Platform (via the WordPress proxy) containing:
{
"message": "Cannot read property 'x' of undefined",
"source": "https://mysite.com/wp-content/themes/my-theme/js/app.js",
"lineno": 42,
"colno": 18,
"stack": "TypeError: Cannot read ...\n at ...",
"context": {
"url": "https://mysite.com/shop/",
"userAgent": "Mozilla/5.0 ...",
"referrer": ""
}
}
Failed Asset Loads
A picture, stylesheet or script that does not load is not a fault in a script — nothing throws, there is no stack, and no line number will ever exist for it. The browser reports it on the element itself, and StorePilot treats it by what it is:
- Media (
img,source,video,audio,track) is a measurement, never an issue: each failure counts into a daily table, grouped by directory — a catalogue with a missing image folder is one thing to go and fix, not five hundred rows — and it costs nothing an issue costs: no notification, no event against your plan, no entry in the spike baseline. - Third-party scripts and stylesheets (an address on somebody else's domain — a tag manager, a chat widget, an analytics pixel) are measured the same way: their failures are usually a visitor's ad blocker, not your site, and an issue you cannot fix is noise.
- Your OWN scripts and stylesheets stay real issues, named after what failed
(
Failed to load script: https://mysite.com/wp-content/themes/shop/app.js) — a broken first-party script breaks the page, and that is exactly what the issues list is for.
The measurements live on the Issues page's Assets tab, split into your own hosts and
third-party ones, each directory with its most recent failing file as an example. A
cache-busting ?ver= or a build hash in a filename never splits one broken file into a new
entry on every deploy, and a page reports a bounded number of distinct failures, because a CDN
outage is one finding rather than a transcript.
Filtering by Type = resource in the errors list narrows to your own failed scripts and
stylesheets — the only resource failures that are issues.
PHP Errors
The plugin registers a custom error handler and exception handler early in WordPress boot (via the MU-plugin loader). Fatal errors, warnings, notices and uncaught exceptions are captured server-side and forwarded to the Platform.
Which plugin's errors you see is your choice, except for fatals. A busy store's PHP log is mostly deprecation notices from plugins you did not write and cannot fix, so WP Admin → StorePilot → Settings → Plugins lists your installed plugins and you tick the ones you want watched. Nothing is ticked to begin with.
That list governs warnings, deprecations and notices. It never governs a fatal error or an uncaught exception: those are reported from any plugin whether or not you ticked it, because a fault that takes a page down is not something anyone selects a checkbox for in advance.
Errors raised in your theme or in WordPress core are always reported and are not affected by that list.
Error Fingerprinting
To group repeated occurrences of the same error into a single record rather than creating thousands of duplicates, the Platform creates a fingerprint for each error.
The fingerprint is computed on the Platform, from two things only:
- the error type (
js,ajax,promise,php,console,resource) - the error message, normalised
Normalising removes what changes between two reports of one fault: query strings, id-shaped
path segments, timestamps, long numbers and hashes, and a build hash inside a filename. So
/product/917 and /product/918 are one issue, and a bundle that stops loading does not
become a new issue on every deploy.
It also removes the browser's dialect. Chrome, Firefox and Safari describe the same event in three different sentences, and without this one bug is three issues:
| What the browser says | Grouped as |
|---|---|
Uncaught ReferenceError: x is not defined · Can't find variable: x | ReferenceError: x is not defined |
Cannot read properties of undefined (reading 'p') · undefined is not an object (evaluating 'a.p') · can't access property "p", a is undefined | TypeError: cannot read 'p' of undefined |
x is not a function. (In '…') | x is not a function |
What is not removed is anything that tells two faults apart — the missing variable, the property being read, the function called. Two different missing globals stay two issues.
If an incoming error matches an existing fingerprint, the occurrenceCount is incremented and the lastSeenAt timestamp is updated.
Error Statuses
| Status | Meaning |
|---|---|
new | Newly captured; not yet reviewed |
investigating | A team member is looking into it |
resolved | Fix deployed; the error should not recur |
ignored | Known, not worth fixing (e.g. browser extension noise) |
muted | Silenced — no further notifications for this error |
Automatic Re-open
If an error with status resolved or ignored is received again (same fingerprint), it is automatically:
- Reset to status
new occurrenceCountreset to1- A new notification fires
This ensures that a regression doesn't go unnoticed because the error was previously marked resolved.
Error Detail
Each error record contains:
| Field | Description |
|---|---|
message | Error message text |
source | JavaScript file URL or "PHP" |
lineno / colno | Source location |
stack | Full stack trace |
status | Current status |
occurrenceCount | How many times this error has been seen |
firstSeenAt | Timestamp of first occurrence |
lastSeenAt | Timestamp of most recent occurrence |
context | URL, user agent, referrer at time of capture |
recording | Linked session recording (if the error occurred during a recorded session) |
Occurrence Timeline
The fields above are an aggregate: how many times, first seen, last seen. They answer is this still happening — not was it once every ten minutes all morning, or four hundred times in ninety seconds, which is usually the question that says what broke.
The Occurrence timeline on an issue's page lists the individual moments: when each one happened on the visitor's own clock, which page it was on, which visit it belonged to, and a link straight into the replay where footage still exists.
It shows the most recent moments rather than all of them, and the panel states three numbers so a partial list is never read as the whole history:
| Number | What it means |
|---|---|
| Shown | Moments on this page of the list |
| Recorded moments | How many individual moments are stored for this issue |
| Reported in total | Every occurrence ever counted, which is the occurrenceCount above |
Two things make the second number smaller than the third, and both are deliberate. A single visit stores a bounded number of moments, so one visitor stuck in an error loop cannot fill the timeline. And moments are kept for 30 days, a much shorter window than the issue itself — the issue and its counts remain long after the individual positions are swept.
Server-side errors have no timeline, and the panel says so. A moment is placed against a visit; a report that arrives from a server — the WordPress plugin's PHP, or a backend SDK — has no visit to be placed on. In that case the counts on the issue are the complete record, and an empty timeline means "this kind of report has no moments", not "nothing happened".
The same moments are what the replay scrubber marks, so a session that hit one bug five times shows five marks rather than one mark and a count.
Linked Recordings
When an error is captured during a session that is being recorded, the recording session ID is stored alongside the error. In the dashboard you can click "View Recording" to replay exactly what the user was doing when the error occurred.
Steps Before the Error
Each visit that hit an error carries a short trail of what the visitor did on the way into
it: pages they moved between, elements they clicked, failed network requests, and anything the
site's own scripts printed with console.error. It is shown under the visit in Linked
Recordings, open by default where no recording survives — a server-side report, a visitor
who declined recording, or footage already removed by retention — and collapsed where you can
simply watch the session instead.
The trail is deliberately structural. A click records the element's selector (tag, id and first class), never text from the page; a request records its method, address and status code and never its body; a click on a password, payment or hidden field records nothing at all. At most 20 steps are kept, and only the first time a given visit hits a given error — the first account of how somebody got into a fault is the useful one.
Because it describes one person's own actions, the trail is part of that visitor's personal data: it is included in a visitor data export and removed by the erasure endpoint, together with the rest of the visit.
"Script error." — when the browser hides the details
Some errors arrive with the message "Script error.", no file and no stack. This is the
browser deliberately withholding them, not StorePilot losing them: when a script served from
another domain throws, the browser refuses to describe the fault to the page unless that
script's <script> tag carries crossorigin="anonymous" and its server sends an
Access-Control-Allow-Origin header.
The error detail page says so, and lists every cross-origin <script src> on the page that was
missing the attribute when the fault happened. Add crossorigin="anonymous" to the tag
responsible and the next occurrence arrives with its real message and stack. StorePilot's own
loader has carried the attribute since 1.9.0, so the script in question is always another
vendor's.
That list is the address the page already publishes in its own HTML, reduced to origin and path; no query strings are stored.
Noise Reduction
Trusted Origins only — The WordPress proxy validates the Origin and Referer header, ensuring only your own domain can submit error events.
Muted errors — Errors you don't care about (e.g. from browser extensions) can be set to muted status and will never trigger notifications again.
Ignored status — Errors set to ignored still accumulate occurrences but do not send notifications.