JavaScript Error Monitoring

Fixing 'Script error.' (Cross-Origin JS Errors)

The infamous 'Script error.' hides the real cause behind cross-origin rules. Learn why it happens and how crossorigin + CORS reveal the details.

You load a third-party script via a CDN, or you ship JavaScript across multiple domains, and suddenly your error tracker is flooded with messages like "Script error." — no file, no line number, no stack trace. Just "Script error." and nothing else. This useless error message is the browser protecting itself using the same mechanism that keeps malicious sites from reading your email: cross-origin security.

The good news is that script error is entirely fixable with a single attribute and a one-line HTTP header. Here's what causes it, why browsers do it, and the exact steps to get back the real error message so you can actually debug production failures. Understanding this is essential for any team doing JavaScript error monitoring at scale.

Why "Script error." happens

When a script on one origin throws an error inside a script loaded from a different origin, the browser sanitizes the error message as a security precaution. It shows you that something broke, but not what broke — because revealing the real error might leak information from a third-party service to a site that has no business reading it.

For example:

  • Your site is at example.com and loads a tracker from analytics.example.com.
  • The tracker's code throws uncaught ReferenceError: sessionKey is not defined.
  • The browser sees a cross-origin error and replaces it with generic "Script error.".
  • Your error tracker records only the useless message, not the real exception.

This is by design. Without this restriction, a malicious script could probe APIs and infer secrets from error messages. The browser's default posture is "I don't trust cross-origin code, so I won't tell you what it said."

How browsers decide what's cross-origin

A script is cross-origin if its URL's scheme, domain, or port differs from the page's URL. So:

  • Page at https://app.example.com:443 loading https://cdn.example.com:443cross-origin (different subdomain)
  • Page at https://example.com loading https://example.com/app.jssame-origin (identical)
  • Page at https://example.com loading http://example.com/app.jscross-origin (different scheme)
  • Page at https://example.com loading https://cdn.example.com/app.jscross-origin (different domain)

The key point: most third-party or CDN-hosted scripts are cross-origin by definition.

Fix 1: Mark scripts with crossorigin="anonymous"

The first fix is to add the crossorigin attribute to the <script> tag. This tells the browser you give permission to reveal the error message — you want the stack trace even though the script is cross-origin.

<!-- Before (generic "Script error.") -->
<script src="https://cdn.example.com/tracker.js"></script>

<!-- After (real error message) -->
<script src="https://cdn.example.com/tracker.js" crossorigin="anonymous"></script>

The value "anonymous" means the browser will request the script without sending credentials (cookies, auth headers). The script will be loaded as if it were from a public CDN with no authentication needed.

If the script does need credentials — for example, it's hosted on a private CDN that requires authentication — use crossorigin="use-credentials" and ensure the server sends the right CORS headers.

Adding crossorigin by itself is not enough. The server hosting the script must also send the correct CORS headers, or the browser will block the script entirely and you get an entirely different error. See the next section.

Fix 2: Server sends the right CORS headers

For crossorigin="anonymous" to work, the server hosting the script must respond with the Access-Control-Allow-Origin header. The simplest fix is to allow any origin:

Access-Control-Allow-Origin: *

This tells the browser: "Any page on any origin is allowed to load and read the details of this script."

If you want to be more restrictive — say, only allow your own domain — use:

Access-Control-Allow-Origin: https://app.example.com

How you set this header depends on your infrastructure:

AWS CloudFront:

Origin: *
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Allow-Headers: *

Nginx:

add_header Access-Control-Allow-Origin "*";

Express.js:

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  next();
});

Python (Flask):

from flask_cors import CORS
CORS(app)

Once both the crossorigin attribute and the header are in place, errors thrown by cross-origin scripts will include their real message, file name, and line number.

Seeing the difference in an error tracker

Before the fix, you see:

{
  "message": "Script error.",
  "filename": "",
  "lineno": 0,
  "colno": 0
}

After the fix:

{
  "message": "Cannot read properties of undefined (reading 'userId')",
  "filename": "https://cdn.example.com/tracker.js",
  "lineno": 142,
  "colno": 45
}

With the real error message, you can now read the stack trace, identify the exact line, and fix it. This is the difference between "something broke somewhere" and "line 142 of tracker.js tried to access .userId on undefined." Once you fix the CORS issue, capturing browser errors with an SDK becomes practical — you get real context instead of cryptic noise.

If you're tracking errors with the Sentry SDK (which LightTrace is compatible with), it automatically captures and groups these errors so you can see which ones are most common across your users. That prioritization turns a noisy error feed into a short list of real issues to fix.

Common mistakes

Mistake 1: crossorigin without CORS headers. The browser requests the script with CORS, the server doesn't send the header, and the entire script is blocked. You get a blank page and a different error in the console. Always send the header.

Mistake 2: Forgetting to redeploy the header. You add crossorigin to the page, but the CDN or server hosting the script hasn't been updated with the CORS header yet. The script loads, but errors are still sanitized. Check that both the script tag and the server are updated.

Mistake 3: Overly restrictive CORS headers. If your script is served from one domain but your main app might be on another (e.g., staging, preview deploys), a too-strict CORS policy will block it on some environments. Use * in development or allow a list of your own domains.

Getting this wrong is frustrating to debug. Combine it with unhandled promise rejections and other silent failures, and you need a robust strategy to catch them all — which is why error tracking matters.

When "Script error." is actually helpful

In some cases, you want to be vague. If you're using a third-party analytics or error tracking provider and you don't trust its code, you might intentionally not add crossorigin to prevent that provider from seeing detailed errors from your app's own scripts. This is rare, but it's worth knowing the trade-off exists.

For your own code or trusted vendors, you should always fix it. Global JavaScript error handling only works if you can see what actually broke.

Summary

The "Script error." message is a browser security feature that hides cross-origin error details. To reveal the real error:

  1. Add crossorigin="anonymous" to the <script> tag.
  2. Ensure the server sends Access-Control-Allow-Origin headers.
  3. Re-deploy both the HTML and the server.

Once both are in place, your error tracker will capture the real error message, file name, and line number — turning mysterious "Script error." entries into actionable issues you can actually fix. Combined with source map upload, you'll see your original source code even in minified production builds, making debugging cross-browser JavaScript errors and other failures dramatically faster.

Start tracking errors in minutes

Start capturing real script errors with full stack traces — point the Sentry SDK at LightTrace to see cross-origin failures grouped and prioritized by impact.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.