# 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](https://spintax.net/docs/grammar-safe-spintax.md#russian-cases) in the grammar guide) | 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. Russian cases: families, prepositions, gender. - [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. - [Repairing spintax templates](https://spintax.net/docs/repairing-spintax.md): Detect → one named recipe → re-check. Lint codes, the catalogue, the repair prompt. - [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/