string-template
string-template is a dependency-free CommonJS formatter that replaces ASCII named or numeric placeholders such as {name} and {0}. Values can come from one object or array, or from positional function arguments; missing, null, and undefined values become empty strings, and doubled braces preserve a literal placeholder. A separate compile subpath can pre-parse frequently reused templates, with an optional new Function mode. It is interpolation only, not a full template language.
Do not add it to new modern code for ordinary interpolation; a template literal or a maintained formatter is easier to type and audit. It remains acceptable as a frozen legacy formatter when you know its flat-key, missing-value, object-shape, and escaping behavior exactly.
Use it if
- You maintain code that already uses its {name} or {0} syntax and depends on missing values becoming empty strings
- You need an ES5-era formatter that runs in very old browsers and has no runtime dependencies
- Your templates are trusted, tiny, context-neutral messages with flat ASCII keys and no conditionals or nesting
- You reuse a hot template enough to benefit from the default pre-parsed compile function without enabling generated code
- You are writing modern JavaScript with values already in scope: native template literals are clearer, typed by the language, and require no package
- You are producing HTML, SQL, shell commands, URLs, regular expressions, or another escaped format: the formatter performs raw string coercion and no context-aware escaping
- You need nested properties, loops, conditions, pluralization, defaults, format specifiers, or internationalization; the grammar recognizes only flat ASCII letters, digits, and, in the direct formatter, underscores
- You need TypeScript declarations, ESM exports, or current maintenance: version 1.0.0 ships none of those and both the npm release and last repository push predate 2018
- Your data objects can have a null prototype or an own hasOwnProperty key: direct source inspection shows null-prototype objects are discarded and a shadowed hasOwnProperty value can throw a TypeError
Setup reality
npm install string-template adds a CommonJS package with no dependencies, peers, native build, credentials, config, engines constraint, ESM export map, or TypeScript declarations. require('string-template') gives the direct formatter. require('string-template/compile') reaches an undeclared subpath because the old package has no exports restrictions. The argument convention has a subtle branch: exactly one value argument is treated as a lookup object only when typeof is object; otherwise all remaining arguments become numeric positions. Arrays work as the object form. Missing own properties and null or undefined values silently become empty strings, which is convenient for display but can hide misspelled keys and produce dangerous partial commands or queries. Zero and false are preserved. Keys are not property paths and must be ASCII alphanumerics; the direct formatter also accepts underscore, but compile's regular expression does not, so {first_name} works directly and remains literal in compiled templates. Both implementations call the object's own hasOwnProperty method rather than Object.prototype.hasOwnProperty.call. Object.create(null) data therefore produces blanks, while an own non-function hasOwnProperty property crashes. Escaping only means doubled braces such as {{0}} becoming literal {0}; it does not sanitize substituted values. The normal compiler pre-tokenizes without code generation and is the safer choice. Passing true as compile's second argument constructs a function with new Function, which violates strict Content Security Policy without unsafe-eval and can be blocked in locked-down runtimes. The README calls the package unstable, and its badges and browser matrix describe an obsolete CI era rather than current runtime testing.
Patterns
Replace named placeholdersformat-named-values
const format = require('string-template');
const message = format('Hello {name}, you have {count} messages', {
name: 'Robert',
count: 12,
});Only own properties are substituted. Values are coerced to strings without escaping, so keep this to context-neutral text or escape values first.
Replace numeric placeholders from an arrayformat-indexed-array
const message = format(
'Hello {0}, you have {1} messages',
['Robert', 12]
);An array works because exactly one object-typed argument is used as the lookup table. Numeric placeholders are property names such as 0 and 1.
Pass positional values as separate argumentsformat-positional-arguments
const message = format(
'Hello {0}, you have {1} messages',
'Robert',
12
);With more than one value argument, the function builds an internal array and resolves {0}, {1}, and later positions from it.
Keep a placeholder literalescape-placeholder-braces
const example = format('Write {{name}} or {{0}} in the config');
console.log(example);
// Write {name} or {0} in the configDouble braces escape only recognized placeholder-shaped text. This is syntax escaping, not HTML, shell, SQL, or URL escaping.
Observe empty output for absent valueshandle-missing-values
const result = format('{first} {middle} {last}', {
first: 'Ada',
last: 'Lovelace',
});
console.log(result); // Ada LovelaceMissing, null, and undefined values all become empty strings without an error. Validate required data before formatting when omissions matter.
Keep valid falsy valuespreserve-zero-and-false
const result = format('count={count}, enabled={enabled}', {
count: 0,
enabled: false,
});
console.log(result); // count=0, enabled=falseOnly null and undefined disappear. Zero, false, NaN, objects, and arrays are passed through JavaScript string coercion.
Use underscore keys only with the direct formatteruse-underscore-key
const result = format('Hello {first_name}', {
first_name: 'Ada',
});
console.log(result); // Hello AdaThe direct regex accepts underscores, but string-template/compile accepts only letters and digits. Compiling this exact template leaves {first_name} unchanged.
Pre-parse a frequently reused templatecompile-reused-template
const compile = require('string-template/compile');
const greet = compile('Hello {0}, you have {1} messages');
console.log(greet('Ada', 3));
console.log(greet(['Grace', 7]));The default compile mode pre-tokenizes and does not use generated code. It accepts one object or array, or separate positional arguments.
Opt into generated-code compilationcompile-inline-function
const compile = require('string-template/compile');
const greet = compile('Hello {0}', true);
console.log(greet('Ada'));The true flag uses new Function. It can violate Content Security Policy and locked-down runtime rules; benchmark before accepting that deployment and audit cost.
Convert a null-prototype map before formattingnormalize-null-prototype-data
const raw = Object.assign(Object.create(null), { name: 'Ada' });
const values = Object.fromEntries(
Object.entries(raw).filter(([key]) => key !== 'hasOwnProperty')
);
console.log(format('Hello {name}', values));A null-prototype object is treated as empty, and an own hasOwnProperty value shadows the method the formatter calls. Normalize and reject that reserved key.
Escape values before inserting into HTMLescape-html-values
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
const html = format('<p>Hello {name}</p>', {
name: escapeHtml(userName),
});string-template never escapes output. This helper is only for HTML text and quoted attributes; other contexts need their own encoder, and DOM APIs are preferable when available.
Fail before silent placeholders disappearassert-template-keys
function strictFormat(template, values) {
const keys = [...template.matchAll(/\{([0-9A-Za-z_]+)\}/g)]
.map((match) => match[1]);
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(values, key)) {
throw new Error(`Missing template value: ${key}`);
}
}
return format(template, values);
}The package intentionally turns absent keys into empty strings. A strict wrapper is useful for configuration, notifications, and any message where silent truncation is a bug.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sprintf-js | npm | Use familiar positional or named format specifiers when widths, precision, numeric bases, and explicit formatting matter |
| mustache | npm | Use escaped HTML interpolation, sections, inverted sections, and nested lookup with a logic-less template format |
| handlebars | npm | Use helpers, partials, conditions, loops, and HTML escaping for larger presentation templates |
| lodash | npm | Use its template function only in an existing Lodash codebase that already accepts code-generation and escaping configuration |