# Spintax documentation > The full text of the spintax syntax reference and the authoring guides, from https://spintax.net. > Product pages, history and pricing articles are not included; they are listed in > https://spintax.net/llms.txt. --- Source: https://spintax.net/what-is-spintax/ # What is Spintax? Spintax (spin syntax) is a template language for turning one source into many genuinely unique variants of a text. You — or an AI — write the variation logic once; the engine renders thousands of distinct, deterministic outputs locally, at near-zero cost. ## The Basic Idea A spintax template is ordinary text with embedded variation markers. Each time it is rendered, the engine picks one option from each marker and produces a distinct output — but every choice is one you defined, so the result stays correct and on-brand rather than a probabilistic guess. The simplest example uses curly braces and pipes: ``` {Hello|Hi|Hey} {world|everyone|there} ``` This single line produces 9 possible outputs: "Hello world", "Hi everyone", "Hey there", and so on. A real template with dozens of variation points can generate thousands or millions of unique variants from one source. ## Why not just let AI write every page? You could call a language model on every page, but that has three problems: it costs money on every request, the quality drifts, and — most importantly — the output is near-identical and easy to spot. Search engines increasingly flag low-effort, template-shaped AI text, and near-duplicate pages compete with each other and invite duplicate-content penalties. Spintax takes the opposite path. You author the variation once and render locally: no per-page API call, deterministic output you can review, and — because every variant is a different combination you defined — content that is genuinely unique by construction. It stays distinct in search rather than reading as spun-out filler. ## Beyond Simple Spinning Modern spintax goes far beyond `{a|b|c}`. The full syntax includes: - **Enumerations** `{a|b|c}` — pick one option, with arbitrary nesting depth - **Permutations** `[a|b|c]` — pick N elements, shuffle, join with configurable separators - **Variables** `%name%` — reusable values defined with `#set` / `#def` or passed at render time - **Conditionals** `{?VAR?then|else}` — value-driven branching, not a coin flip - **Plural agreement** `{plural N: one|few|many}` — the correct noun form by count, locale-aware - **Includes** `#include "slug"` — embed one template inside another - **Comments** `/#...#/` — author notes stripped from output - **Post-processing** — automatic capitalization, spacing, and punctuation correction The full specification is in the [syntax reference](https://spintax.net/docs/syntax.md). ## Spintax + AI: The Modern Workflow Content teams already use AI as their default writing interface. The question is what happens after the AI writes. The workflow is reverse-authored: you don't write every variant by hand. You write the final text once and add the variation markup last, so the engine executes your intent instead of guessing at it — reverse for the source, diverse for the output. That is how you [create diverse content](https://spintax.net/docs/authoring-mindset.md) from a single, reviewable template. So spintax inverts the usual AI model: 1. Use AI to **create a template once** — a structured document with variation logic 2. **Validate** it — syntax checking, preview, human review 3. **Render on-site** — each output is a local string operation: no API call, no cost, no latency The output is deterministic and depends only on the quality of the template, so a single asset captures your brand voice and variation strategy — and generates unique content safely and cheaply forever. ## Use Cases - **Content at scale** — product descriptions, landing pages, FAQ blocks, and meta descriptions across many pages, each genuinely different - **Localization** — locale-specific variants from one template, with variables and locale-aware plurals - **Multi-site & agency** — reusable template packs; unique content per site, without the duplicate-content penalty near-identical pages invite - **Email & messaging** — subject-line and body variations for testing and personalization - **QA & test data** — varied, realistic content for staging environments ## Getting Started Read the [syntax reference](https://spintax.net/docs/syntax.md) to learn the markup, or try it live in the [playground](https://spintax.net/play/) — no install. To render in your own app, pick an engine by runtime: `@spintax/core` (JavaScript), `spintax/core` (PHP), `spintax-core` (Python), or the Free Pascal engine — all open-source, zero-dependency, MIT, and held to one shared corpus. WordPress users get the [free plugin](https://wordpress.org/plugins/spintax/) with an editor, caching, and field bindings. See [all four engines](https://spintax.net/spintax-engines.md). --- [Open in playground](https://spintax.net/play/) [Syntax Reference](https://spintax.net/docs/syntax.md) [All Docs](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/syntax # Spintax Syntax Reference Complete reference for spintax template markup. ## Enumerations `{ }` Randomly selects **one** option from the list. ``` {option1|option2|option3} ``` ### Examples ``` {blue|grey|clear} {|free|paid} plan ← empty option = sometimes nothing {Acme {Pro|Lite}} ← nested enumerations {order {|#42-A} confirmed} ← nesting with empty option ``` ### Rules - Delimiters: `{` and `}` - Separator: `|` - Supports nesting to arbitrary depth - Empty options are valid (produce empty string) - Resolution is from the innermost expression outward ## Permutations `[ ]` Selects N elements, shuffles them, and joins with separators. ### Simple permutations All elements included, space-separated: ``` [1|2|3|4] ``` Output examples: `1 4 3 2`, `2 3 4 1`, `3 2 4 1` ### With separator Uniform separator specified in `< >` at the start: ``` [<, > 1|2|3|4] ``` Output examples: `2, 1, 4, 3`, `4, 3, 2, 1` **Important:** No space between `[` and ``. ### Per-element separators Each option can have its own separator defined with `` before the preceding `|`. The separator travels with its element during shuffle. ``` [<, > 1|2|3 < and >|4] ``` Output examples: `1, 3, 2 and 4`, `3, 1, 2 and 4` **Auto-spacing:** Word separators like `` or `` are automatically padded with spaces, so `` produces `and` . Punctuation separators (`<,>`) are not padded. ### Permutations with combinations Configurable min/max element count and separators: ``` [ apple|plum|orange|apricot] ``` Output examples: `apple, plum and orange`, `apple and apricot`, `orange` ### Configuration parameters | Parameter | Default | Description | | --- | --- | --- | | `minsize` | count of all | Minimum number of elements to pick | | `maxsize` | count of all | Maximum number of elements to pick | | `sep` | `" "` (space) | Separator between non-final items | | `lastsep` | same as `sep` | Separator before the last element | ### Permutation rules - Delimiters: `[` and `]` - Config block `<...>` must immediately follow `[` - Config parameters are semicolon-separated - String values in config are quoted: `sep=", "` - Enumerations and permutations can be nested inside options - HTML elements can be options ## Variables `%var%` Defines a reusable variable that is substituted wherever it appears. ``` #set %VARIABLE_NAME% = value or spintax structure #def %VARIABLE_NAME% = value or spintax structure ``` ### Examples ``` #set %name% = John #set %greeting% = {Hello|Hi|Hey} #set %items% = [ apples|oranges|bananas] Some text with %name% and %greeting%, also %items%. /# %greeting% above may differ between the two references — #set re-rolls. #def picks once and keeps it: #/ #def %tone% = {friendly|warm|upbeat} A %tone% intro, and a %tone% outro — always the same word. ``` ### Variable rules - `#set` and `#def` must start at the beginning of a line - Variable names are enclosed in `%`: `%name%` - Variable names are alphanumeric + underscore - Values can contain any spintax syntax (enumerations, permutations, other variables) - `#set` variables are expanded when referenced, not when defined (lazy evaluation) - `#set` is a macro: its value is re-substituted — and any spintax inside it re-rolled — at every reference. `#def` resolves its value once per render and holds that result everywhere - `#set` and `#def` lines are stripped from output ### Variable scopes in the WordPress plugin The plugin supports three variable scopes. When the same name exists in multiple scopes, the strongest scope wins: 1. **Runtime variables** (strongest) — passed via shortcode: `[spintax slug="greeting" name="Alice"]` 2. **Local variables** — defined with `#set` or `#def` inside the template 3. **Global variables** (weakest) — defined on the Settings page ## Conditionals `{?VAR?then|else}` Conditionals are **spintax.net's distinctive extension** to the GTW family. Where `{a|b}` is a uniform random pick that ignores variables, `{?VAR?then|else}` picks based on whether `%VAR%` has a value. Use it for value-driven choices — show a free-tier line only when a free tier exists, render a pro-features block only when the user is on a paid plan, hide a CTA that does not apply. The pre-pass runs before `%var%` expansion and before the random branch picker, so a falsy branch is fully discarded — nothing inside it is evaluated. ### Forms ``` {?VAR?then} ← truthy ⇒ then; falsy ⇒ empty {?VAR?then|else} ← truthy ⇒ then; falsy ⇒ else {?!VAR?then|else} ← inverted ``` ``` {?HasFreeTier? — free tier available since %founded%|, trusted since %founded%} ``` ### Truthy and falsy The rule is deliberately simpler than JavaScript — **truthy = at least one non-whitespace character**: | Value of `%VAR%` | Truthy? | | --- | --- | | not declared | falsy | | empty string | falsy | | whitespace only | falsy | | `"0"`, `"false"` | truthy (non-empty) | | any other text or HTML | truthy | ### Conditional rules - Variable names follow the same regex as `%var%` (case-insensitive) - The `!` prefix inverts the check: `{?!VAR?missing}` - The first depth-0 `|` separates `then` from `else`; later top-level `|` stays literal in `else` - Nested conditionals evaluate outer-first — falsy branches short-circuit - Composite logic (`&&`, `||`, comparisons) is not supported — pre-compute a guard variable in the assembler - Malformed forms (`{??yes}`, `{?VAR}`) never throw — the playground flags them as warnings - **Deep dive:** see the [Conditional spintax guide](https://spintax.net/docs/conditional-spintax.md) for worked examples and anti-patterns ## Plurals `{plural %n%: one|many}` Picks the grammatically correct word form for a number. The count goes before the colon, the forms after it, separated by `|`. The form is chosen by the **render locale**, not by the template — so how many forms you must supply depends on that locale. English needs two, Russian needs three. ``` {plural %n%: form1|form2} ← 2-form locale (en, de, es…) {plural %n%: form1|form2|form3} ← 3-form locale (ru, uk, sr…) ``` ``` #def %LangCount% = 5 supports %LangCount% {plural %LangCount%: language|languages} ← supports 5 languages ``` ### Forms per locale The locale is matched on its language subtag, so `ru-RU` and `ru` behave identically: | Locale | Forms | Selected by | | --- | --- | --- | | `ru`, `uk`, `be`, `sr`, `hr`, `bs` | 3 | 1 · 2–4 · 5 and up | | every other locale, incl. `en` | 2 | exactly 1 · everything else | Supply the wrong number of forms and the engine reports `plural.arity` and leaves the block visible with fullwidth braces — a silent wrong plural never ships. ### Plural rules - The opener is literal, including the space: `{plural` . `{plural: x}` and `{pluralN: x}` are not plural blocks - The colon is mandatory — it separates the count from the forms - The count is a `%Var%` reference or a literal integer; variables in the count are substituted before the form is chosen - Negative counts use the absolute value; `0` takes the "everything else" form - **A count variable must be `#def`, not `#set`** — `#set` is a macro, so a value like `{1|4|9}` is still unresolved spintax when the plural is decided and the whole block renders empty. The playground flags this as `plural.count-macro` - A non-numeric or undefined count erases the block rather than guessing - **Deep dive:** see the [Plural spintax guide](https://spintax.net/docs/plural-spintax.md) for the Russian 3-form rules and worked examples ## Includes `#include` Embeds another template at the directive's position. ``` #include "hero-text" ``` ### Include rules - Template reference is in double quotes - Resolves by template slug or numeric ID - Included templates can contain their own variables and spintax - Recursive includes are supported - Circular references are detected and blocked - Child templates inherit global and runtime variables but **not** parent's `#set` / `#def` locals ## Comments `/#...#/` Text between comment markers is stripped from the output before any other processing. ``` /# This is a comment section. It can span multiple lines. It won't appear in output. #/ ``` ### Comment rules - Start delimiter: `/#` - End delimiter: `#/` - Can span multiple lines - Cannot be nested - Removed before any other processing ## Nesting All syntax elements can be nested within each other to arbitrary depth: ``` {option1|[<, > sub1|sub2|sub3]|option3} [ {red|blue} apples|{big|small} oranges|bananas] #set %var% = {a|[b|c]} ``` ## Post-Processing The engine applies automatic text correction after generation: 1. Shield URLs, emails, domains, decimals, and abbreviations from capitalization 2. Collapse duplicate spaces and tabs 3. Remove whitespace before punctuation (`,` `.` `!` `?`) 4. Add space after punctuation where missing 5. Capitalize first letter of the output (skipping HTML tags) 6. Capitalize after sentence-ending punctuation 7. Capitalize after block-level HTML tags 8. Capitalize after line breaks 9. Restore shielded placeholders ## Syntax Summary | Feature | Syntax | Behavior | | --- | --- | --- | | Enumeration | `{a|b|c}` | Pick one random option | | Permutation | `[a|b|c]` | Pick N, shuffle, join | | Separator | `[ a|b|c]` | Permutation with uniform separator | | Per-element sep | `[<,> a|b |c]` | Permutation with custom separators | | Combinations | `[ a|b|c]` | Permutation with min/max count | | Variable | `#set %var% = val` | Reusable substitution | | Variable (roll once) | `#def %var% = val` | Resolved once per render | | Conditional | `{?VAR?then|else}` | Render `then` if truthy, `else` if falsy | | Plural | `{plural %n%: one|many}` | Agree the word form with the number, by locale | | Include | `#include "slug"` | Embed another template | | Comment | `/#...#/` | Stripped from output | The syntax is compatible with the [Generating The Web](https://spintax.net/spintax-editor.md) (GTW) standard. --- [Open in playground](https://spintax.net/play/) [All Docs](https://spintax.net/docs.md) [GTW History](https://spintax.net/spintax-editor.md#gtw) [GitHub](https://github.com/investblog/spintax) --- Source: https://spintax.net/docs/nested-spintax/ # Nested Spintax How nesting transforms flat spinning into a powerful template engine — and why the Spintax.Net approach is the standard. ## What is Nested Spintax? Regular spintax picks a random option from a list: `{red|blue|green}` produces one of three colors. **Nested spintax** places spintax inside spintax — like a matryoshka doll, where each layer reveals more variation inside. When the engine encounters nested structures, it resolves from the **innermost expression outward**. The inner braces are evaluated first, and their result becomes part of the outer expression. ``` {red|{dark|light} blue} car ``` Here `{dark|light}` resolves first (e.g. `dark`), producing `{red|dark blue}`. Then the outer enumeration picks one option: `red` or `dark blue`. ## Why Nesting Matters Without nesting, your options are flat. Three enumerations with three options each give you 3 + 3 + 3 = 9 fragments. **With nesting**, the same elements combine multiplicatively: 3 × 3 × 3 = **27 unique variants** from a single compact template. This exponential growth is the key to generating truly unique content. A moderately complex template with nested enumerations and permutations can produce **thousands or millions** of distinct outputs — all from one carefully crafted source. ## Common Limitations of Other Tools Most spintax tools handle only the basics. Here is what typically goes wrong: - **Enum-in-enum only** — they support `{a|{b|c}}` but nothing else. No permutations, no variables, no includes inside other elements. - **Depth limits** — many parsers break after 2–3 levels of nesting, silently producing corrupted output. - **No cleanup** — after resolving nested structures, spacing collapses, punctuation doubles, capitalization breaks. The result needs manual editing. - **No composability** — without variables and includes, every template is an island. Reusing common blocks means copy-pasting. ## The Spintax.Net Approach Spintax.Net implements nesting as a first-class feature, not an afterthought. Five design decisions make it work: 1. **Arbitrary depth** — there is no nesting limit. Ten levels deep works the same as two. 2. **Cross-element nesting** — enumerations inside permutations, permutations inside enumerations, variables containing nested structures, includes embedding entire nested templates. Any element inside any other. 3. **Innermost-first resolution** — the engine always resolves from the deepest level outward. This makes evaluation predictable and debuggable. 4. **Smart post-processing** — after all nesting is resolved, the engine automatically fixes capitalization, collapses duplicate spaces, corrects punctuation spacing, and handles sentence boundaries. The output is clean text, not raw concatenation. 5. **Safety** — circular reference detection for `#include` prevents infinite loops. Variable scope rules (runtime > local > global) prevent accidental overwrites. ## From Simple to Advanced ### 1\. Enum inside enum ``` {{premium|luxury} sedan|{compact|mid-size} SUV} ``` Inner enumerations resolve first, then the outer one picks a result. Possible outputs: `premium sedan`, `luxury sedan`, `compact SUV`, `mid-size SUV`. ### 2\. Enum inside permutation ``` [ {red|blue} apples|{big|small} oranges|bananas] ``` Each permutation element contains its own enumeration. The engine resolves inner enumerations first, then shuffles and joins. Example output: `blue apples, bananas and small oranges`. ### 3\. `#set` variables with nested spintax ``` #set %product% = {{premium|budget} {laptop|tablet}|{smart|classic} phone} #set %action% = {Buy|Get|Order} %action% your new %product% today! ``` `#set` is a macro: it stores the nested spintax unresolved and re-rolls it at _every_ reference, so two `%product%` mentions may render two different products. Combined with multiple variables, the variant count multiplies rapidly. ### 4\. `#def` — roll once, hold everywhere ``` #def %brand% = {{Nord|Prime} Tools|Acme {Labs|Works}} %brand% ships today. Order %brand% now! ``` `#def` resolves its nested spintax **once per render** and holds that result at every reference — here both mentions name the same brand. Use `#def` when repeated references must agree (brand names, counts); use `#set` when every reference should vary on its own. ### 5\. Conditionals around nested blocks ``` {?PLAN?{Upgrade to|Unlock} {Pro|Premium} features|{Try|Start with} the free tier} ``` The `{?VAR?then|else}` pre-pass picks a branch by whether `%PLAN%` has a value — before any random picks run. The losing branch is discarded whole: nothing inside it is evaluated or rolled. ### 6\. Plurals with a nested count ``` #def %n% = {2|5|21} Renders %n% {plural %n%: variant|variants} per click. ``` The count is itself spintax — and its variable must be `#def`, not `#set`: the form is chosen after the count resolves, and a macro would still be unresolved spintax at that moment. The render locale decides how many forms you supply: two in English, three in Russian or Ukrainian. ### 7\. Includes with nesting ``` /# main template #/ #include "hero-text" {Check out|Discover|Explore} our [<, > features|plans|pricing]. ``` The included template can itself contain enumerations, permutations, variables, and even further includes. Circular reference detection keeps everything safe. ## AI + Nested Spintax Large language models are excellent at writing complex nested templates. A single prompt can produce a template with multiple nesting levels, value-driven conditional blocks (`{?VAR?…}`), locale-aware plurals, and reusable sections via includes. The workflow is simple: **use AI to create the template once, then use Spintax to generate unique variants cheaply forever**. One API call to create the template. Zero API calls to generate each variant. Nested spintax is what makes this economically viable — the deeper the nesting, the more unique outputs per template. ## Getting Started Ready to use nested spintax in your projects? Try it live in the [playground](https://spintax.net/play/) — paste any example above and watch it resolve. The [syntax reference](https://spintax.net/docs/syntax.md) has the complete specification, and when you are ready to render in production, pick one of the [four open-source engines](https://spintax.net/spintax-engines.md) — or the [WordPress plugin](https://github.com/investblog/spintax) if that is your runtime. --- [Open in playground](https://spintax.net/play/) [Syntax Reference](https://spintax.net/docs/syntax.md) [All Docs](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/authoring-mindset/ # Reverse authoring: write the text first, mark it up last Most authors start with `{a|b|c}` fragments, hoping a stack of synonyms adds up to an article. That approach falls apart the moment grammar, tenant-specific facts, or real narrative flow get involved. The fix is a simple shift in direction. ## The mental model The parser and the author work in **opposite directions**. - The parser expands syntax from the inside out. Inner enumerations resolve first, then outer ones, then permutations, then variable substitution. - The author designs the template from the _resolved_ readable phrase _back_ into syntax. You are not generating variants. You are constraining one clean article into something the engine can safely regenerate a thousand times without breaking grammar. That is the whole shift: ``` final readable phrase → bind grammar → choose safe branch boundaries → extract variables → add structural variation → add local synonyms last ``` Synonymization is the last refinement, not the first step. ### Use this guide as an AI prompt You can read this page as a human author, or you can feed it to Claude, GPT, or Gemini as a prompt. That is the whole premise of Spintax: **write one template with AI, render it safely and cheaply forever.** **How to use it that way:** 1. Paste this whole guide into your chat. 2. Below it, paste your draft article. 3. Ask: _"Convert my article into a spintax template. Follow the rules above. Readability wins over variety."_ Every rule below is phrased so that a model can apply it directly. The anti-patterns list doubles as a checklist for the model to self-review its output. Working through an agent instead of a chat window? Everything here is machine-ready: this page as clean Markdown at [/docs/authoring-mindset.md](https://spintax.net/docs/authoring-mindset.md), the whole series condensed into the [spintax-authoring skill](https://spintax.net/.well-known/agent-skills/spintax-authoring/SKILL.md), and an MCP server at `https://spintax.net/mcp` whose `validate_spintax` tool lets the model check its own template before you ever see it. The full setup, with a ready prompt, is in [writing spintax templates with AI](https://spintax.net/ai-spintax-templates.md). ## The five-step reverse workflow 1. **Write the final sentence in plain language.** No curly braces, no square brackets, no variables. Just the sentence you want a reader to see. 2. **Mark grammar that must stay bound.** Subject + verb. Preposition + object. Article + noun phrase. Noun + adjective agreement. These cannot be split by a branch boundary. 3. **Choose safe branch boundaries.** A branch boundary goes _between_ grammatical units, never inside one. 4. **Extract repeated or tenant-specific facts into variables.** Anything that changes per site, per product, or per article moves to `%VariableName%`. 5. **Add structural variation first, local synonyms last.** Permutations and optional fragments create real variety. Synonyms are a small, final refinement inside already-safe grammar slots. The practical test before every `{a|b}` you write: > _If I swap only this fragment, do case, agreement, governance, articles, and word order all remain correct?_ If the answer is not an immediate yes, bind a larger phrase in one branch. ## Phase 0 — Write a readable article first The source text is an article first and a spintax input second. Readability always wins over template convenience. ### Step 1. Write a normal article Write a clean, readable article as if no templatization will happen. Include introductions, transitions, context, explanations — everything a good article needs. Your voice. Your hooks. Real flow between sections. Do not optimize for spintax at this stage. You are writing for one human reader. The template comes after. ### Step 2. Identify templatizable zones Not every paragraph needs to be variable. Walk through the finished text and mark specific zones that are good candidates: - **Factual lists** (features, use cases, supported integrations) — items can shuffle and subset. - **Parallel descriptions** (product tiers, platform capabilities) — items with similar structure can reorder. - **Opening sentences of sections** — usually can have two or three alternative phrasings. - **Concrete data points that vary by tenant** — become variables. Leave the rest as-is. Intros, transitions, explanatory text, and narrative flow usually read better fixed. Templatizing them adds complexity with little real anti-footprint value. A typical article ends up roughly **60-70% fixed readable text and 30-40% templatizable zones**. That ratio is the sign of a healthy template. ### Step 3. Prepare templatizable zones Inside the marked zones only, apply these writing principles: - **Sentence independence.** Each sentence in a permutable zone should stand alone. No "This is why...", "Therefore...", or "As mentioned above..." — those break when sentences are reordered. - **Parallel list items.** Items that will be permuted should follow the same grammatical pattern — same structure, same sentence shape. Parallel items are interchangeable, which is exactly what permutations need. - **One idea per sentence.** Separate ideas into individual sentences. Each becomes a permutation element. - **Concrete over vague.** Specific facts produce better variation than generic adjectives. "Requests complete in under 100 ms" gives more to work with than "It is fast." ## General article rules These apply everywhere in the article, not just inside templatizable zones. - **Brand placement.** Mention the brand early, then use generic references ("the platform", "the product"). The whole article should read naturally without the brand name in every paragraph. - **Avoid stale instructions.** Do not write step-by-step walkthroughs for third-party UIs (wallets, dashboards, external integrations). They change frequently and your template goes stale. Describe what the tool does in one or two sentences instead. - **Section length.** `

` sections: 100–250 words. `

` sections: 50–150 words. If a section crosses ~300 words, split it. - **No filler or meta-commentary.** Remove sentences that talk about the text instead of conveying information: "Let's take a look at...", "It is worth noting that...", "This section will explain..." ## Common mistakes at the authoring stage | Don't | Why | Do instead | | --- | --- | --- | | Start with `{a|b|c}` fragments | You're solving the syntax puzzle before the content exists. | Write a normal article first. Add syntax last. | | Templatize every paragraph | Destroys readability without adding variety. | Templatize ~30–40% of zones. Leave narrative fixed. | | Permute narrative sentences | "As we saw above..." breaks when order changes. | Keep narrative fixed. Only permute independent facts. | | Write synonyms before structure | You lock grammar before thinking about shape. | Structure first (permutations, variables), synonyms last. | | Pack three ideas into one sentence, then try to permute | Elements overlap and reordering produces nonsense. | One idea per sentence inside permutable zones. | ## Source-text checklist Before moving on to markup, verify: - The article reads well as a standalone page. - Templatizable zones are identified — not the whole text. - Sentences inside those zones stand alone. - List items inside those zones are parallel. - Brand is mentioned sparingly. - No step-by-step instructions for third-party UIs. - No filler or meta-commentary. - Each `

