Externalizing the Bytecode Array Encoding Key
Supply your own VM bytecode encryption key with vmBytecodeArrayEncodingKey and hand it back at runtime through a key getter — kept out of the bundle, read from client storage, or fetched from your backend.
What these options do
vmBytecodeArrayEncoding encrypts the VM bytecode array so it does not sit in the
output as plaintext. By default the encryption key is derived from the environment and reconstructed on the client, so
you never handle it. That is convenient, but the key material still lives in the bundle.
Two options let you take the key out of the bundle and control it yourself:
vmBytecodeArrayEncodingKey— the key you supply at compile time. When set, it is used instead of the default environment-derived key, and it is not embedded in the obfuscated output.vmBytecodeArrayEncodingKeyGetter— a JavaScript expression that returns that same key at runtime. It is embedded verbatim and evaluated in the browser when the obfuscated code loads.
The point is separation: because the key is not in the code, a purely static scan of the bundle cannot recover it. It still has to be present at runtime for the code to run, so it is not truly secret — but you decide where it comes from and who gets to see it.
How the two keys combine
Your key is never used on its own — on both sides it is mixed with an internal key the obfuscator controls:
- Compile time.
vmBytecodeArrayEncodingKeyis combined with an internal key that the obfuscator derives, and the bytecode array is encoded with the resulting mixed key. - Runtime. The value your
vmBytecodeArrayEncodingKeyGetterresolves to is combined with the same internal key, reconstructed on the client from various runtime factors, to decode the bytecode.
Because both sides mix your key with the internal key, the getter must resolve to the exact same string you passed
as vmBytecodeArrayEncodingKey. Neither piece is sufficient alone: your key without the internal key cannot decode the
bytecode, and the internal key is useless without yours — which is why controlling who receives your key is what
actually protects the code.
Supplying the key at runtime
By default the getter is synchronous: the expression must return the key immediately when the obfuscated code
loads. Read it from any source that is already present on the client — a cookie, localStorage, a global variable, or
a server-injected DOM element.
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // e.g. 'mySecretKey123'
vmBytecodeArrayEncodingKeyGetter: "window.__VM_KEY__" // returns the key at runtime
});
The key must exist before the obfuscated code runs:
// Set by a different script, a server-injected inline script, etc.
window.__VM_KEY__ = 'mySecretKey123';
Other synchronous sources work the same way — pick whichever your app already populates:
// From a cookie
vmBytecodeArrayEncodingKeyGetter: "document.cookie.match(/vmKey=([^;]+)/)?.[1]"
// From localStorage
vmBytecodeArrayEncodingKeyGetter: "localStorage.getItem('vmKey')"
// From a server-injected meta tag
vmBytecodeArrayEncodingKeyGetter: "document.querySelector('meta[name=\"vm-key\"]').content"
// From a nested object
vmBytecodeArrayEncodingKeyGetter: "window.config.encryption.key"
Fetching the key from your backend (async)
Requires vmAsyncExecutor · v7.3.0+A synchronous getter can only read what is already on the client. To fetch the key from your server — so you can
gate it behind authentication and revoke it — the getter has to be asynchronous, and that requires
vmAsyncExecutor. With the async executor enabled, the getter may return a
Promise, and the VM awaits it before running.
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmAsyncExecutor: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // kept on your server, not in the bundle
vmBytecodeArrayEncodingKeyGetter:
'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});
On the server, decide which key to return based on whatever your application trusts — a valid session, an expected
Origin or Referer, a license check, and so on. The twist: instead of rejecting untrusted callers, return a
wrong key. The bytecode then decodes to garbage and the protected code fails on its own, which is stealthier than
an obvious 401 that tells an attacker exactly what to bypass.
// Express example — the exact checks depend on your app
app.get('/api/vm-key', (req, res) => {
const origin = req.get('origin');
const trusted =
req.session?.user && // a valid session, and
origin === 'https://app.example.com'; // the expected production origin
res.type('text/plain').send(
// Real key for valid users; a decoy for everyone else
// (no session, or a localhost / unexpected origin).
trusted ? process.env.VM_KEY : process.env.VM_DECOY_KEY
);
});
Serve the exact same string from this endpoint that you passed as vmBytecodeArrayEncodingKey at build time. A copy of
the bundle running outside your environment gets the decoy key, decrypts to nothing, and is inert.
When the key doesn't match
The obfuscated code only works when the getter returns exactly the same key used during obfuscation. If the keys
differ — or the getter returns undefined, null, or an empty string — decryption produces a wrong keystream and the
code fails at runtime with garbage output or an ordinary runtime error.
There is deliberately no distinct, key-specific error message: a failed key is indistinguishable from any other runtime fault. So when a VM-protected bundle throws only once this option is in play, check the key path first — that the getter resolves on the page, returns a non-empty string, and returns the same value you built with.
