How VM Transforms Code
Watch
Obfuscator.io: Targeting Functions in VM Obfuscation (IIFE and Comment Mode)
With vmTargetFunctionsMode: 'root' (default), VM obfuscation transforms the body of each root-level function (or, with vmAsyncExecutor, each outermost async function) into bytecode. Root-level functions keep their names; nested ones are renamed as usual. To pick individual functions at any nesting level instead, use comment mode.
This design maintains compatibility with code that calls these functions from the global scope or expects them on the global object. The function body is fully protected, but the name remains visible, and the obfuscator reports a VMGlobalFunctionNamesNotRenamed warning listing such names.
VM obfuscation protects function bodies, plus top-level variable initializers when vmWrapTopLevelInitializers is on (see below). If the input has no function and no such initializer - top-level-only code such as alert(1); - nothing is virtualized: the rest of the obfuscation still runs, but no VM protection is applied, and the obfuscator reports a VMNoFunctionsToVirtualize warning (v7.14.1+; 7.14.0 itself is disabled). It also fires when functions exist but every one was skipped (for example, direct eval or a dynamic new Function, vmTargetFunctions/vmExcludeFunctions leaving nothing selected, or vmAsyncExecutor finding no async function). Wrap the code you want to protect in a function, for example an IIFE.
Top-level initializers
In root mode, vmWrapTopLevelInitializers wraps eligible top-level variable initializers such as const MY_STRING = 'my-string'; in IIFEs so their values move into bytecode instead of staying visible as plain JavaScript. Current VM presets enable it. It covers variable initializers only, not other top-level statements, and it has no effect in comment mode. Initializers that stay in plain JavaScript are listed in a VMTopLevelInitializerNotVirtualized warning.
Hiding sensitive function names
Because root mode keeps the name, a revealing name like validateLicense or decryptData stays readable even after its body becomes bytecode. The fix is to move the function off the top level - wrap it in an IIFE, where it is no longer root-level and gets fully VM-transformed, name included - or to rename root-level identifiers with renameGlobals. See Hiding Function Names from LLM Analysis for both techniques and their trade-offs.
