Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Javascript Error Handler Bundle Laravel Package

mhujer/javascript-error-handler-bundle

A Symfony bundle that integrates a JavaScript error handler, capturing client-side JavaScript errors and reporting them to your backend so they can be logged and monitored alongside server-side issues.

View on GitHub
Deep Wiki
Context7

Getting Started

Accept the premise correction first: mhujer/javascript-error-handler-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no laravel/illuminate entry in composer.json, and no publishable config. composer require mhujer/javascript-error-handler-bundle in a Laravel app is not the starting move — reading the source is.

Where to look first (in the repo, not in your app):

  1. Resources/public/js/ (or the Twig template that inlines window.onerror) — this is the only artifact worth having. It is typically a handful of lines that hooks the browser error event and forwards the message.
  2. DependencyInjection/ and the bundle class — confirms it is Symfony 2.x/3.x-era glue with PHP 5.x constraints. Skim, then discard.
  3. Resources/views/ — a Twig partial that emits the script. You will re-home this as a Blade partial or Vite entry.
  4. composer.json — verify the symfony/* and php constraints so you can document why it will not resolve in a Laravel 10/11 lockfile.

First use case: dev/staging QA visibility. You want testers to see client-side breakage without opening devtools. Do not point this at production — the payload is an alert(), which is user-hostile and blocks the main thread.

Minimal viable adoption in Laravel (~30 lines, owned by you):

// resources/js/error-reporter.js
let count = 0;
const seen = new Set();
const CAP = 10;

function report(payload) {
  if (count >= CAP) return;
  const key = `${payload.message}|${payload.source}|${payload.lineno}`;
  if (seen.has(key)) return;
  seen.add(key); count++;

  fetch('/_dev/js-errors', {
    method: 'POST',
    keepalive: true,
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content,
    },
    body: JSON.stringify(payload),
  }).catch(() => {});
}

window.addEventListener('error', (e) => {
  report({ message: e.message, source: e.filename, lineno: e.lineno, colno: e.colno });
}, true); // capture phase catches resource-load errors too

window.addEventListener('unhandledrejection', (e) => {
  report({ message: String(e.reason?.message ?? e.reason), type: 'unhandledrejection' });
});

Register it in resources/js/app.js behind a flag, and gate emission from Blade with @env(['local','staging']). Backend sink: a single route guarded by auth + throttle: writing to Log::channel('js_errors').

Implementation Patterns

Pattern 1 — Vendored module behind an env flag (default recommendation). Import the module in app.js only when import.meta.env.DEV or an explicit VITE_JS_ERROR_REPORTING flag is on, and expose the same flag from Blade so the sink route is registered only in that environment. This keeps the dev-only aid out of production bundles and off your production route table.

Pattern 2 — Send to Laravel's log, not a database. The bundle's intent is "make it visible in the logs you already use." In Laravel that means a dedicated channel, not an Eloquent model:

// config/logging.php
'js_errors' => [
    'driver' => 'daily',
    'path' => storage_path('logs/js_errors.log'),
    'level' => 'debug',
    'days' => 7,
],

Then Log::channel('js_errors')->warning('client error', $request->only([...])). This gives you tail -f during QA sessions and zero schema work.

Pattern 3 — Route hardening. Even in staging, treat the ingestion endpoint as untrusted input: throttle:30,1, a payload-size guard, a whitelist of keys (message, source, lineno, colno, url), and no storage of request.userAgent unless you have a reason. Strip anything resembling a query string before logging.

Pattern 4 — Swap alert() for something non-blocking. This is the single highest-value deviation from the original. In local, console.error plus a DevTools badge is enough; in staging, a non-modal toast. Never alert() — see Gotchas.

Pattern 5 — Add unhandledrejection. The original bundle predates universal promise-rejection handling, so a modern app will silently lose a large class of failures (failed fetch, async render paths). Add the listener as shown above; it is a two-line extension to the copied pattern.

Pattern 6 — Pair with Telescope for the dev loop, not against it. Telescope covers server-side inspection; this covers the client side. They compose fine — but do not run both this snippet and the Sentry browser SDK in the same environment, or you will double-report and double-alert.

Workflow tip: write the ADR at the same time you copy the file. One paragraph: "copied the pattern from mhujer/javascript-error-handler-bundle (MIT, unmaintained, Symfony-only); implemented as first-party module; dev/staging only; owner X." Eighteen months later that paragraph is the difference between archaeology and a clean answer.

Gotchas and Tips

It will not install — and that is the point. The bundle's Symfony constraints will not resolve in a Laravel project. Any attempt to "make it work" (aliasing the bundle namespace, adding a fake service provider) is more effort than the rewrite and produces code you cannot upgrade. Copy the behavior, not the package.

window.onerror silently loses cross-origin errors. Scripts served from a CDN without crossorigin="anonymous" plus a matching Access-Control-Allow-Origin header report only "Script error." with null source/line. Your telemetry will look suspiciously clean. Fix: add crossorigin to the CDN <script> tags and send the CORS header, or self-host.

Use addEventListener('error', ..., true). The legacy window.onerror assignment (which the original bundle uses) does not catch resource-load failures (<img>, <script>, <link> 404s). The capture-phase listener does. This is a one-line upgrade with a large coverage delta.

alert() is an outage amplifier. It blocks the main thread, and an error thrown during a reflow triggered by the alert can re-enter the handler — an error loop becomes a modal storm that makes the page unusable and blocks the very console you need. The dedupe set and per-page cap in the snippet are not optional; they are the difference between a QA aid and an incident.

Never let raw messages or stacks reach end users. Error strings can contain URLs with tokens, user IDs, and DOM state. If you ever flip this into a user-visible toast, render a fixed string and log the detail server-side only.

CSP will block an inline include. If the original pattern inlines a <script> block, a strict script-src policy kills it — and you get zero telemetry with false confidence that there were no errors. Put the code in your Vite bundle (hashed by the bundler) rather than a Blade <script> block, or add a nonce.

sendBeacon has a 64KB payload cap and no custom headers. That means no CSRF token on the beacon path. If you use sendBeacon for unload-time reporting, make the endpoint CSRF-exempt but rate-limited and authenticated-by-session-only — or just use fetch(..., { keepalive: true }) as above.

Dedupe keys must survive SPA route changes. If you scope the seen set to a page load, a hash-router app will re-report the same error on every navigation. Keep the set at module scope and reset it only on explicit user action or a hard reload.

No source maps, no grouping. The payload is a raw message and line number. If your build minifies, lineno is meaningless and every error looks unique. Accept this for a dev aid; if you find yourself wanting source-map resolution, that is the signal to move to Sentry rather than extend this.

Promises and workers are invisible. Pre-unhandledrejection code (and worker contexts) escape the hook entirely. Coverage will be sparse and should be read as "some signal," never as "error rate."

Extension points, in order of value: (1) unhandledrejection listener, (2) capture-phase resource errors, (3) dedupe + cap, (4) server-side whitelist and throttle, (5) a beforeSend-style filter for known-noisy third-party scripts. Stop there — everything past that is rebuilding a monitoring product.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle