npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spintax/core

v0.14.0

Published

Framework-agnostic spintax / text-spinning engine for JS & TS — turn one LLM-drafted {a|b|c} template into endless deterministic variations. Render, validate, extract. Zero-dependency; Node, Workers, browser. Also available as an n8n community node (n8n-n

Readme

@spintax/core

npm CI types license

Framework-agnostic spintax engine for JavaScript / TypeScript — parse, render, validate, extract, analyze, and neutralize spintax templates.

Pairs naturally with LLMs. Have a model draft a spintax template once, then generate unlimited variations on-device — deterministic, free, and offline, with no per-generation API calls. The LLM handles creativity; the engine handles scale.

  • Zero runtime dependencies. Runs unchanged on Cloudflare Workers, Node 18+, and in the browser.
  • ESM-first, dual CJS. Ships .d.ts types for both.
  • One syntax contract, six independent engines. This package, spintax/core (PHP), spintax-core (Python), an Object Pascal engine, Spintax.Core (.NET), and the Spintax WordPress plugin are all held to the same behavior contract by a shared golden corpus of 486 fixtures — deterministic verdicts, plural buckets, conditionals, #set/#def semantics and post-processing agree everywhere, so a template is an asset you can move between runtimes, not a lock-in.
  • MIT licensed.

Status: released & stable. Feature-complete — parse / render / validate / extract / analyze / neutralize — with the public API proven by real consumers: a reference Cloudflare Worker, the @spintaxnetbot Telegram bot, the spintax.net playground, and the n8n community node.

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: [] }

Use cases

Anywhere one message has to go out many times without reading like a form letter:

  • Cold email & outreach — one template, a distinct body per recipient, personalized through context.
  • SMS / push / notifications — deterministic output in a few kilobytes, no API call per send.
  • Chatbots & agents — vary canned replies so a bot doesn't repeat itself verbatim.
  • A/B and multivariate testing — enumerate copy variants in the template instead of in application code.
  • Programmatic SEO / content generation — thousands of pages from one authored source.
  • LLM output at scale — have a model draft the template once, then spin it locally, forever, for free.

The core renders a single string per call — batching is a host concern. To emit N variants, call render N times with different seeds; a seeded call is reproducible, so any variant can be regenerated later from its seed alone:

const template = '{Hi|Hello|Hey} %name%, {quick|short|small} question about {pricing|billing}';
const variants = [1, 2, 3].map((seed) => render(template, { context: { name: 'Ada' }, seed }));
// → [ 'Hello Ada, quick question about billing',
//     'Hey Ada, quick question about pricing',
//     'Hey Ada, quick question about pricing' ]   ← same as seed 2; see the caveat below

Distinct seeds are independent draws, not distinct results — like any sampling they can repeat, and the fewer combinations a template has, the more often they will. If you need N unique variants, dedupe in the host and cap the retries: a template may simply not have N combinations to give.

Spintax syntax

| Construct | Example | Meaning | | --- | --- | --- | | Enumeration | {a\|b\|c} | pick one (nestable: {a\|{b\|c}}) | | Permutation | [a\|b\|c] | pick N, shuffle, join — [<minsize=1;maxsize=2;sep=", ">a\|b\|c] | | Variable | %var% | substitute a context value — inside {…}/[…] the value is spliced as text first, so a | it carries separates options | | Local set | #set %v% = value | define a macro — re-picked at every use | | Local def | #def %v% = value | define a value — picked once per render, held at every use | | Conditional | {?VAR?then\|else} | then if VAR is truthy, else else — inside {…}/[…] it resolves first, so a \| in the taken branch separates options and an empty one drops its element | | Plural | {plural %n%: one\|few\|many} | grammatical agreement by locale — 2 forms (en), 3 (ru/uk/be, sr/hr/bs), 6 (ar: zero|one|two|few|many|other) | | Include | #include "slug-or-id" | embed another template (host-resolved) | | Comment | /# … #/ | stripped before rendering | | Verbatim | /! … !/ | emitted exactly as written — no spintax inside, no post-process: /![Docs](https://spintax.net)!/, /!#file[D:\kw.txt,1,S]!/. The first !/ closes it; an unclosed /! is plain text and a verbatim.unclosed warning |

