A JSFuck decoder takes a JSFuck-encoded payload, source written using only the six characters [, ], (, ), !, and +, and statically recovers the original JavaScript without running it. JSFuck itself is public knowledge: it exploits JavaScript's implicit type coercion rules to spell out any program from just those six symbols. Decoding it safely means simulating those coercion rules on paper, not handing the payload to a browser console, Node, or an online JS sandbox.
Paste JSFuck source, the bracket-only []()!+ blob, and get the original JavaScript back as readable text. The payload does not get a chance to run. If you got here because you were halfway through writing a python jsfuck decoder, or searching for a jsfuck decoder library to pip install, that instinct is close to right and worth pausing on before you finish it. A decode script that works by calling exec(), shelling out to node, or driving a headless browser to render the page is not decoding JSFuck. It is running it, and if the sample is malicious, running it is the one step you cannot take back: a cookie is already read, a beacon has already fired, a redirect has already happened, before your script even finishes printing a result. Static decoding skips that step. Read on for how JSFuck payloads actually execute, why the usual quick-check moves, console paste, online evaluator, Python-to-JS bridge, cross the same line in slightly different ways, and what decoding an alert(), an XSS payload, or a hybrid JSFireTruck stub looks like as a read-only static result.

Decode JSFuck without running it. Paste a JSFuck-encoded sample at klaroskope.com/submit and get the recovered JavaScript back as text, including hybrid JSFireTruck stubs that unpack a Base64 or string-array inner layer in the same pass.
- Why you cannot just run it to decode JSFuck code
- Decode a JSFuck alert() or XSS payload
- Hybrid JSFuck (JSFireTruck): the stub that unpacks Base64 or a string array
- JSFuck with variables: when the blob is a program, not an expression
- Do I need a python JSFuck decoder library?
- When JSFuck decoding gets hard
- The wider family: JSFuck dialects, JJEncode, and AAEncode
Why You Cannot Just Run It to Decode It
JSFuck output is not a stub that unpacks itself later and waits for a signal. It is the program. What is JSFuck covers how six characters build up to full statements through type coercion; the practical consequence is that JSFuck can spell out any identifier character by character, including eval and the Function constructor, the exact primitives that turn decoded text into running code. The moment a JavaScript engine parses the source, the hidden logic runs. This is also what makes JSFuck effective against keyword-based content filters: a filter scanning for eval or document.cookie as literal text finds neither, because those words do not appear as strings in the file. They exist only as a coercion result, produced once something actually executes the expression.
That property is exactly why a jsfuck decode has to be read-only to count as decoding. The moment execution enters the picture, you are not decoding the sample anymore, you are running it, and running an untrusted sample to find out what it does is the outcome static analysis exists to avoid. The common ad hoc moves defenders and developers reach for to quickly decode jsfuck code cross that same line in slightly different ways.
- Pasting the blob into a browser console, which runs it with the page's cookies, DOM, and session context
- Online JS "unpacker" or beautifier sites that call eval() internally to produce readable output
- Rendering the page in a headless browser (Selenium, Puppeteer, Playwright) to see what loads, which runs the script with real network access
- Python-to-JS bridges such as PyExecJS, js2py, or quickjs bindings, built specifically to execute JavaScript from Python
- Shelling out to a real JS runtime, for example calling node -e from a Python script, which just moves the eval into a subprocess
# This "quick check" looks like static analysis but is not:
import execjs
ctx = execjs.compile(jsfuck_source) # already ran whatever the payload does
result = ctx.eval("1") # side effects have already happened# Shelling out doesn't remove the risk, it relocates it:
import subprocess
subprocess.run(["node", "-e", jsfuck_source]) # executes in a real JS engineA JSFuck payload is valid JavaScript from its first character. There is no preview mode: a browser tab, a Node process, a headless browser, and a Python-to-JS bridge each run the hidden logic in full the moment they parse it. Decode it statically first, and only consider running the recovered plaintext once you know what it says.
Decode a JSFuck alert() or XSS Payload
Searching for something like jsfuck alert(1) code usually means you already have a bracket blob and a hunch about what it decodes to: a pen tester's proof-of-concept, or a live injection someone else planted, encoded in JSFuck specifically so a WAF or input filter waves it through. What you are left with is a bracket blob sitting inside a page's HTML, and the question is simple: is this an alert(1) proof-of-concept, an alert(document.cookie) cookie-theft demonstration, or something that redirects the victim to another site altogether? You cannot tell from the encoded form. You can tell from the decoded one.
INPUT: JSFuck-encoded XSS payload (truncated, ~3,800 characters)
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+
(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][...]
OUTPUT: Decoded JavaScript (recovered statically, no execution)
alert('XSS successful')That output tells you what a security assessment or bug bounty submission actually looked like: a confirmation string, no cookie access, no follow-on network call. Write-ups and challenge descriptions phrase the same confirmation different ways, alert('XSS successful'), a printed "Operation successful", a changed document.title, but the mechanism is identical: the decoded call is the evidence. If the decoded source instead reads document.location='http://...' or reaches into document.cookie, the severity assessment changes, and it changes based on evidence you can cite rather than a guess. Recognising JSFuck-encoded XSS in the wild is not new: JSFuck-encoded payloads slipped past eBay's listing filters between 2015 and 2017, executing in auction pages that buyers viewed. The decode step is what turns "there is a bracket blob in this listing" into a specific answer for what it does.
Hybrid JSFuck (JSFireTruck): When the Stub Unpacks Base64 or a String Array
Pure JSFuck is expensive to write in. The word "alert" alone costs roughly 21,000 characters once each letter is derived from type coercion, so a full payload encoded purely in JSFuck would run to megabytes, impractical to inject into a compromised page without drawing attention. Attackers who want JSFuck's filter-evasion property at real scale solve this by encoding only a small stub in JSFuck, just enough to build the handful of function names a filter would otherwise flag, and passing everything else through as an ordinary Base64 string or a string-array that the stub decodes and runs.