` and `

` section is within length limits. If every box is ticked, the source text is ready. The next three guides turn that source into a template: variables, permutations, and grammar-safe synonymization — in that order. --- ## Continue the series - [Variables & multi-site reuse](https://spintax.net/docs/variables.md): Precedence, reroll gotchas, separator collisions. - [Permutations in practice](https://spintax.net/docs/permutations.md): Lists, title-style headings, separator rules. - [Grammar-safe synonymization](https://spintax.net/docs/grammar-safe-spintax.md): Binding rules. English agreement. Plus Russian cases in the RU version. - [Template composition](https://spintax.net/docs/template-composition.md): Variables as rendered HTML chunks. Item → section → orchestrator pipeline. - [Conditional spintax](https://spintax.net/docs/conditional-spintax.md): Value-driven {?VAR?then|else} — the syntax-level alternative to random enum picks. - [Plural agreement](https://spintax.net/docs/plural-spintax.md): Pick the right noun form by count. RU/UK/BE and SR/HR/BS 3-form, EN-style 2-form. - [Spintax by example](https://spintax.net/examples.md): Every technique in the series, applied to one real paragraph, with real renders. [Open in playground](https://spintax.net/play/) [Back to all guides](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/variables/ # Variables and multi-site reuse Variables are what turn one template into a network. Get the variable design right and 100 sites render from one source. Get it wrong and you are copy-pasting text into every preset. ## Three sources, one merged scope At render time, most engines merge variables from three places into one lookup table. When the template reads `%SomeName%`, the resolver walks that table and substitutes the value. 1. **Template-local helpers** declared with `#set` or `#def` inside the template body. 2. **Site variables** defined per tenant — one record per site, shared by every template on that site. 3. **Runtime variables** passed into the resolver at call time (article context, system context, user context). Authoring decisions boil down to _which layer_ owns each fact. ## Template-local helpers with `#set` Use `#set` for short-lived helpers inside one template: ``` #set %Lead% = {Welcome|Greetings|Hello} %Lead% to %brand_name%! ``` Good uses: - one-template helpers that would otherwise clutter the body with repetition; - long repeated phrases used multiple times inside the same template; - readability, when nesting gets deep enough to hurt the eye. Bad uses: - tenant-specific facts — those belong in site variables; - anything the runtime already provides — local `#set` loses the precedence fight. ### Syntax rules that trip people up - Variable names are **case-insensitive**. - Use ASCII only: letters, numbers, underscore. No spaces, no hyphens. - `#set` only works when it starts a line. - Comments use `/# ... #/` and are stripped before processing. - Unknown variables stay **literal**. `%MissingVar%` renders as `%MissingVar%`, not as an empty string or an error. Treat leftovers as a QA failure. ## Site variables — the multi-site multiplier Site variables are the reason a shared template can serve many sites without reading the same across every domain. A generic site preset looks like this: ``` #set %BrandTone% = {practical|no-nonsense|straightforward} #set %Industry% = SaaS analytics #set %TopFeatures% = [dashboards|alerting|audit logs|SSO|role-based access] #set %Audience% = {teams|product leads|operations} ``` Every shared template can now read `%BrandTone%`, `%TopFeatures%`, etc., and the output changes per site without anyone touching the template. ### When to create a site variable | Signal | Action | | --- | --- | | Phrase appears in 2+ templates | Extract to a site variable. | | Fact changes per site | Must be a site variable. | | List should shuffle or differ per site | Site variable with a permutation inside. | | Used exactly once in one template | Usually keep it inline. | ## Runtime variables Runtime variables come from the calling context: the article being rendered, the current user, the system clock. They override site variables and template-local helpers with the same name. Common runtime variables across engines (names depend on your implementation): - `%year%` — current year - `%lang%` — current language code - `%site_domain%` — current site host - `%brand_name%`, `%product_name%` — brand/product the article talks about - `%article_topic%`, `%category%` — article-level metadata Authors never assign these from a template. Reading them is enough. ## Variable precedence When the same name exists in multiple layers, the highest priority wins. A standard order, from strongest to weakest: 1. Runtime variables 2. Site variables 3. System variables 4. Template-local `#set` Practical consequence: `#set %brand_name% = Demo` inside a template does nothing if the runtime passes `%brand_name%`. Runtime wins. Pick local helper names that do not shadow the runtime. ## Naming conventions Consistency inside one preset matters more than any specific style. Still, a reasonable default: - **Runtime variables:** `lowercase_snake_case`, usually. They exist outside your control. - **Site variables:** `PascalCase` for regular strings, `PascalCaseWithSuffix` for grammatical variants. - **List variables:** pluralize (`%TopFeatures%`, `%SupportedLanguages%`). - **Local helpers:** short and descriptive — `%Lead%`, `%Closing%`. ## Compound variables Site variables can reference each other. The preset resolver substitutes inter-variable references first while keeping nested spintax raw, so later rerolls still work: ``` #set %FoundedLine% = launched in %FoundedYear%, based in %HQ% #set %Pitch% = {fast|lightweight|self-hosted} %ProductCategory% ``` Use compounds to compose repeated facts once and reuse them across templates. ## The reroll gotcha This is the single most common source of confusion for new authors. **If a variable contains raw spintax, every occurrence rerolls independently.** ``` #set %Tone% = {safe|trusted} %Tone% and %Tone% ``` Possible output: ``` Safe and trusted ``` Do not assume a `#set` variable resolves once and then echoes. If you need two different adjectives, use two variables. **Design rule:** treat every occurrence of a `#set` variable as an _independent_ reroll. For that directive the engine is not caching resolved values across a render — it is re-resolving. ### When you do need exact repetition: `#def` The rule above is about `#set`, which is a macro. Its sibling `#def` takes the same shape and does the opposite: it resolves its value **once per render** and hands that same result to every reference. ``` #def %Tone% = {safe|trusted|secure} %Tone% and %Tone% ``` Now both slots always agree — "safe and safe", "trusted and trusted" — because the roll happened once, before either reference was filled. That is the whole difference between the two directives; everything else (line-anchored, one per line, stripped from output, same name rules) is identical. Reach for `#def` when a value has to stay stable across the template: a count feeding a `{plural}` block, a noun hoisted out of a plural form slot, or any phrase you repeat deliberately. Reach for `#set` when you _want_ the variation, which is the common case in body copy. One caveat worth stating plainly: `#def` makes a single variable consistent with itself. It does **not** correlate two different variables — each `#def` rolls on its own, so `%Noun%` and `%NounGenitive%` can still land on different words. When two values must agree with each other, bind them in one enumeration rather than two variables. ## Optional fragments An empty branch in an enumeration yields an optional fragment: ``` {|official }website {fast|secure|} withdrawals ``` Put the space _inside_ the optional branch when the fragment may disappear, otherwise you get double spaces or jammed words. For an optional list (a permutation that may be empty), wrap the whole permutation: ``` {|[Slack|Jira|Linear]} ``` The engine cannot pick zero items from a permutation. Wrapping is the only way to make "no list at all" a possible outcome. ## Separator collisions A common rendering bug: a list variable already contains `and`, and the surrounding text adds another `and`. ``` %Integrations% and other tools ``` If `%Integrations%` resolves to `Slack, Jira, and Linear`, the final text reads: ``` Slack, Jira, and Linear and other tools ``` Fixes: - insert a comma: `%Integrations%, and other tools`; - restructure: `{Besides|Along with} %Integrations%, other tools...`; - drop the trailing conjunction and use a colon or em-dash. Same issue with a permutation that has `lastsep=" and "` followed by fixed text starting with `and`. Preview a few variants before shipping. ## Variables vs inline spintax | Use a variable | Use inline spintax | | --- | --- | | Phrase repeats across templates | One-off synonym inside one sentence | | Fact changes per site | Generic verb or noun synonym | | List should differ by tenant | Small fixed one-off list | | Grammatical form needs multiple spellings (see Russian cases in article 4) | Word used in only one grammatical position | Rule of thumb: **extract repeated grammar-sensitive phrases to variables before adding tiny inline synonym slots.** The variable gives you one place to fix mistakes. Inline scatters them. ### Prompt fragment — what to tell the model When asking a model to convert an article into a template, include this section's rules verbatim: - _"Move any fact that differs between sites into a site variable. Do not hardcode it in the template."_ - _"Never let a local `#set` shadow a runtime variable name. If a name is already runtime-provided, use a different helper name."_ - _"When you place the same variable twice in a sentence, confirm that both occurrences rerolling independently is acceptable. If not, rewrite."_ - _"After a list variable, do not add a redundant conjunction. Prefer comma or restructuring."_ Pair this with the reverse-authoring mindset: the model should write a readable draft first, _then_ extract variables. Working through an agent? The whole series is condensed into the machine-readable [spintax-authoring skill](https://spintax.net/.well-known/agent-skills/spintax-authoring/SKILL.md), and every English page has a clean Markdown mirror — append `.md` to its URL. ## Common mistakes with variables | Don't | Why | Do instead | | --- | --- | --- | | Hardcode a tenant fact in a shared template | All sites output the same copy, defeating multi-site reuse. | Move the fact to a site variable. | | Use `#set` to override a runtime variable | Runtime always wins, your override silently does nothing. | Rename the helper so it doesn't shadow the runtime name. | | Assume `%X% ... %X%` repeats the same word | Each occurrence rerolls. You may get two different words. | Rewrite the sentence or use two different variables. | | Assume missing variables throw | They render literally as `%MissingVar%`. | Add a preview pass that flags leftover `%...%`. | | Concatenate a list variable with another "and" | Produces "A, B, and C and other things". | Use a comma or restructure. | | Forget the space in an optional fragment | Produces double spaces or jammed words. | Put the space _inside_ the optional branch. | ## Variable-design checklist - Every tenant-specific fact lives in a site variable, not in the shared template. - Every article-specific fact lives in a runtime variable, not in `#set`. - No helper `#set` name shadows a runtime variable. - Variable names are ASCII, no spaces, no hyphens. - Every repeated variable has been reviewed for the reroll effect. - Every optional fragment has its whitespace handled inside the branch. - Every list variable followed by a conjunction has been checked for separator collision. - Five resolved samples have no leftover `%...%`. Ready for structure? The next guide covers [permutations in practice](https://spintax.net/docs/permutations.md) — where the variety actually lives. --- ## Continue the series - [Reverse authoring mindset](https://spintax.net/docs/authoring-mindset.md): Write the text first. Add markup last. - [Permutations in practice](https://spintax.net/docs/permutations.md): Lists, title-style headings, separator rules. - [Grammar-safe synonymization](https://spintax.net/docs/grammar-safe-spintax.md): Binding rules. English agreement. Plus Russian cases in the RU version. - [Template composition](https://spintax.net/docs/template-composition.md): Variables as rendered HTML chunks. Item → section → orchestrator pipeline. - [Conditional spintax](https://spintax.net/docs/conditional-spintax.md): Value-driven {?VAR?then|else} — the syntax-level alternative to random enum picks. - [Plural agreement](https://spintax.net/docs/plural-spintax.md): Pick the right noun form by count. RU/UK/BE and SR/HR/BS 3-form, EN-style 2-form. - [Spintax by example](https://spintax.net/examples.md): Every technique in the series, applied to one real paragraph, with real renders. [Open in playground](https://spintax.net/play/) [Back to all guides](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/permutations/ # Permutations in practice Permutations shuffle. Enumerations pick. Most real variety in a well-authored template comes from permutations — not from stacking synonyms. This guide covers the syntax, the separator rules, and the one pattern most authors miss: serial lists in titles and headings. ## Simple permutation ``` [a|b|c] ``` Defaults: - all elements are included; - order is shuffled on every render; - separator is a single space. Output examples: `a b c`, `c a b`, `b c a`. ## Single-separator shorthand To set the separator (and `lastsep`) in one stroke: ``` [< and >a|b|c] ``` Possible outputs: `a and b and c`, `c and a and b`. The shorthand assigns the same string to both `sep` and `lastsep`, so all joins look alike. ## Full config The verbose form gives full control: ``` [a|b|c|d|e] ``` Rules: - `minsize` — smallest number of elements to emit; - `maxsize` — largest number of elements to emit; - `sep` — joins all elements except the last pair; - `lastsep` — joins the last two elements (for natural "A, B and C" output). Omit either size and the other fills in sensibly: - only `minsize` set → `maxsize` becomes "all available"; - only `maxsize` set → `minsize` becomes 1; - both larger than the element count → clamped to the total; - **the engine cannot pick zero items.** Use an empty-branch wrap for "maybe no list" (see below). ## Per-element separators If you need finer control than a global `sep`/`lastsep`, one element can carry its own separator: ``` [<, >Visa|Mastercard < and >|Skrill] ``` Here: - the global separator is `", "`; - the element after `Mastercard` carries its own local separator `" and "`. Per-element separators are rare but useful when a particular slot needs a different connector. ## Separator auto-spacing Plain word separators are auto-padded with spaces on both sides: ``` [a|b|c] ``` behaves like: ``` a and b and c ``` Punctuation separators are _not_ auto-padded: ``` [<,>a|b|c] ``` produces: ``` a,b,c ``` If you want a space after the comma, include it explicitly: `[<, >a|b|c]`. ## Optional lists Because a permutation cannot emit zero items, the way to make a list "maybe absent" is to wrap the whole permutation in an empty enumeration branch: ``` {|[Postgres|Redis|Kafka|MongoDB]} ``` Possible outputs: `Postgres and Redis`, `Kafka, Postgres and Redis`, or an empty string. Remember to handle the surrounding whitespace — keep the space inside the branch when the list may disappear. ## Serial lists in titles, descriptions, and headings This is the pattern most authors miss. Titles and H2/H3 headings often promise several parallel section themes: ``` Benefits, Integrations, and How to Get Started ``` That is _not_ a frozen string — it is a list of three parallel headline chunks. Recognize the pattern and author it as a permutation: ``` [Benefits|Integrations|How to Get Started] ``` Good inside a heading frame with a brand slot: ``` %product_name%: [Benefits|Integrations|How to Get Started] ``` Bad: ``` %product_name%: {Benefits, Integrations, and How to Get Started|A Complete Guide for Teams} ``` Why the second form is weaker: - it treats a structured list as one frozen string; - it loses the separator pattern the engine already supports; - it pushes the model toward whole-string alternation instead of reusable structure; - it produces only a handful of variants where a permutation would produce dozens. Use the serial-list pattern for: - titles that enumerate three or more promises; - meta descriptions naming several article blocks; - H1/H2/H3 headings listing parallel section themes. Do _not_ use it when the sequence is procedural and must stay in order: ``` Create an account, verify email, and make a deposit ``` That is a step sequence, not a freely permutable headline list. ## Heading casing is manual The usual body-text rule says permutation elements should start lowercase, because post-processing capitalizes sentence starts. That rule does **not** apply to title-style serial headings. Each element must already be authored in its display case: ``` [Benefits|Integrations|How to Get Started] ``` not: ``` [benefits|integrations|how to get started] ``` Post-processing will not convert a comma-separated headline list into title case for you. ### Prompt fragment — how to instruct the model When a model helps templatize an article, spell out the permutation rules explicitly: - _"For any list of 3+ parallel items, emit a permutation with `minsize`/`maxsize` and an explicit `sep`/`lastsep`. Never wrap a fixed list in an enumeration of whole-string alternatives."_ - _"Titles and headings that enumerate several themes are permutations, not frozen strings. Emit them with `minsize=N;maxsize=N` so all themes always appear but in shuffled order."_ - _"For headings, preserve title case in every element. For body text, start elements lowercase so sentence capitalization still works."_ - _"Never end a permutation element with `.`, `!`, or `?` if the separator already provides that punctuation."_ Working through an agent? The whole series is condensed into the machine-readable [spintax-authoring skill](https://spintax.net/.well-known/agent-skills/spintax-authoring/SKILL.md), and every English page has a clean Markdown mirror — append `.md` to its URL. ## Common mistakes with permutations | Don't | Why | Do instead | | --- | --- | --- | | Hardcode a fixed 4+ item list | Fixed order becomes a footprint across all renders. | Use a permutation, even with `minsize=maxsize` if the set is mandatory. | | Freeze a serial headline as one string | Loses the separator pattern; shrinks variant space dramatically. | Emit as a permutation with `sep` and `lastsep`. | | Use `minsize=0` | Engine clamps to 1 minimum; you get a silently-broken expectation. | Wrap the list in `{|[...]}` for "maybe no list at all". | | End permutation elements with a period while `sep=". "` | Double punctuation: `"fact. . next fact"`. | Let the separator add punctuation; keep elements bare. | | Forget spaces around word separators the engine already pads | Renders `aandb` when you tried to fix a non-existent problem. | Trust auto-padding for word separators; add spaces only for punctuation separators. | | Author permutation elements in mixed case | Rendered text has random-looking capitalization. | Use lowercase for body text, title case for headings. Consistent inside one permutation. | | Make every element a whole sentence with its own subject | Serial flow breaks — each element reads as a new paragraph. | Keep elements phrase-level; bind subject outside the permutation. | ## Permutation checklist - Every 3+ item list is a permutation, not a frozen string. - Serial lists in titles and headings use permutations with explicit `sep`/`lastsep`. - Optional lists are wrapped in `{|[...]}`, not attempted with `minsize=0`. - Permutation elements do not end with punctuation that the separator already supplies. - Heading permutations are authored in display case. - Body-text permutations start lowercase when post-processing will capitalize. - Every element in one permutation is grammatically parallel with the others. With structure in place, the final pass is grammar: [grammar-safe synonymization](https://spintax.net/docs/grammar-safe-spintax.md). --- ## Continue the series - [Reverse authoring mindset](https://spintax.net/docs/authoring-mindset.md): Write the text first. Add markup last. - [Variables & multi-site reuse](https://spintax.net/docs/variables.md): Precedence, reroll gotchas, separator collisions. - [Grammar-safe synonymization](https://spintax.net/docs/grammar-safe-spintax.md): Binding rules. English agreement. Plus Russian cases in the RU version. - [Template composition](https://spintax.net/docs/template-composition.md): Variables as rendered HTML chunks. Item → section → orchestrator pipeline. - [Conditional spintax](https://spintax.net/docs/conditional-spintax.md): Value-driven {?VAR?then|else} — the syntax-level alternative to random enum picks. - [Plural agreement](https://spintax.net/docs/plural-spintax.md): Pick the right noun form by count. RU/UK/BE and SR/HR/BS 3-form, EN-style 2-form. - [Spintax by example](https://spintax.net/examples.md): Every technique in the series, applied to one real paragraph, with real renders. [Open in playground](https://spintax.net/play/) [Back to all guides](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/grammar-safe-spintax/ # Grammar-safe synonymization Synonymization is the last refinement, not the first step. By the time you reach this guide, you should already have a readable draft, variables extracted, and structure in place. What is left: swap a handful of words for variety without breaking a single variant. ## The principle Every `{a|b}` is a small cut inside an already-correct phrase. The cut must preserve everything around it: case, agreement, governance, articles, word order. If the swap changes any of those, the cut is in the wrong place and you need to bind a larger phrase. Think of it this way: you are not choosing between two synonyms, you are choosing between two _grammatically complete_ fragments that happen to differ by one word. ## The practical test Before writing any `{a|b}`, ask: > _If I swap only this fragment, do case, agreement, governance, articles, and word order all remain correct?_ If the answer is not an immediate yes, bind a larger phrase in one branch. ## Synonymization rules - **Structure first, synonyms last.** Never add `{a|b}` before permutations, variables, and sentence shape are locked. - **2–4 options per slot is enough.** Five is diminishing returns; ten starts producing awkward variants. - **Same grammatical role in every branch.** All options occupy the same slot: verb vs verb, noun vs noun, prepositional phrase vs prepositional phrase. - **Preserve register.** Do not mix a formal option with a casual one inside the same `{a|b}`. - **Vary what matters.** Changing "big" to "large" everywhere is noise. Changing verbs in topic sentences is variety. ### Good ``` {offers|provides|features} live support ``` All three options occupy the same verb slot with the same subject and object. ``` for {teams|users|customers} ``` All three fit the same English prepositional slot. ### Wrong ``` {secure|encrypted} connection ``` The preceding article is missing — one branch needs `a`, the other needs `an`. Bind the article inside: ``` {a secure|an encrypted} connection ``` Or bind the whole phrase: ``` {a secure connection|an encrypted connection} ``` ## English subject-verb agreement Bind subject and verb together whenever they could disagree: ### Wrong ``` {the team|all members} {responds|respond} ``` The two enumerations are independent — nothing prevents the output `the team respond`. ### Right ``` {the team responds|all members respond} ``` ## Article and determiner binding Any time the branch changes a noun's initial sound or number, pull the article into the branch: - Wrong: `a {secure|encrypted} connection` - Right: `{a secure|an encrypted} connection` - Wrong: `the {result|results}` (if "the" is fine but verb depends on number, re-bind the verb too) - Right: `{the result is|the results are}` ## Repeated-word collision When two nearby `{a|b}` slots share a word, they can produce awkward repetition: ``` Users who {need to acquire the tool|do not own the tool} can {acquire|purchase} it here. ``` If both slots pick `acquire`, the result reads "need to acquire... can acquire". Fix: ensure the overlapping word appears in only one slot, or rephrase the other branch. ### Prompt fragment — grammar audit for the model Ask the model to run every `{a|b}` through the practical test explicitly: - _"For every enumeration `{a|b|c}`, verify that swapping branches preserves case, agreement, governance, article use, and word order. If any branch needs a different article or a different verb form, bind the larger phrase."_ - _"Never split a determiner from its noun across an enumeration boundary in English."_ - _"Scan the template for repeated words across nearby enumerations. If the same word can appear in two consecutive slots, rewrite one of them."_ - _"Give me five sample renders before committing. Any variant that reads awkwardly means the grammar binding is wrong."_ Structure and variables come first, this grammar pass is the final gate. Working through an agent? The whole series is condensed into the machine-readable [spintax-authoring skill](https://spintax.net/.well-known/agent-skills/spintax-authoring/SKILL.md), and every English page has a clean Markdown mirror — append `.md` to its URL. ## Common mistakes | Don't | Why | Do instead | | --- | --- | --- | | Synonymize before structure is finalized | Locks grammar before you know the sentence shape. | Do structure, variables, then synonyms. | | Split subject and verb across a boundary | Agreement breaks in at least one variant. | Bind subject+verb inside one branch. | | Keep article outside the branch when branches change sound/number | Produces `a encrypted` or `the results is`. | Pull the article (and noun if needed) into each branch. | | Stack 6+ options per slot | Variants grow awkward and register drifts. | Cap at 2–4 high-quality options. | | Synonymize narrative connectives | "Therefore / Thus / Hence" change register mid-paragraph. | Keep narrative connectives fixed. | | Use the same word in two adjacent slots | Output repeats mechanically. | Ensure the overlap only lives in one slot. | ## Grammar checklist - Every enumeration branch holds a grammatically complete fragment in its own slot. - Every enumeration has been put through the practical test. - Subject-verb agreement works in every branch. - Articles and determiners are bound inside branches when sound or number changes. - No word appears in two adjacent enumerations. - Register stays consistent across all branches of a slot. - Five sample renders read naturally end to end. - No leftover `%...%`, `{...}`, or `[...]` in any sample. That is the whole series. Structure, variables, permutations, grammar — in that order. Come back to [the mindset](https://spintax.net/docs/authoring-mindset.md) when you start a new article. The workflow is faster every time. --- ## Continue the series - [Reverse authoring mindset](https://spintax.net/docs/authoring-mindset.md): Write the text first. Add markup last. - [Variables & multi-site reuse](https://spintax.net/docs/variables.md): Precedence, reroll gotchas, separator collisions. - [Permutations in practice](https://spintax.net/docs/permutations.md): Lists, title-style headings, separator rules. - [Template composition](https://spintax.net/docs/template-composition.md): Variables as rendered HTML chunks. Item → section → orchestrator pipeline. - [Conditional spintax](https://spintax.net/docs/conditional-spintax.md): Value-driven {?VAR?then|else} — the syntax-level alternative to random enum picks. - [Plural agreement](https://spintax.net/docs/plural-spintax.md): Pick the right noun form by count. RU/UK/BE and SR/HR/BS 3-form, EN-style 2-form. - [Spintax by example](https://spintax.net/examples.md): Every technique in the series, applied to one real paragraph, with real renders. [Open in playground](https://spintax.net/play/) [Back to all guides](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/template-composition/ # Template composition: variables as rendered HTML chunks Sometimes one template gets too big to live with. Hundreds of `
  • ` items in a payment-methods page, dozens of inline editorial notes, sort-order changes that have to ripple through every variant — at some point one giant nested template becomes the bottleneck instead of the help. The next step is splitting it into a pipeline of small templates, joined by variables that hold already-rendered HTML. ## The mental shift So far in this series a variable has been a _value_: a brand name, a year, a comma-separated list of features. Plain strings substituted into the template at render time. The shift in this guide is small and powerful: **a variable's value can be already-resolved HTML**. Not `"Acme Co."` but `

    Crypto deposits

    • BTC — fastest…
    `. The resolver does not care; it just substitutes. This unlocks composition. You build the page from a pipeline of small sub-templates, each rendered to a chunk of HTML, then assembled by an orchestrator that is just a few lines long. ### Without composition ```

    Crypto deposits

    • {Bitcoin|BTC} — {fastest|the most popular} option, {confirms in 10–60 min|settles within an hour}.
    • {Ethereum|ETH} — {smart-contract chain|programmable network}, {2–5 min blocks|fast block times}.
    • /# … 8 more crypto items #/

    Fiat deposits

      /# … 12 more fiat items, each with editorial notes #/

    Deposit and withdrawal limits

    /# … 20+ rows #/
    ``` That is a 200-line monolith. Adding a coin means editing inside one big enum chain. Sort changes are hand work. Per-currency editorial nuances scatter across the file. ### With composition ``` %CryptoSection% %FiatSection% %LimitsSection% ``` Three lines. Each variable already holds the fully resolved HTML for its part of the page. The values come from a pipeline that runs _before_ the orchestrator is rendered. ## Why this works — the engine pipeline The [syntax reference](https://spintax.net/docs/syntax.md) spells out the order of resolution; the relevant lines for composition are: 1. strip comments; 2. extract `#set` / `#def` directives; 3. merge variables; 4. **expand `%var%` references**; 5. resolve enumerations `{a|b|c}`; 6. resolve permutations `[a|b|c]`; 7. post-process. Variables expand _before_ enum and permutation resolution. By the time enum/perm runs, `%CryptoSection%` has already been replaced with whatever HTML the assembler computed. No special syntax — variable substitution is literally string replacement. You can even mix layers: an outer permutation can shuffle pre-rendered sections. ``` [%CryptoSection%|%FiatSection%|%LimitsSection%] ``` Each section is fully resolved first, then the permutation reorders the chunks. ## The three-level pipeline The pattern lives in three layers, each one stage of refinement: ### Level 1 — Item templates (per id) Smallest reusable unit. One template per data item: per coin, per payment method, per plan tier, per FAQ entry, per product SKU. ``` /# spintax.crypto_item.btc #/
  • {Bitcoin|BTC} — {fastest|the most popular} option, {confirms in 10–60 min|settles within an hour}.
  • /# spintax.crypto_item.eth #/
  • Ethereum — {smart-contract chain|programmable network}, {2–5 min blocks|fast block times}.
  • ``` ### Level 2 — Section templates Wrap the list with structure. Use a placeholder variable for the joined items. ``` /# spintax.section.crypto #/

    {Crypto deposits|Cryptocurrencies accepted}

    {Pick from|We support} the following coins:

      %CryptoItems%
    ``` `%CryptoItems%` is "every per-id item resolved and joined as a single string". The assembler builds it. ### Level 3 — Orchestrator The page-level template. References pre-rendered section variables only. ``` /# spintax.payment_options #/

    {Accepted payment methods|How to pay}

    %CryptoSection% %FiatSection% %LimitsSection% ``` That is the whole orchestrator. Editing rules: change a per-coin description? Edit one item template. Add a new currency? Drop a new item template, add the id to the active list. Reorder? Sort field, not template change. ## Walkthrough — a payment-methods page A merchant accepts BTC, USDT, ETH for crypto and Visa, Mastercard, SEPA for fiat. Three lookups and a handful of templates produce the full page. Pseudo-code for the assembler that runs _before_ the orchestrator render: ``` function buildPaymentVars(merchantId, lang) { // 1. Pull active items, in display order. const cryptos = db.query("active cryptos for merchant ordered by sort", merchantId); const fiats = db.query("active fiats for merchant ordered by sort", merchantId); // 2. Resolve each per-id template, join the chunks. const cryptoItems = cryptos .map(c => parser.process(templates.find(`crypto_item.${c.id}`, lang))) .join(""); const fiatItems = fiats .map(f => parser.process(templates.find(`payment_item.${f.id}`, lang))) .join(""); // 3. Resolve each section template with item placeholders. const cryptoSection = cryptos.length ? parser.process(templates.find("section.crypto", lang), { CryptoItems: cryptoItems }) : ""; const fiatSection = fiats.length ? parser.process(templates.find("section.fiat", lang), { FiatItems: fiatItems }) : ""; // 4. Limits section is similar; LimitsRows are joined chunks. const limitsSection = (cryptos.length || fiats.length) ? parser.process(templates.find("section.limits", lang), { LimitsRows: buildLimitsRows(cryptos, fiats, lang) }) : ""; // 5. Return the variables the orchestrator references. return { CryptoSection: cryptoSection, FiatSection: fiatSection, LimitsSection: limitsSection, HasCrypto: cryptos.length ? "1" : "", HasFiat: fiats.length ? "1" : "", }; } ``` The orchestrator render then receives these alongside any normal site/runtime variables and does a final pass. ## Naming conventions Convention beats freedom here, because the assembler finds templates by id. | Pattern | Example | | --- | --- | | `spintax._item.` | `spintax.crypto_item.btc` | | `spintax._row.` | `spintax.crypto_row.btc` (table row) | | `spintax.section.` | `spintax.section.crypto` | | `spintax.` | `spintax.payment_options` | Variables follow the same shape: - `%CryptoItems%`, `%FiatItems%`, `%LimitsRows%` — joined per-id chunks - `%CryptoSection%`, `%FiatSection%`, `%LimitsSection%` — resolved sections - `%HasCrypto%`, `%HasFiat%` — markers (`'1'` or `''`) PascalCase for variables, snake\_case for IDs, ASCII only for both. ## Storage is your problem, not spintax's The pattern works the same regardless of where sub-templates live: - database table (`templates` with id + body + lang) - JSON file: `{ "crypto_item.btc": "
  • ", … }` - filesystem: `templates/crypto_item/btc.txt` - CMS field per locale The engine doesn't need a database. It just substitutes resolved HTML into variable references. The assembler is your code, written in whatever runtime drives your renders. A WordPress plugin, a Cloudflare Worker, a Node script, a Postgres function — same pattern. ## Per-id editorial nuances This is the killer feature. Editorial nuances live with the data, not in every page. ``` /# spintax.payment_item.visa — 3DS warning baked in #/
  • Visa — {3DS-protected|with 3D Secure} debit and credit cards, {instant deposit|immediate confirmation}.
  • /# spintax.crypto_item.xrp — destination-tag reminder per coin #/
  • XRP — fast and {cheap|low-fee}, {do not forget the destination tag|destination tag is required}.
  • /# spintax.payment_item.qiwi — legacy status per method #/
  • QIWI — {legacy support|now legacy}, {accepted but discouraged|not recommended for new accounts}.
  • ``` Each per-id template captures the nuance once. Three pages, ten pages, a thousand pages — they all inherit the right warnings. Move QIWI to "deprecated" by editing one template; every render flips simultaneously. Without composition, those nuances would be inline strings duplicated across pages. Audit nightmare and a slow-burn legal risk in regulated industries. ## Conditional fallback You will see authors trying to express conditionals with the engine's enum syntax: ``` {%HasCrypto%|%HasFiat%||

    Payment methods coming soon.

    } ``` The hope is "show the fallback when both flags are empty". The reality with a plain `{a|b|c|d}` enum: the engine picks one of the four branches at random with equal probability. The output is non-deterministic and includes `"1"` as a possible variant on the page. Enum branches are a uniform random pick — they never look at variables. For value-driven choice, use the conditional pre-pass: ``` {?!HasCrypto?{?!HasFiat?

    Payment methods coming soon.

    }} ``` Read it as: _if not crypto and not fiat, render the fallback_. The conditional resolves **before** the random branch picker runs, so the output is fully determined by the variables. Composite logic that conditional spintax does _not_ support stays in the assembler — comparisons, `&&`/`||`, computed values. Pre-compute a guard variable, then gate it with `{?Guard?…}`. The dedicated [Conditional spintax guide](https://spintax.net/docs/conditional-spintax.md) covers the three forms, the truthy table, the two-pass pipeline, and the anti-patterns in detail. ## When NOT to compose Composition has overhead — three template kinds to maintain, an assembler to wire, a storage layer to organize. The pipeline pays off when: - you have five or more similar items sharing a structure; - per-id editorial nuances or sort-order requirements exist; - multiple pages reuse the same item set; - editors need to modify items independently. Skip composition when: - the page has one to three items total; - items don't repeat across pages; - nothing in the structure changes for the next year; - nobody but you will edit it. For a one-off about page or a single article, one self-contained template is faster, cleaner, and easier to debug. ### Prompt fragment — instructing the model to compose When asking a model to refactor a giant template into a composition pipeline, include these rules verbatim: - _"Identify groups of repeated structure (3+ items with the same shape). Each group is a candidate for an item template."_ - _"Extract per-item editorial nuances into the item templates. Do not duplicate them in the section or orchestrator."_ - _"Wrap each item group in a section template that uses an `%XxxItems%` placeholder."_ - _"Build the orchestrator from section variables only. Keep it under twenty lines."_ - _"Move every conditional out of spintax into the variable assembler. Spintax does not evaluate conditions; do not use enum branches as if-then-else."_ - _"For every item, suggest a stable id (lowercase, snake\_case) and propose where the item template would live (DB row, JSON key, file path)."_ Working through an agent? The whole series is condensed into the machine-readable [spintax-authoring skill](https://spintax.net/.well-known/agent-skills/spintax-authoring/SKILL.md), and every English page has a clean Markdown mirror — append `.md` to its URL. ## Common mistakes | Don't | Why | Do instead | | --- | --- | --- | | Compose a small page (≤3 items, no editorial variance) | Pipeline overhead is more than the saving. | Keep one self-contained template. | | Encode conditionals in spintax enums | Engine picks randomly, not based on values; output is non-deterministic. | Use `{?VAR?then|else}` for single-variable checks; compute composite logic in the assembler and gate it with `{?Guard?…}`. | | Inline per-id nuances in the orchestrator or section | Loses the "edit once, propagate everywhere" benefit. | Keep nuances in the per-id `_item` template. | | Mix item-level and section-level concerns in one template | Refactoring becomes painful as the page grows. | Three clean levels: item, section, orchestrator. | | Hardcode sort order in the orchestrator | Sort changes need page edits across the catalog. | Sort in the assembler from a single sort field on each item. | | Forget to short-circuit empty sections | Empty `

    ` with no `