How to deminify a JavaScript stack trace (the manual way and the 5-second way)
A walkthrough of the VLQ-decode pain of reading minified stack traces by hand, then a free tool that does it instantly.

It's 4:30 PM on a Friday. You're mentally checking out, planning your weekend, or maybe just looking forward to closing your laptop. Then, the Slack notification hits. A critical bug just reached production. You pull up your error logs, hoping for a quick fix, but all you see is a wall of text that looks like this: TypeError: n is not a function at t.default (main.ab42f1.js:1:1402).
You open main.ab42f1.js in your editor. You see 150,000 characters of mangled variable names, zero whitespace, and everything crammed onto a single line. You look for column 1402. Is it the function call? Is it a property access? Is n a callback, a configuration object, or a DOM element? You have no idea. You're flying blind in your own codebase, and every second the fix takes is another second of downtime for your users.
Why Your Production Stack Traces Are Useless (By Design)
The reason your stack trace looks like garbage isn't because your tools are broken; it's because they are doing exactly what you told them to do. We spend hours optimizing our build pipelines to ship the smallest possible bundles. We use tools like Terser, ESBuild, or SWC to strip out comments, remove whitespace, and shorten calculateMonthlyRecurringRevenue to e. This is great for your Core Web Vitals, but it is a disaster for your mental health when things break.
The trade-off between bundle size and debuggability
In development, your code is beautiful. It has descriptive variable names, helpful comments, and a logical structure spread across dozens of files. But shipping that code to a browser is expensive. For every kilobyte of JavaScript you send, the browser has to download, parse, and compile it. Minification and bundling are the solutions to this performance problem. By the time your code hits the CDN, the original structure is gone. The relationship between your source code and the execution environment is severed.
Why Source:1:1402 is a mathematical riddle, not a location
When an error occurs at line 1, column 1402, it doesn't mean the error happened on the first line of your source code. It means the error happened 1,402 characters into a file that might contain 50 different source files stitched together. Without a map, that number is effectively meaningless. It's like being given a set of coordinates but no map of the planet—you know exactly where you are, but you have no idea what's supposed to be there.
// Your original TypeScript
function authenticateUser(user: User, token: string) {
if (!token) {
throw new Error("Missing authentication token");
}
return user.id;
}
// The "terser" output
function(n,t){if(!t)throw new Error("Missing authentication token");return n.id}In the original code, the error is on line 3. In the minified version, it's somewhere in the middle of a massive blob. If the minified file is 2MB, "line 1" could be anything.
Anatomy of a Source Map: The .map File
To solve this, the web community created the Source Map. A source map is essentially a JSON sidecar file that tells the browser (or your error tracker) how to translate positions in the minified file back to the original source code. If you've ever looked inside a .map file, you probably saw something that looked like a terrifying alien language.
Version, Sources, and Names arrays
A typical source map contains a few key fields. The version is usually 3. The sources array lists the original file paths (e.g., ["src/auth.ts", "src/api.ts"]). The names array contains all the original variable and function names that were mangled during minification. This is how a tool knows that n was actually user.
Some source maps also include a sourcesContent field, which actually embeds the entire original source code inside the JSON. This is incredibly helpful because it means you don't need to have the original files lying around to deminify a trace—everything is self-contained.
The mappings string: The heart of the deminification process
The most important field, and the most confusing one, is mappings. It looks like a long string of random characters: AACA,EAAG,OAAOC. This is a highly compressed representation of every single character in your minified file and where it maps to in your original source. Without this string, deminification is impossible.
The Hard Way: Decoding Base64 VLQs by Hand
If you really want to understand how deminification works, you have to look at Variable-Length Quantities (VLQ). Source maps use VLQ encoding because representing millions of mappings as standard integers would make the .map file ten times larger than the JavaScript bundle itself. VLQ allows us to represent large numbers using a small number of Base64 characters.
What is a Variable-Length Quantity (VLQ)?
VLQ is a bitwise encoding scheme. Each character in the mappings string represents a value. Here is how the math works: each Base64 character provides 6 bits of data. The first bit (the Most Significant Bit) is a "continuation bit." If it's set to 1, it means the current number continues into the next character. The last bit (the Least Significant Bit) is the "sign bit," telling us if the number is positive or negative. The middle 4 bits are the actual value.
The Base64 mapping table and bitwise shifts
To decode a character like C, we first find its value in the Base64 table (where A=0, B=1, C=2, etc.). C is 2. In binary, that is 000010.
- The continuation bit (leftmost) is 0, so the number is complete.
- The sign bit (rightmost) is 0, so the number is positive.
- We shift the value right by one (
2 >> 1) to get the final integer: 1.
Walking the string: Relative offsets vs. absolute positions
Source maps don't store absolute line and column numbers. They store relative offsets. Each segment in the mappings string usually consists of four or five fields:
- The column in the generated (minified) file.
- The index of the original source file in the
sourcesarray. - The original line number.
- The original column number.
- (Optional) The index of the original name in the
namesarray.
If you see AACA, you decode each character into an integer. Let's say it decodes to [0, 0, 1, 0]. This means: "At column 0 of the current line, look at source file 0, line 1, column 0." The next segment will then be relative to those numbers. If the next segment starts with a 2, you add 2 to your current column position.
Why Manual Decoding Fails in the Real World
You can technically decode a VLQ string with a pen, paper, and a bitwise calculator, but you shouldn't. Modern bundles are massive. A single mappings string can contain hundreds of thousands of segments. If you are trying to debug a production incident at 2:00 AM while the error rate is tripling, you do not have time to do bitwise math.
The scale of modern bundles
Webpack and Vite often produce bundles that are several megabytes in size. The source map for a 2MB bundle can easily be 10MB. Manual decoding is not just slow; it's physically impossible for a human to track the state machine required to maintain relative offsets across millions of characters without making a mistake.
The "Off-by-one" nightmare
Even if you get the math right, column counting is a nightmare. Does your editor start columns at 0 or 1? Does your minifier? If you are off by a single character, you might be looking at the wrong variable, leading you down a rabbit hole of "impossible" bugs that don't actually exist.
Context matters: Losing original variable names
Deminification isn't just about finding the file and line. It's about context. If the error says Cannot read property 'id' of undefined and the minified code says e.id, you need to know what e was. A real deminifier uses the names array to reconstruct the original variable name, so you can see that the error was actually user.id.
The 5-Second Way: Using the GlitchReplay Deminifier
We built a free JavaScript Source Map Deminifier specifically because we were tired of the manual friction. When you are in the middle of an outage, you need an answer, not a math lesson. You shouldn't have to set up a full Sentry or Datadog instance just to read a single stack trace from a side project or a quick script.
Instant resolution without a subscription
Most error tracking platforms keep their deminification logic behind a login wall. Our tool is public and requires zero setup. You paste your minified stack trace, upload your .map file, and we do the VLQ decoding, relative offset tracking, and name mapping for you instantly.
How to use the tool
- Copy the messy stack trace from your logs (e.g., Cloudflare Logs, AWS CloudWatch, or browser console).
- Navigate to the Deminifier tool.
- Paste the trace into the text area.
- Drag and drop your
.mapfile into the browser. - Watch as the obfuscated mess turns into readable code with file paths, line numbers, and original function names.
Privacy first: Why local deminification is safer
A common concern with "online" tools is privacy. You don't want to upload your proprietary source maps (which contain your source code) to a random server. We designed our tool to work entirely in your browser. When you "upload" a source map, it never leaves your machine. The deminification logic runs in your local JavaScript environment. Your code stays your code.
Security Best Practices: Handling Source Maps in Production
Since source maps can contain your entire original source code via the sourcesContent field, you need to be careful about how you handle them. If you leave your .map files on your public CDN, anyone can download them and see exactly how your application is built. Competitors can audit your logic, and bad actors can look for vulnerabilities in your source that are harder to spot in minified code.
The risk of exposing original source code
If you run ls dist/ and see main.js and main.js.map, and both are accessible via example.com/main.js.map, you are leaking your source code. For many internal tools or B2B apps, this is a security violation. You want the benefits of source maps without the public exposure.
Hidden source maps and VPNs
One common strategy is "Hidden Source Maps." You generate the maps during your build process, but you don't upload them to your public server. Instead, you store them in an S3 bucket that is only accessible behind a VPN or through your internal error tracking platform. You can find more details on how to set this up in our Source Map Documentation.
CI/CD integration
The gold standard is to upload your source maps to your error tracker (like GlitchReplay or Sentry) during the build phase of your CI/CD pipeline and then immediately delete them before the final assets are deployed to your CDN. This ensures that your error tracker can deminify traces for you, but no curious user can ever find the maps themselves.
Toward Better Error Tracking
While a manual tool is great for one-off debugging, the ultimate goal should be never seeing a minified stack trace in the first place. Modern error tracking is about automation. At GlitchReplay, we handle this entire flow for you. When your application throws an error, our SDK captures the minified trace and sends it to our ingest server. We then match the bundle hash to the source maps you uploaded during your build process and present you with a beautiful, deminified trace immediately.
Automated map resolution on ingest
By automating this, you remove the "human error" element. You don't have to go hunting for the right version of a .map file from a deployment that happened three weeks ago. We maintain the version history so that the trace you see always matches the code that was running at that exact moment.
Stacking source maps with Session Replay
The real "God Mode" of debugging happens when you combine deminified stack traces with Session Replay. Seeing TypeError: user.id is undefined at AuthService.ts:42 is great. But seeing the exact mouse movements and network requests the user made leading up to that error—and seeing that user.id is undefined because a previous API call returned a 404—is how you fix bugs in minutes instead of days.
Stop doing mental math during production outages. If you have a messy stack trace right now, use our Free Source Map Deminifier to get readable code in seconds. And if you're tired of doing this manually, it might be time to look into a tool that does the heavy lifting for you.
GlitchReplay is Sentry-SDK compatible, includes session replay and security signals, and never charges per event. Free to start, five minutes to first event.