Unit 42 documented this exact pattern at scale in its 2025 JSFireTruck research: JSFuck-encoded redirector stubs injected into compromised WordPress pages, with the campaign reaching 269,552 pages in a single month and daily infection rates topping 50,000 pages at its peak. The stub itself stayed small; the Base64 or string-array payload it decoded carried the redirect logic that sent visitors toward tech-support scams and malware downloads. This is advisory-level reporting on a documented campaign, not attribution to a specific threat actor.
This hybrid shape is where a naive "just eval it" approach is at its most dangerous and least informative at the same time. Running the stub executes both layers back to back in a single step: the redirect or download happens, but the Base64 or string-array data underneath, the part that would actually tell a defender what the stub was reaching for, stays invisible as data. It only exists as a side effect that already happened. A static decoder that treats the stub as one layer in a chain avoids that trade-off: recover the stub's plaintext first, recognise the encoding underneath it, and keep decoding through it in the same pass, so the output is a readable redirect URL or payload location rather than a page navigation you have to reverse-engineer after the fact.
JSFuck With Variables: When the Blob Is a Program, Not an Expression
The JSFuck most write-ups describe is one expression. Classic jsfuck.com output is a single bracket chain that evaluates to a string and hands it to the Function constructor; you can decode it by walking the expression once. A sample that reached the decoder on this page in August 2026 did not look like that. It opened with a letter, an equals sign and a semicolon-terminated line, and it used two characters JSFuck is not supposed to need: curly braces and a left shift.
N = (!![] + [])[+[]] + ([] + {})[+!![]] + ...; // "toString"
I = ([] + {})[(!![] + !![] << !![]) + !![]] + ...; // "constructor"
R = (!![] + [])[+!![]]; // "r"
GI = ([][[]] + [])[+!![]]; // "n"
EE = ([] + {})[+!![]]; // "o"
...twenty more single-character assignments...
[][EG + RI][I](R + GR + RI + IE + R + GI + ...)()[...]Three things are going on, and each has a reason. First, hoisting. Each character the program needs is computed once, assigned to a one- or two-letter name, and reused by name everywhere else. A character that costs a few dozen brackets to derive now costs two letters at each later use, so a program that would run to tens of kilobytes in pure JSFuck fits in under eight. Second, a cheaper alphabet. ([] + {}) coerces to the string "[object Object]", which puts b, c, e, j, t and a space within reach at short indexes without the long detour through fontcolor() or escape(). The left shift builds small integers (!![] + !![] << !![] is 4) in fewer characters than chaining plus signs, and a division by zero, +!![] / +[], is the shortest route to the string "Infinity" and its capital I. Third, a real control flow at the end: the final statement reads a browser global, branches on the result with a ternary, and calls a second global with the outcome.
None of this is exotic JavaScript. It is the same coercion trick as JSFuck with a slightly larger toolbox, and the encoders that produce it are trading the six-character purity for a blob small enough to paste into a CTF page or a comment field. From a filter's point of view the evasion property is unchanged: no keyword appears as a literal anywhere in the source. From a decoder's point of view, though, the job changed shape. This is a program with state, not an expression with a value, and a decoder that expects one expression either rejects it outright or reads the first statement and stops.
The sample above also makes a second point that is easy to miss. Its final statement calls prompt() and then alert(). There is no way to compute what prompt() returns without a browser and a person typing into it, so a decoder that insists on resolving each value to a constant is stuck, and the tempting way out is to execute it, which is the line this whole article is about not crossing. The better framing is partial evaluation. Resolve what can be resolved statically, which is almost all of it: the strings, the method names, the numbers, the comparison target. Leave what depends on a runtime value in place as source. The output is not a value; it is a shorter program.
INPUT: 7.6 KB, 28 statements, 27 hoisted single-character variables
OUTPUT: the residual program (recovered statically, no execution)
alert(!("a" + prompt("Enter the flag")).replace("a<expected>", "") ? "Correct!" : "Incorrect...")That one line is the whole answer an analyst needs. It names both globals the script touches, it shows the prompt text, it exposes the comparison target (the expected flag, redacted here), and it shows that the only effects are two dialogs. The replace() trick is worth a second look because it is how the author compared strings without a single equals sign, which the character budget did not allow: prepend a known letter, remove the expected value as a substring, and test whether anything is left. Seen in the residual source it is obvious. Seen in the bracket blob it is invisible. No network access appears anywhere in it, which is the usual outcome for a CTF checker, and the same residual-program approach is what turns a hoisted redirector stub into a readable URL when the sample is not a puzzle.
If your JSFuck sample contains semicolons, short identifier names, curly braces or a << operator, it is the hoisted-variable dialect. A single-expression decoder will not read it. The decoder behind this page treats it as a program and returns the residual source, with anything that depends on a browser value left in place.
Do I Need a Python JSFuck Decoder Library?
Searching for a python jsfuck decoder, jsfuck decoder python, or jsfuck decoder library usually means one of two things: you want to automate triage of samples inside a Python pipeline, or you want to avoid running untrusted JavaScript in the first place and would rather stay inside a language you already control. The second reason is sound. The first is where most available options fall short, and the honest reason is worth spelling out rather than waving away.
Writing a robust decoder from scratch is harder than the six-character rule suggests. It has to reproduce JavaScript's type-coercion behaviour with specification-level accuracy: HTML-escaping quirks in deprecated string methods like fontcolor(), the exact string form a RegExp literal produces, multi-pass resolution of characters that are themselves defined in terms of other characters, and escape-sequence handling that differs from what most people assume about octal versus hex. Why JSFuck deobfuscation breaks walks through these specific edge cases in depth. A decoder that gets one of them wrong does not throw an error; it quietly produces the wrong character at that position, and a single wrong character in a wall of brackets is easy to miss.
Many Python JSFuck decoder packages take a shortcut instead of solving that problem: they wrap execjs, PyExecJS, or a bundled Node subprocess, and hand your JSFuck string to a real JavaScript engine to evaluate, then capture whatever it produces. That is not static decoding. It is running the payload inside a subprocess instead of a browser tab, with the same execution risk in a different wrapper. If the JSFuck decodes to something that reaches the network, writes a file, or spawns a shell, the wrapper library gives it a place to do exactly that.
The practical route for a one-off sample: paste it into a hosted static decoder built for this, instead of writing or importing a jsfuck decoder library. Paste at klaroskope.com/submit; the decoded JavaScript comes back as text, hybrid stubs continue through the inner Base64 or string-array layer in the same pass, and there is no local Python environment that had to execute an unknown payload to get there.
When JSFuck Decoding Gets Hard
Some JSFuck samples do not decode cleanly on the first pass. The encoder relies on specification-level JavaScript runtime behaviour, including deprecated string methods and edge cases in how certain characters get indexed, and a decoder that gets those details even slightly wrong produces output that looks plausible but does not match what the original code actually did. Custom character sets like the ones introduced in the JSFireTruck campaign, very large hybrid files, and deeply nested multi-pass character references push on those same edge cases. Why JSFuck deobfuscation breaks covers the specific cases that defeat naive decoders in more depth, useful background if a decode result looks off or truncated. The short version: a decoder should flag when it is unsure rather than presenting a partial guess as a clean recovery.
Paste your JSFuck sample at klaroskope.com/submit and get the recovered JavaScript back as text, without running any of it along the way. Hybrid samples (a JSFuck stub wrapping Base64 or a string-array inner layer) decode through the full chain in a single pass. Try KlaroSkope Free
JSFuck Is One Dialect in a Family of Esoteric JavaScript
Everything above treats JSFuck as a single thing, but in practice it arrives in several dialects, and it has close siblings built on the same idea. All of them spell out ordinary JavaScript using a restricted character set and JavaScript's own type-coercion rules, which means all of them can be read the same way: simulate the coercion on paper, and never hand the source to a runtime. The decoder behind this page is built for the family rather than a single canonical form, so a sample that is not textbook jsfuck.com output still has a good chance of decoding.
- Classic JSFuck: the pure six-character []()!+ expression from jsfuck.com, recovered to the original call.
- JSFireTruck custom character sets: the campaign variant that remaps which brackets stand for what, covered above, recovered to source.
- Hybrid JSFuck: a small JSFuck stub wrapping a Base64 or string-array inner layer, decoded through the whole chain in one pass.
- Hoisted-variable JSFuck: the program-with-state dialect that leans on semicolons, short names, {} and <<, returned as residual source.
- JJEncode: a sibling scheme that widens the alphabet to a set of symbols such as $ _ { } and returns the same underlying JavaScript.
- AAEncode: a sibling scheme that spells the program out as Japanese-emoticon faces, which tends to read as harmless text art at a glance.
JJEncode and AAEncode are worth calling out because they look nothing like JSFuck on the surface, yet they decode by the same static method. A JJEncode blob is a wall of dollar signs and underscores; an AAEncode blob is a row of smiling faces. Both are ordinary JavaScript underneath, and both come back as readable source without the payload ever running.
INPUT: JJEncode payload (head shown)
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],...
OUTPUT: decoded JavaScript (recovered statically, no execution)
alert(1)INPUT: AAEncode payload (head shown)
゚ω゚ノ= /`m´)ノ ~━━━ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; ...
OUTPUT: decoded JavaScript (recovered statically, no execution)
alert(1)AAEncode has its own page with worked examples: Decode AAEncode.
Not every dialect resolves to a clean answer, and the tool is built to say so rather than guess. Some variants assemble the Function constructor or eval from a string at runtime and hand the real payload to it only once a JavaScript engine is actually running, which is the one step static analysis does not take. On a sample like that, the honest result is to report that the outer layer was recognised and that the inner payload was not statically recoverable, rather than to present a placeholder value as if it were the decoded code. A decoder that returns a confident wrong answer on a sample it could not resolve is worse than one that flags the limit, so these dynamic-bootstrap dialects are treated as a frontier to be marked, not papered over.
Common Decoded Outputs
A JSFuck payload found in the wild tends to decode to one of a handful of confirmation strings. Recognising the shape can save a step before reading the full decoded output.
alert(1): the standard proof of concept call used to confirm that arbitrary JavaScript executed on a page. On its own it is a confirmation, not evidence of any specific follow on action.
xss successful: a human readable confirmation string, commonly wrapped inside a call such as alert("xss successful"). It confirms a script injection worked, again without implying a particular follow on step.
operation successful: often appears inside a console.log() call as a generic marker left behind by a script or challenge author, rather than as a description of what the underlying code actually does.
Decode Your Own JSFuck Sample
Frequently Asked Questions
Is it safe to decode JSFuck?
Can I decode JSFuck in Python?
What does a JSFuck alert(1) payload do?
How do I decode a JSFuck XSS payload found in a compromised page?
What is JSFireTruck?
Why does my JSFuck sample contain letters, semicolons and curly braces?
Do I need a JSFuck decoder library, or is a hosted tool enough?
Continue Learning
Ready to decode?
See KlaroSkope transform obfuscated scripts into actionable intelligence.
Try It Free