Docs
/
Recipes
/

Host-side Template Substitution

Host-side Template Substitution

v6.10.0+

Inject server-rendered values (e.g. Go html/template `{{ .Field }}` expressions) into VM-obfuscated JavaScript without breaking the obfuscation pipeline.

The problem

Your backend (Go, Rails, Django, PHP, …) serves a JavaScript file whose contents must be partially rendered per-request — an API endpoint, a list of feature flags, an initial state blob, a build id, a nonce. You obfuscate the JS with vmObfuscation: true, but you also need the template engine to substitute placeholders in the obfuscated output after obfuscation finishes. If the placeholders are absorbed into VM bytecode (the default for strings and identifiers), the template engine has nothing to substitute.

Which option do I pick?

Server value is…Use
A raw JS expression (array literal, object, number, boolean, function call)reservedNames + identifier placeholder
A string, and you control the templating delimitersreservedStrings + template-literal placeholder
A string, and the template engine forces certain delimiters (e.g. Go {{ .Field }})reservedStrings + string or template-literal placeholder

Pattern 1 — reservedNames with an identifier placeholder

Best for: injecting raw JS expressions (arrays, objects, numbers, …) with zero quote-escaping concerns.

Pick a distinctive identifier that will never collide with real code, reference it directly, and list a regex matching it in reservedNames. Under VM obfuscation, the identifier is routed through the reserved-expressions array and appears verbatim in the output.

Source

(function () {
    var initialState = __INITIAL_STATE__;
    bootstrap(initialState);
})();

Obfuscator options

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedNames: ['^__INITIAL_STATE__$']
});

After obfuscation

Somewhere in the output you will find a reserved-expressions array similar to:

let _r = [__INITIAL_STATE__, /* …other entries… */];

The token __INITIAL_STATE__ survives identifier renaming and is not absorbed into VM bytecode.

Host-side substitution (Go example)

tpl := template.Must(template.New("obf.js").Parse(obfuscated))
tpl.Execute(w, map[string]any{
    "InitialState": map[string]any{"user": "alice", "theme": "dark"},
})

…where your Go template replaces the token with a raw JS expression:

{{ `__INITIAL_STATE__` }} → {{ .InitialState | toJSON }}

Result:

let _r = [{"user":"alice","theme":"dark"}, /* … */];

No quotes, no escaping — the injected value is parsed by the JS engine as an ordinary expression.

Pattern 2 — reservedStrings with a template-literal placeholder

Best for: Go / Jinja-style template engines that require delimiters like {{ .Field }} which happen to be valid JS text when wrapped in backticks.

The placeholder lives inside a single-quasi, non-interpolated template literal. Under VM obfuscation this routes through the reserved-expressions array, preserving the raw backtick form in the output.

Source

(function () {
    var featureFlags = JSON.parse(`{{.Config.FeatureFlags}}`);
    applyFlags(featureFlags);
})();

Obfuscator options

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedStrings: ['\\{\\{\\.[^}]+\\}\\}']
});

After obfuscation

let _r = [`{{.Config.FeatureFlags}}`, /* … */];

The placeholder is preserved byte-for-byte, including the surrounding backticks.

Host-side substitution

Replace {{.Config.FeatureFlags}} with a valid JSON string — it will sit inside backticks at runtime, which do not require inner-quote escaping:

flagsJSON, _ := json.Marshal(config.FeatureFlags) // e.g. {"newCheckout":true,"darkMode":false}
out := strings.ReplaceAll(obfuscated, "{{.Config.FeatureFlags}}", string(flagsJSON))

At runtime: JSON.parse(`{"newCheckout":true,"darkMode":false}`) — works.

Pattern 3 — reservedStrings with a quoted string placeholder

Best for: source code that must stay in ES5 (no template literals), or cases where the surrounding API expects a regular string literal.

The placeholder is a single- or double-quoted string literal. Under VM obfuscation it routes through the reserved-strings array, which is emitted as a JSON.stringify-serialized JS array — always double-quoted regardless of the input quote style.

Source

(function () {
    var tags = JSON.parse("{{.Page.Tags}}");
    renderTags(tags);
})();

Obfuscator options

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedStrings: ['\\{\\{\\.[^}]+\\}\\}']
});

After obfuscation

var _rs = ["{{.Page.Tags}}", /* … */];

Host-side substitution

Because the placeholder sits inside a double-quoted JS string, the injected JSON payload must have its inner " characters escaped with \":

raw, _ := json.Marshal(page.Tags) // e.g. ["news","tech","release"]
// Escape " for embedding inside a JS double-quoted string.
escaped := strings.ReplaceAll(string(raw), `"`, `\"`)
out := strings.ReplaceAll(obfuscated, "{{.Page.Tags}}", escaped)

Resulting output:

var _rs = ["[\"news\",\"tech\",\"release\"]", /* … */];

At runtime: JSON.parse("[\"news\",\"tech\",\"release\"]") ["news","tech","release"].

If you forget the escaping, the browser will see unbalanced quotes and throw a SyntaxError. Pattern 2 sidesteps this entirely by using backticks.

Multiple placeholders in one program

All three patterns compose. A single reservedStrings regex with an alternation can match every placeholder shape your template engine emits:

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedNames: ['^__INITIAL_STATE__$'],
    reservedStrings: ['\\{\\{\\.[^}]+\\}\\}']
});

You can mix identifier placeholders (for raw JS values) and string placeholders (for rendered JSON) in the same source — pick per-placeholder based on what the server will actually inject.

Compatibility notes

  • Don't reuse placeholders across identifiers and strings. A reserved name like __TOKEN__ and a reserved string matching __TOKEN__ describe two different code paths (reserved-expressions array vs. reserved-strings array). Use distinct textual shapes for each — for example, an UPPER_SNAKE convention for identifier placeholders and a delimiter-wrapped shape ({{ ... }}, %{...}, <<<...>>>) for string placeholders. That way a bug in one regex can't silently match the other.
  • reservedStrings regexes run against raw string values. The regex is matched against the runtime value of the string, not the source text. \{\{\.[^}]+\}\} matches strings that contain {{.something}}. If your placeholder might be wrapped in extra content ("prefix-{{.Field}}-suffix"), the regex still matches but the whole string is preserved — plan your substitution accordingly.
  • Works with any templating engine. Although the examples use Go text/templatesyntax, nothing in the javascript-obfuscator integration is Go-specific. Anything that can do a string-level replacement on the obfuscator's output will work: Rails ERB, Django, PHP short-tags, sed in a CI pipeline, etc. Choose delimiters that your engine emits naturally and that don't collide with real JS syntax.