A modern, fully-typed TypeScript reimplementation of
sprintf-js. It is a drop-in
replacement for sprintf-js — same functions, same format specifiers, same
semantics — but rewritten from scratch with:
- Template-literal-typed format strings. The compiler parses your format string and infers argument types, and where possible the return type too.
- ESM-only. No CommonJS, no UMD, no AMD, no browser globals.
- Zero runtime dependencies.
- Modern tooling. Vite, Vitest, oxlint, TypeScript ≥ 5.6, Node.js ≥ 20.
npm install sprintf-typescriptimport { sprintf, vsprintf } from 'sprintf-typescript';
sprintf('Hello, %s!', 'world');
// ⇒ "Hello, world!" (inferred return type: "Hello, world!")
sprintf('%d items at %.2f each', 3, 9.99);
// ⇒ "3 items at 9.99 each"
sprintf('%(user.name)s has %(user.posts.length)d posts', {
user: { name: 'Dolly', posts: [/* … */] },
});
vsprintf('%2$s %3$s a %1$s', ['cracker', 'Polly', 'wants']);
// ⇒ "Polly wants a cracker"If a %d is given a non-number, or a format string is malformed, you get a
TypeError / SyntaxError at runtime — and likely a type error at compile
time too.
%[index$|(name)][flags][width][.precision]specifier
| Specifier | Output |
|---|---|
% |
A literal % |
b |
Binary integer |
c |
Character (from char code) |
d i |
Signed decimal integer |
e |
Scientific notation |
f |
Fixed-point float |
g |
Float (general, uses toPrecision) |
j |
JSON-serialised value (width → indent) |
o |
Unsigned octal |
s |
String (anything coerced via String(...)) |
t |
Boolean ("true" / "false") |
T |
Type name (e.g. "number", "array") |
u |
Unsigned decimal |
v |
Primitive via .valueOf() |
x |
Lowercase hexadecimal |
X |
Uppercase hexadecimal |
| Flag | Meaning |
|---|---|
+ |
Always emit a sign for numeric specifiers |
- |
Left-align within the width |
0 |
Pad numeric output with zeros |
'<char>' |
Pad with <char> (any single character after ') |
sprintf('%(path.to[0].key)s', { path: { to: [{ key: 'hi' }] } });Paths support .key and [index] access, arbitrarily nested.
If an argument is a function, it is called with no arguments and its return
value is used — except for %T and %v, which expect the function value
itself.
The generic overloads of sprintf / vsprintf parse the format string at the
type level, so:
sprintf('%d', 'oops');
// ~~~~~ Argument of type 'string' is not assignable to parameter of type 'number'.
sprintf('%s %s', 'only one');
// ^^^^^^ Expected 3 arguments, but got 2.
const s = sprintf('Hi, %s!', 'world');
// ^? const s: "Hi, world!"For format strings with width/precision/padding, the compile-time result
gracefully falls back to string for that slot while keeping the literal
parts literal.
sprintf itself is a POSIX/C standard library function, and most of the
format grammar here (specifiers, flags, width, precision, %n$ positional
arguments) comes from C. The %(name)s named-argument syntax follows
Python's convention.
This package is a from-scratch reimplementation that aims to be a drop-in
replacement for sprintf-js by
Alexandru Mărășteanu — matching its JS-specific extensions (%j, %T,
%v, %t) and coercion semantics.
MIT — see LICENSE.