Spintax for JavaScript & TypeScript
The spintax engine now ships as a standalone npm package: @spintax/core. Zero dependencies, MIT-licensed, and framework-agnostic — the same parse / render / validate primitives that power the WordPress plugin, now usable anywhere JavaScript runs.
What it is
@spintax/core is an open-source, framework-agnostic spintax engine for JavaScript and TypeScript. It is a companion to the Spintax WordPress plugin — an independent TypeScript implementation of the same syntax, with zero WordPress dependency. One engine, many surfaces.
- Zero runtime dependencies. Runs unchanged on Cloudflare Workers, Node 18+, and in the browser.
- ESM-first, dual CJS. Ships
.d.tstypes for both module systems. - MIT licensed. The WordPress plugin stays GPL; MIT/Expat is GPL-compatible, so the two coexist cleanly.
- Parity-verified against the PHP plugin by a shared golden corpus — not a promise, a machine-checked gate.
Install
npm install @spintax/core
Quick start
import { render, validate, extract } from '@spintax/core';
render('{Hello|Hi|Hey} %name%!', { context: { name: 'Ada' }, seed: 42 });
// → "Hi Ada!" (deterministic for a given seed; post-processed by default)
validate('{a|b'); // → [{ severity: 'error', code: 'bracket.unclosed', … }]
extract('%title% {?promo?Sale}'); // → { refs: ['title', 'promo'], sets: [], includes: [] }
With a seed, render is fully reproducible. Omit it and every call returns a fresh random pick from the template's space.
The API
A small, sharp core. Every function accepts a raw string or a parsed Ast (from parse), so a consumer can parse once and reuse.
| Function | What it does |
|---|---|
parse(src) | Parse once into an opaque, versioned Ast for reuse — an in-memory perf handle, not a serialization format. |
render(input, opts?) | Render to a single string. Lenient — never throws on malformed markup. Cosmetic post-processing (spacing, capitalization, URL/email shielding) is on by default. |
validate(input, opts?) | Return diagnostics. Valid ⇔ no severity:'error'. An unresolved %var% is a warning, not an error. Locale-aware plural verdicts. |
extract(input) | List variable refs, #set names, and #include targets — powers a two-phase prefetch for async includes. |
analyze(input, opts?) | extract + validate + a best-effort construct census. Stats layer for tooling. |
neutralize(value) | Shield untrusted, data-derived text so it can't be re-interpreted as spintax markup. Text-safe, not HTML escaping. |
render takes context (the variable map), seed, locale (plural buckets, e.g. ru for the 3-form rule), an optional host-injected includeResolver, a postProcess toggle, and a maxDepth guard for nesting and #include.
The same syntax you already know
It is the full spintax surface — the syntax reference applies verbatim.
| Construct | Example | Meaning |
|---|---|---|
| Enumeration | {a|b|c} | pick one (nestable: {a|{b|c}}) |
| Permutation | [a|b|c] | pick N, shuffle, join — configurable separators |
| Variable | %var% | substitute a context value |
| Local set | #set %v% = value | define a variable (one line) |
| Conditional | {?VAR?then|else} | value-driven branch (guide) |
| Plural | {plural %n%: one|few|many} | grammatical agreement by locale (guide) |
| Include | #include "slug-or-id" | embed another template (host-resolved) |
| Comment | /# … #/ | stripped before rendering |
Parity with the WordPress plugin
"Independent implementation, but parity where it counts" is the whole point of the package — and it is enforced, not intended. A shared golden corpus of language-neutral fixtures (template, context, locale, seed) → expected is consumed by both the PHP plugin's test suite and this TypeScript suite.
- Deterministic behavior is parity-gated: validation verdicts, plural buckets, conditional truthiness,
#setcollapse, and the post-process pipeline assert identical output in both engines. - RNG selection is not. Seeded renders are reproducible within an engine; cross-engine random-sequence parity is a deliberate non-goal. You get a valid pick either way, just not the same one.
It is not a line-by-line port of the GPL PHP — it is reimplemented from the behavior contract plus that corpus, which is exactly what keeps it cleanly MIT.
One engine, many surfaces
The package is the shared runtime behind the whole ecosystem — every surface below is a consumer of the same public API, never a private fork:
- The playground on this site (/play/) renders client-side in your browser.
- A Cloudflare Worker reference API — validate and preview-render over HTTP.
- The @spintaxnetbot Telegram bot — paste a template, get validation plus a handful of rendered variations, right in chat.
Because the examples import @spintax/core and nothing else, each one proves the API is usable without polluting it. If it works for the Worker and the bot, it works for your project too.