API

All functions accept a string or a parsed Ast (from parse) as their first argument.

render(input, options?): string

Renders a template to a single string. Lenient — never throws on malformed markup (a bad block is emitted verbatim with fullwidth braces {…}). Cosmetic post-processing (spacing, capitalization, URL/email shielding) is on by default.

render(input, {
  context?:         Record<string, string>,   // variable map
  seed?:            number | string,           // deterministic RNG; omit ⇒ random
  locale?:          string,                     // plural buckets: 'ru' 3-form, 'ar' 6-form
  includeResolver?: (ref: string) => string | null,  // host-injected, synchronous
  postProcess?:     boolean,                    // default true; false ⇒ raw pick
  maxDepth?:        number,                      // #include / nesting guard (default 20)
});

#include is resolved only when you pass an includeResolver; child templates inherit the runtime context but not the parent's #set locals. Circular / too-deep includes resolve to '' (lenient).

validate(input, options?): Diagnostic[]

Returns diagnostics. A template is valid ⇔ no diagnostic has severity: 'error'. An unresolved %var% is a warning, not an error.

validate(input, {
  locale?:         string,             // locale-aware plural-arity verdicts
  knownIncludes?:  readonly string[],  // enables "unknown #include target" errors
  knownVariables?: readonly string[],  // suppresses undefined-variable warnings
});
// Diagnostic: { severity, code, message, line, column, endLine?, endColumn?, data? }

extract(input): { refs, sets, includes }

Variable references (%var%, {?…}, plural counts), #set names, and #include targets.

analyze(input, options?): Analysis

extract + validate + a best-effort constructs census ({ enumeration, permutation, variable, conditional, plural, set, include }). The census counts author-visible constructs; it is not a variant-cardinality promise.

parse(input): Ast

Parses once for reuse. Ast is opaque and versioned — pass it back to the other functions, do not introspect or persist it across engine versions.

neutralize(value): string

Shields data-derived (untrusted) text so it can't be re-interpreted as spintax markup — use it on any value you inject via context that isn't author-controlled. It is text-safe (round-trips to literal glyphs in any sink), not HTML/XSS escaping.

render('%bio%', { context: { bio: neutralize('Save {50|60}% today') }, postProcess: false });
// → "Save {50|60}% today"   (the braces stay literal, not a random pick)

The pipe is deliberately not shielded: it means nothing outside a construct, and inside one it is the separator. A neutralized value an author places in {…}/[…] is still split on its |, in this engine as in every other in the family. Keep such a value out of a construct if it must stay one option.

Using it without writing code

  • n8n — install n8n-nodes-spintax (Settings → Community Nodes). One node, five operations: Render, Render Many, a two-output Validate, and the LLM authoring/repair prompt builders — a Sheets-to-outbox personalization flow or a full AI authoring loop with no code and no credentials on the node's side.
  • Telegram — @spintaxnetbot: validate, preview, and AI-draft templates in chat.
  • Browser — the spintax.net playground: live validation and N-variant rendering, entirely client-side (it runs this very package).
  • Windows — Spintax Studio, a native editor in the Microsoft Store (renders via the family's Object Pascal engine; same corpus).

Determinism & RNG

With a seed, render is reproducible within this engine. Cross-engine RNG-sequence parity with the PHP plugin is a non-goal — only the deterministic behavior (validation verdicts, plural buckets, conditional truthiness, #set/#def semantics, post-process output) is parity-gated.

Links

License

MIT. The Spintax WordPress plugin remains GPL; MIT/Expat is GPL-compatible.


Part of the 301.st toolset. Product home: spintax.net.