{"id":294363,"date":"2026-05-11T07:53:30","date_gmt":"2026-05-11T07:53:30","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/spintax\/"},"modified":"2026-09-06T17:43:01","modified_gmt":"2026-09-06T17:43:01","slug":"spintax","status":"publish","type":"plugin","link":"https:\/\/ja.wordpress.org\/plugins\/spintax\/","author":23448903,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"3.1.0","stable_tag":"3.1.0","tested":"7.1.1","requires":"6.2","requires_php":"8.0","requires_plugins":null,"header_name":"Spintax","header_author":"301st","header_description":"Template-based dynamic content generation using spintax markup. Create reusable templates with randomised text variants, variable substitution, and permutation logic.","assets_banners_color":"0c0f15","last_updated":"2026-09-06 17:43:01","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/spintax.net","header_author_uri":"https:\/\/301.st","rating":0,"author_block_rating":0,"active_installs":0,"downloads":802,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.5.0":{"tag":"1.5.0","author":"301st","date":"2026-05-11 07:53:09","revision":3528366},"2.0.0":{"tag":"2.0.0","author":"301st","date":"2026-05-12 15:54:30","revision":3530118},"2.0.1":{"tag":"2.0.1","author":"301st","date":"2026-05-12 19:04:28","revision":3530301},"2.0.2":{"tag":"2.0.2","author":"301st","date":"2026-05-12 19:29:39","revision":3530320},"2.0.3":{"tag":"2.0.3","author":"301st","date":"2026-05-12 22:18:14","revision":3530391},"2.1.0":{"tag":"2.1.0","author":"301st","date":"2026-05-14 11:55:34","revision":3531933},"2.1.1":{"tag":"2.1.1","author":"301st","date":"2026-05-14 17:42:37","revision":3532366},"2.2.0":{"tag":"2.2.0","author":"301st","date":"2026-07-04 06:27:37","revision":3595725},"2.2.1":{"tag":"2.2.1","author":"301st","date":"2026-07-04 07:16:02","revision":3595744},"2.2.2":{"tag":"2.2.2","author":"301st","date":"2026-07-04 07:43:26","revision":3595766},"2.3.0":{"tag":"2.3.0","author":"301st","date":"2026-07-05 13:48:03","revision":3596799},"2.3.1":{"tag":"2.3.1","author":"301st","date":"2026-07-05 14:16:59","revision":3596826},"2.3.2":{"tag":"2.3.2","author":"301st","date":"2026-07-05 15:47:20","revision":3596898},"2.3.3":{"tag":"2.3.3","author":"301st","date":"2026-07-13 20:40:09","revision":3606694},"2.4.0":{"tag":"2.4.0","author":"301st","date":"2026-07-14 01:04:43","revision":3606821},"2.5.0":{"tag":"2.5.0","author":"301st","date":"2026-07-18 15:56:17","revision":3612778},"3.0.0":{"tag":"3.0.0","author":"301st","date":"2026-07-19 08:43:45","revision":3613366},"3.0.1":{"tag":"3.0.1","author":"301st","date":"2026-08-07 12:08:47","revision":3637828},"3.0.2":{"tag":"3.0.2","author":"301st","date":"2026-08-07 12:41:31","revision":3637865},"3.1.0":{"tag":"3.1.0","author":"301st","date":"2026-09-06 17:43:01","revision":3683767}},"upgrade_notice":{"3.1.0":"<p>Engine catch-up with spintax\/core 0.8.0: validation and rendering stay memory-safe on pathological templates, plural forms are counted after #def expansion (fewer false arity errors), one circular-reference error per variable. Tested up to WordPress 7.1. No template changes needed.<\/p>","3.0.2":"<p>Engine parity catch-up with spintax\/core 0.5.2: a circular #set renders partial text instead of empty, directive\/#include grammar matches the whole engine family (ASCII variable names, as documented), and validating large templates is orders of magnitude faster.<\/p>","3.0.1":"<p>Fixes a rare stray null character in rendered output near adjacent links, emails or nested shortcodes, and makes large renders much faster. No template changes needed.<\/p>","3.0.0":"<p>Breaking: #set is a macro again, re-picked at every use. The new #def holds one value for the whole page. If you used #set for a plural count, that block now renders empty - change the one #set line to #def. References stay as they are; the editor flags the affected templates.<\/p>","2.5.0":"<p>Adds Serbian, Croatian and Bosnian plural agreement, both Serbian scripts. Breaking for those three only: they used to accept a 2-form {plural} and now need 3, having silently used the English rule. A stale 2-form block renders as fullwidth braces - add the third form before updating.<\/p>","2.4.0":"<p>New: WooCommerce product-field bindings. Generate a product&#039;s description or short description from a template, per product, with that product&#039;s own SKU, categories and attributes. Only those two fields are writable; manual edits are preserved.<\/p>","2.3.3":"<p>Post-processing fixes: repeated punctuation (<code>...<\/code>, <code>!!!<\/code>, <code>?!<\/code>) is no longer split apart, <code>mailto:<\/code> \/ <code>tel:<\/code> links are no longer broken, and Spanish <code>\u00bf \u00a1<\/code> sentences keep their capital. If a binding already wrote mangled text into a field, re-run Bulk Apply to regenerate it.<\/p>","2.3.2":"<p>WordPress.org listing refresh (description + tags). No code or behavior change.<\/p>","2.3.1":"<p>Internal follow-up to 2.3.0: restores cheap out-of-scope skips in the bindings applier (no redundant source read on out-of-scope dry-runs). No behavior or output change. Safe upgrade.<\/p>","2.3.0":"<p>Internal bindings refactor (pure Planner + target registry). No behavior change \u2014 every binding outcome is byte-for-byte identical, verified by the full test suite passing unchanged plus a new 13-outcome table test and a contract audit. Safe upgrade; nothing to do.<\/p>","2.2.2":"<p>Extends 2.2.1&#039;s product-value spintax shielding to post-context and ACF-sibling binding variables via a shared utility. Record-sourced values (post_title, acf_*) now render literally instead of being re-interpreted as spintax. Template \/ #set \/ global authoring is unchanged.<\/p>","2.2.1":"<p>Security hardening for 2.2.0&#039;s WooCommerce context variables: closes a same-request memo bypass of the published-product gate on explicit product_id, and neutralizes spintax characters in product values so they render literally. Recommended for 2.2.0 users.<\/p>","2.2.0":"<p>Read-only WooCommerce product context variables (<code>%product_name%<\/code>, <code>%product_categories%<\/code>, <code>%product_attribute_%<\/code>, and more) in <code>[spintax]<\/code> \/ <code>spintax_render()<\/code> on single-product pages; each product caches its own variant. Pricing excluded. WooCommerce optional; non-product sites unchanged.<\/p>","2.1.1":"<p>Bindings UX polish: Bulk Apply disables with a tooltip when Action Scheduler is missing, the stale-source banner promotes Run-now instead, the ACF picker keeps its selection, and clean Run-now walks write a Logs entry so the success notice&#039;s CTA has something to show.<\/p>","2.1.0":"<p>Admin UX overhaul. New Logs page closes the &quot;check logs&quot; gap. Bindings form is now three keyboard-friendly tabs with a real ACF combobox. TTL fields use presets. Stale banner + trigger warning + Run-now sync button on the list. No data migration; recommended for binding users.<\/p>","2.0.3":"<p>Adds runtime ACF target validation (closes a wrong-field-write path under ACF reactivation \/ WP-CLI imports), cumulative-failure tracking across Bulk Apply chunks (Stale badge no longer clears on partial failures), and a per-binding walk lock that refuses concurrent walks. Strongly recommended.<\/p>","2.0.2":"<p>Documentation refresh for the 2.0 binding surface (Action Scheduler as a recommended optional dependency, full WP-CLI command set, variable scopes, scheduling, manual edits) plus an admin notice on the Bindings page when Action Scheduler isn&#039;t loaded. No functional changes to the engine.<\/p>","2.0.1":"<p>Hot-fix for 2.0.0: cross-kind binding collisions, missing ACF field_key validation, Test panel scope-filter parity, Bulk Apply Stale-badge gating, and form value preservation on validation errors. Highly recommended if you&#039;re on 2.0.0.<\/p>","2.0.0":"<p>Major release \u2014 adds ACF \/ post-meta bindings, per-binding cron, Bulk Apply with Action Scheduler, full WP-CLI surface, and a one-shot migration wizard for <code>nested-spintax-for-acf<\/code> users. No breaking changes to the existing template \/ shortcode \/ render API.<\/p>","1.4.0":"<p>New <code>{?VAR?then|else}<\/code> conditional syntax, smarter sentence-end capitalisation around abbreviations, and a fix for <code>#set<\/code> directives with empty values.<\/p>","1.1.0":"<p>Per-element permutation separators, auto-spacing for word separators, improved input sanitization.<\/p>","1.0.1":"<p>Fixes permutation config handling, preview rendering, and scope isolation. Recommended update.<\/p>","1.0.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3528366,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3528366,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3528366,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3528366,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3528366,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.5.0","2.0.0","2.0.1","2.0.2","2.0.3","2.1.0","2.1.1","2.2.0","2.2.1","2.2.2","2.3.0","2.3.1","2.3.2","2.3.3","2.4.0","2.5.0","3.0.0","3.0.1","3.0.2","3.1.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3528366,"resolution":"1","location":"assets","locale":"","width":1280,"height":800},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3528366,"resolution":"2","location":"assets","locale":"","width":1280,"height":800},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3528366,"resolution":"3","location":"assets","locale":"","width":1280,"height":800},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3532366,"resolution":"4","location":"assets","locale":"","width":1280,"height":800}},"screenshots":{"1":"Template editor with spintax markup and live preview.","2":"Settings page with global variables editor.","3":"Template list with shortcode, cache status, and cron schedule.","4":"Binding edit form: three-tab layout (Source &amp; Target \/ Behavior \/ Test), ACF combobox with stable field-key autofill, post-type and status scope filters, shared-template vs per-post source modes."}},"plugin_section":[],"plugin_tags":[2211,8494,186,47760,286],"plugin_category":[43,55],"plugin_contributors":[255945],"plugin_business_model":[],"class_list":["post-294363","plugin","type-plugin","status-publish","hentry","plugin_tags-acf","plugin_tags-content-generation","plugin_tags-seo","plugin_tags-spintax","plugin_tags-woocommerce","plugin_category-customization","plugin_category-seo-and-marketing","plugin_contributors-301st","plugin_committers-301st"],"banners":{"banner":"https:\/\/ps.w.org\/spintax\/assets\/banner-772x250.png?rev=3528366","banner_2x":"https:\/\/ps.w.org\/spintax\/assets\/banner-1544x500.png?rev=3528366","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/spintax\/assets\/icon.svg?rev=3528366","icon":"https:\/\/ps.w.org\/spintax\/assets\/icon.svg?rev=3528366","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/spintax\/assets\/screenshot-1.png?rev=3528366","caption":"Template editor with spintax markup and live preview."},{"src":"https:\/\/ps.w.org\/spintax\/assets\/screenshot-2.png?rev=3528366","caption":"Settings page with global variables editor."},{"src":"https:\/\/ps.w.org\/spintax\/assets\/screenshot-3.png?rev=3528366","caption":"Template list with shortcode, cache status, and cron schedule."},{"src":"https:\/\/ps.w.org\/spintax\/assets\/screenshot-4.png?rev=3532366","caption":"Binding edit form: three-tab layout (Source &amp; Target \/ Behavior \/ Test), ACF combobox with stable field-key autofill, post-type and status scope filters, shared-template vs per-post source modes."}],"raw_content":"<!--section=description-->\n<p>Spintax is a content-generation toolkit for WordPress that turns one template into unique, non-duplicate copy across your whole site. Author reusable templates in spintax markup (enumerations, permutations, conditionals, plural agreement), then either embed them inline via shortcodes \/ PHP \u2014 automatically picking up the <strong>current WooCommerce product<\/strong> on product pages \u2014 or <strong>bind them to ACF and post-meta fields so every matching post gets its own rendered variant on save, on a cron schedule, or on demand<\/strong>. A built-in Logs page surfaces what each Bulk Apply \/ Run-now walk did; a WP-CLI surface covers staging-to-production sync.<\/p>\n\n<p>Ideal for content managers and SEO specialists producing many similar-but-unique pages: product descriptions, category copy, location \/ landing pages, listing blurbs, and FAQ snippets.<\/p>\n\n<p><strong>Key features:<\/strong><\/p>\n\n<ul>\n<li><strong>Enumerations<\/strong> <code>{a|b|c}<\/code> \u2014 randomly pick one option, with nesting support<\/li>\n<li><strong>Permutations<\/strong> <code>[&lt;config&gt;a|b|c]<\/code> \u2014 pick N elements, shuffle, join with custom separators<\/li>\n<li><strong>Variables<\/strong> <code>%var%<\/code> \u2014 global, local (<code>#set<\/code> re-picks at every use, <code>#def<\/code> picks once per render), and shortcode-level scopes<\/li>\n<li><strong>Conditionals<\/strong> <code>{?VAR?then|else}<\/code> \u2014 render a branch based on whether a variable is set (also <code>{?!VAR?then}<\/code> inverted)<\/li>\n<li><strong>Plural agreement<\/strong> <code>{plural &lt;count&gt;: form1|form2|form3}<\/code> \u2014 pick grammatically correct noun form by count. RU\/UK\/BE and SR\/HR\/BS 3-form (one|few|many), EN-style 2-form (one|many). Other languages fall back to the 2-form rule, so <code>pl<\/code>, <code>cs<\/code>, <code>sk<\/code>, <code>sl<\/code> and <code>bg<\/code> are bucketed by a rule that is not theirs rather than rejected. First spintax engine with first-class plurals.<\/li>\n<li><strong>Nested templates<\/strong> \u2014 embed templates within templates via <code>#include<\/code> or <code>[spintax]<\/code><\/li>\n<li><strong>ACF \/ post-meta bindings (NEW in 2.0)<\/strong> \u2014 configure once per post type, render Spintax templates into ACF text\/textarea\/wysiwyg fields or post-meta keys on every matching post. Auto-seed empty fields, preserve manual edits, Bulk Apply via Action Scheduler.<\/li>\n<li><strong>WooCommerce product context (NEW in 2.2)<\/strong> \u2014 on a single-product page, <code>[spintax]<\/code> \/ <code>spintax_render()<\/code> automatically expose the current product as <code>%product_name%<\/code>, <code>%product_sku%<\/code>, <code>%product_categories%<\/code>, <code>%product_attribute_&lt;slug&gt;%<\/code>, and more. Volatile pricing is intentionally out of scope. WooCommerce is optional \u2014 the variables simply appear when a product context is present.<\/li>\n<li><strong>WooCommerce product-field bindings (NEW in 2.4)<\/strong> \u2014 generate a product's <strong>description<\/strong> or <strong>short description<\/strong> from a template, per product, using that product's own SKU, categories and attributes. Only those two fields are writable; price, SKU and stock are commerce data and stay out of reach. Manual edits are preserved by default.<\/li>\n<li><strong>Object cache<\/strong> \u2014 rendered output cached via WP Object Cache API (Redis\/Memcached ready)<\/li>\n<li><strong>Cron regeneration<\/strong> \u2014 optional scheduled cache refresh per template, plus per-binding cron walks<\/li>\n<li><strong>WP-CLI<\/strong> \u2014 <code>wp spintax bindings list|apply|test|export|import<\/code><\/li>\n<li><strong>Validation<\/strong> \u2014 bracket matching, circular reference detection, syntax checking<\/li>\n<li><strong>Admin UI<\/strong> \u2014 code editor, live preview, shortcode copy, settings page, bindings list<\/li>\n<\/ul>\n\n<p><strong>The syntax is an open, documented standard.<\/strong> Its core is GTW-compatible (enumerations, permutations, variables, includes), extended over the years with its own primitives \u2014 value-driven conditionals, plural agreement, roll-once <code>#def<\/code> variables \u2014 and documented in full at <a href=\"https:\/\/spintax.net\/docs\/syntax\">spintax.net<\/a>. Six independent engines \u2014 five installable libraries (PHP, JavaScript\/TypeScript, Python, .NET, Object Pascal) plus this plugin \u2014 are held to one shared test corpus, and a free native Windows editor \u2014 <a href=\"https:\/\/apps.microsoft.com\/detail\/9mw3ch7b530p\">Spintax Studio<\/a> \u2014 authors it with live preview and validation.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin does <strong>not<\/strong> connect to any external services, APIs, or third-party servers.<\/p>\n\n<p>All content generation happens locally on your WordPress server. No data is sent externally. No remote requests are made during activation, rendering, or caching.<\/p>\n\n<h3>Privacy Policy<\/h3>\n\n<p>This plugin does not collect, store, or transmit any personal user data. It does not use cookies, tracking pixels, analytics, or any form of telemetry.<\/p>\n\n<p>Templates and their rendered output are stored entirely within your WordPress database and object cache.<\/p>\n\n<h3>Credits<\/h3>\n\n<ul>\n<li>Syntax: the open spintax standard documented at <a href=\"https:\/\/spintax.net\">spintax.net<\/a>, rooted in the historical <a href=\"https:\/\/spintax.net\/spintax-editor\/\">GTW (Generating The Web)<\/a> application<\/li>\n<li>Developed by <a href=\"https:\/\/301.st\">301st<\/a><\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>spintax<\/code> folder to <code>\/wp-content\/plugins\/<\/code><\/li>\n<li>Activate the plugin through the 'Plugins' menu in WordPress<\/li>\n<li>Create templates under the \"Spintax\" menu in the admin sidebar<\/li>\n<li>Embed templates using <code>[spintax slug=\"my-template\"]<\/code> in posts\/pages or <code>spintax_render('my-template')<\/code> in theme files<\/li>\n<\/ol>\n\n<p><strong>Recommended optional dependency:<\/strong> install <a href=\"https:\/\/wordpress.org\/plugins\/action-scheduler\/\">Action Scheduler<\/a> if you plan to use the \"Bulk Apply\" button on ACF \/ post-meta bindings, or schedule bindings via per-binding cron on a site with many matching posts. The plugin works without it \u2014 admins can use the synchronous \"Run now\" button on each binding card, and the same walk is available as <code>wp spintax bindings apply --binding=&lt;id&gt; --all<\/code>. Action Scheduler turns those into one-click chunked async jobs that don't block the request. If you already use WooCommerce or another plugin that bundles Action Scheduler, you're already set; the Bindings page only shows the install notice when AS isn't loaded.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"how%20do%20i%20create%20a%20template%3F\"><h3>How do I create a template?<\/h3><\/dt>\n<dd><p>Go to Spintax &gt; Add New in the WordPress admin. Enter a title and your spintax markup in the editor.<\/p><\/dd>\n<dt id=\"what%20syntax%20does%20the%20plugin%20use%3F\"><h3>What syntax does the plugin use?<\/h3><\/dt>\n<dd><ul>\n<li><code>{a|b|c}<\/code> \u2014 randomly picks one option<\/li>\n<li><code>[a|b|c]<\/code> \u2014 permutation: picks N elements, shuffles, joins with space<\/li>\n<li><code>[&lt;minsize=2;maxsize=3;sep=\", \";lastsep=\" and \"&gt; a|b|c|d]<\/code> \u2014 configured permutation<\/li>\n<li><code>%variable%<\/code> \u2014 variable reference<\/li>\n<li><code>#set %var% = value<\/code> \u2014 local variable, a macro: re-picked at every use<\/li>\n<li><code>#def %var% = value<\/code> \u2014 local variable, picked once per render and held at every use<\/li>\n<li><code>{?VAR?then|else}<\/code> \u2014 conditional: render a branch by truthiness of <code>%VAR%<\/code> (also <code>{?!VAR?then}<\/code> inverted)<\/li>\n<li><code>{plural %Count%: form1|form2|form3}<\/code> \u2014 plural agreement: picks the correct grammatical form by count (RU\/UK\/BE and SR\/HR\/BS 3-form, EN-style 2-form)<\/li>\n<li><code>\/#comment#\/<\/code> \u2014 block comment (stripped from output)<\/li>\n<li><code>#include \"slug\"<\/code> \u2014 embed another template<\/li>\n<\/ul>\n\n<p>Full syntax reference with examples and a live playground: https:\/\/spintax.net\/docs\/syntax<\/p><\/dd>\n<dt id=\"where%20can%20i%20learn%20more%3F\"><h3>Where can I learn more?<\/h3><\/dt>\n<dd><ul>\n<li><strong>Documentation hub:<\/strong> https:\/\/spintax.net\/docs\/ \u2014 guides, reference, recipes<\/li>\n<li><strong>Compact syntax reference:<\/strong> https:\/\/spintax.net\/docs\/syntax \u2014 all primitives in one page (13 languages)<\/li>\n<li><strong>Plural agreement guide:<\/strong> https:\/\/spintax.net\/docs\/plural-spintax\/ \u2014 <code>{plural N: form1|form2|form3}<\/code> in depth (EN\/RU)<\/li>\n<li><strong>Conditional spintax guide:<\/strong> https:\/\/spintax.net\/docs\/conditional-spintax\/ \u2014 <code>{?VAR?then|else}<\/code> value-driven branching (EN\/RU)<\/li>\n<li><strong>Authoring mindset:<\/strong> https:\/\/spintax.net\/docs\/authoring-mindset\/ \u2014 write the final text first, add markup last (EN\/RU)<\/li>\n<li><strong>Live playground:<\/strong> https:\/\/spintax.net\/play\/ \u2014 write a template, set variables, render N variants in your browser (EN\/RU)<\/li>\n<li><strong>Editors &amp; tooling:<\/strong> https:\/\/spintax.net\/spintax-editor\/ \u2014 Spintax Studio for Windows, VS Code and Sublime Text extensions, and the archived GTW application that started the syntax<\/li>\n<li><strong>Engine family:<\/strong> https:\/\/spintax.net\/spintax-engines\/ \u2014 the five standalone libraries (PHP, JavaScript\/TypeScript, Python, .NET, Object Pascal) and how one shared corpus keeps them identical<\/li>\n<li><strong>Spintax in PHP:<\/strong> https:\/\/spintax.net\/spintax-for-php\/ \u2014 the standalone <code>spintax\/core<\/code> package this plugin's engine is kept in step with<\/li>\n<\/ul><\/dd>\n<dt id=\"is%20there%20a%20desktop%20editor%20for%20spintax%20templates%3F\"><h3>Is there a desktop editor for spintax templates?<\/h3><\/dt>\n<dd><p>Yes \u2014 <strong>Spintax Studio<\/strong>, a free native Windows editor built for exactly this syntax: two-pane live preview rendered by a real engine, inline validation with per-error documentation, variable panels, variant counting and export, and built-in help in 14 languages. Install it from the <a href=\"https:\/\/apps.microsoft.com\/detail\/9mw3ch7b530p\">Microsoft Store<\/a>; an overview of all the editor tooling (including VS Code and Sublime Text extensions) lives at https:\/\/spintax.net\/spintax-editor\/<\/p>\n\n<p>Studio embeds <code>spintax-win<\/code> v0.8.1 \u2014 the Object Pascal engine from the same corpus-locked family as this plugin's \u2014 so a template that validates and previews there behaves the same way when this plugin renders it.<\/p><\/dd>\n<dt id=\"does%20caching%20require%20redis%20or%20memcached%3F\"><h3>Does caching require Redis or Memcached?<\/h3><\/dt>\n<dd><p>The plugin uses the WordPress Object Cache API. With a persistent backend (Redis, Memcached), cached output persists across requests. Without one, templates are re-rendered on each page load.<\/p><\/dd>\n<dt id=\"can%20i%20pass%20variables%20through%20shortcodes%3F\"><h3>Can I pass variables through shortcodes?<\/h3><\/dt>\n<dd><p>Yes: <code>[spintax slug=\"greeting\" name=\"Alice\" city=\"Moscow\"]<\/code> makes <code>%name%<\/code> and <code>%city%<\/code> available inside the template.<\/p><\/dd>\n<dt id=\"can%20i%20use%20product%20data%20from%20woocommerce%3F\"><h3>Can I use product data from WooCommerce?<\/h3><\/dt>\n<dd><p>Yes, since 2.2. On a single-product page the plugin auto-detects the current product and exposes it to <code>[spintax]<\/code> and <code>spintax_render()<\/code> as <code>%product_*%<\/code> variables \u2014 for example <code>%product_name%<\/code>, <code>%product_slug%<\/code>, <code>%product_sku%<\/code>, <code>%product_type%<\/code>, <code>%product_stock_status%<\/code>, <code>%product_categories%<\/code>, <code>%product_tags%<\/code>, <code>%product_short_description%<\/code>, and one <code>%product_attribute_&lt;slug&gt;%<\/code> per product attribute. So a template embedded as <code>[spintax slug=\"product-seo-block\"]<\/code> on a product renders that product's data, and the same template on two products gets two separate cached variants.<\/p>\n\n<p>Pricing (<code>%product_price%<\/code> and friends) is intentionally <strong>not<\/strong> exposed: it is volatile commerce data, not generated copy, and folding it into templates would churn the render cache on every price change.<\/p>\n\n<p>To target a specific product regardless of the current page, pass <code>[spintax slug=\"\u2026\" product_id=\"123\"]<\/code>; any explicit variable you pass always overrides the auto-detected one. WooCommerce is optional: with it inactive, or on non-product pages, behavior is unchanged.<\/p>\n\n<p>Since 2.4 the plugin can also <strong>write<\/strong> generated copy into a product \u2014 see the next question. Product loops and cards are still deferred.<\/p><\/dd>\n<dt id=\"can%20spintax%20write%20the%20product%20description%20itself%3F\"><h3>Can Spintax write the product description itself?<\/h3><\/dt>\n<dd><p>Yes, since 2.4. Create a binding with the target kind <strong>WooCommerce product field<\/strong>, on the <strong>Product<\/strong> post type, and pick <strong>Description<\/strong> or <strong>Short description<\/strong>. Every matching product then gets its own rendered copy \u2014 seeded when the field is empty, or regenerated on save if you ask for that \u2014 through the same machinery as ACF and post-meta bindings: cron schedules, Bulk Apply, WP-CLI, and the Logs page.<\/p>\n\n<p>Turn on <strong>Expose WooCommerce product data<\/strong> in the binding's Variables tab and the template can use that product's own facts \u2014 <code>%product_name%<\/code>, <code>%product_sku%<\/code>, <code>%product_type%<\/code>, <code>%product_categories%<\/code>, <code>%product_tags%<\/code>, <code>%product_attribute_&lt;slug&gt;%<\/code> \u2014 so each product gets copy that is actually about <em>it<\/em>, not just a differently-worded version of the same sentence.<\/p>\n\n<p>Three deliberate limits:<\/p>\n\n<ul>\n<li><strong>Only those two fields are writable.<\/strong> Price, SKU, stock and sale dates are commerce data, not copy. A template cannot reach them \u2014 the whitelist is enforced when you save the binding and again before every write.<\/li>\n<li><strong>Manual edits win.<\/strong> With \"Preserve manual edits\" on (the default), a description a human has changed is never overwritten; the binding skips it and says so in the Logs.<\/li>\n<li><strong>Writes go through WooCommerce.<\/strong> Not straight into the database \u2014 so WooCommerce's own caches, lookup tables and save hooks stay consistent, and other plugins that listen for product saves still hear them.<\/li>\n<\/ul>\n\n<p>With WooCommerce deactivated, product bindings simply stop writing. Copy that was already generated stays where it is: by then it is the product's real description, and reverting it would destroy content.<\/p><\/dd>\n<dt id=\"what%20are%20acf%20%2F%20post-meta%20bindings%3F\"><h3>What are ACF \/ post-meta bindings?<\/h3><\/dt>\n<dd><p>A binding pairs a Spintax template (or a per-post inline source) with one target field on one post type \u2014 for example \"Posts \u2192 ACF: hero_subtitle\". Configure it once under Spintax \u2192 Bindings and the plugin populates the field on every matching post on save, on a cron schedule, or on demand via Bulk Apply. Manual edits are preserved by default (hash-tracked); flags control whether the binding auto-seeds empty fields, regenerates on every save, or clears the field when the template renders to empty.<\/p><\/dd>\n<dt id=\"can%20i%20bind%20to%20acf%20fields%3F\"><h3>Can I bind to ACF fields?<\/h3><\/dt>\n<dd><p>Yes. Bindings support both ACF (text \/ textarea \/ wysiwyg, top-level fields) and plain post-meta keys. ACF Free and Pro are both supported; nested fields (repeater \/ flexible_content rows) are not supported in 2.0 \u2014 that lands in a later release. The form-side field picker auto-fills the stable ACF field key so writes work on the first save without ACF's reference-meta handshake.<\/p><\/dd>\n<dt id=\"do%20i%20need%20action%20scheduler%3F\"><h3>Do I need Action Scheduler?<\/h3><\/dt>\n<dd><p>It's a recommended optional dependency for binding-heavy sites. The plugin works without it: admins can run a walk via the synchronous <strong>Run now<\/strong> button on each binding card, or <code>wp spintax bindings apply --binding=&lt;id&gt; --all<\/code> from the CLI. What Action Scheduler adds is chunked async execution, so:<\/p>\n\n<ul>\n<li>The admin <strong>Bulk Apply<\/strong> button can dispatch a non-blocking background job instead of holding the request.<\/li>\n<li>Per-binding cron schedules enqueue an async job instead of running the walk inline on the cron tick \u2014 useful on large catalogues where the synchronous path risks PHP-FPM timeouts.<\/li>\n<\/ul>\n\n<p>Many WP shops already ship Action Scheduler bundled with WooCommerce or other plugins \u2014 check Plugins \u2192 Installed Plugins for \"Action Scheduler\" before installing it separately. If the Bindings admin page shows an \"Action Scheduler is not installed\" notice at the top, you don't have it loaded yet.<\/p><\/dd>\n<dt id=\"what%27s%20the%20difference%20between%20bulk%20apply%20and%20run%20now%3F\"><h3>What's the difference between Bulk Apply and Run now?<\/h3><\/dt>\n<dd><p>Both walk every matching post for a binding and produce the same writes. They differ in <em>how<\/em> the walk runs:<\/p>\n\n<ul>\n<li><strong>Bulk Apply<\/strong> \u2014 dispatches the walk to Action Scheduler as chunked async jobs. The request returns immediately and you can watch progress on the Logs page. Requires Action Scheduler.<\/li>\n<li><strong>Run now<\/strong> \u2014 runs the entire walk synchronously in the current request. No async dependency, but the page blocks until the walk finishes. Available to administrators, and the recommended path on sites without Action Scheduler.<\/li>\n<\/ul>\n\n<p>When Action Scheduler isn't loaded, the Bulk Apply button is disabled with a tooltip pointing at Run now \/ WP-CLI; the stale-source banner on the binding edit form promotes Run now to its primary action.<\/p><\/dd>\n<dt id=\"where%20do%20i%20see%20bulk%20apply%20or%20run%20now%20progress%3F\"><h3>Where do I see Bulk Apply or Run now progress?<\/h3><\/dt>\n<dd><p><strong>Spintax \u2192 Logs<\/strong> in the admin sidebar. Both paths log a completion entry per walk (e.g. <code>Bulk Apply run_synchronously completed for binding &lt;id&gt; \u2014 wrote=N skipped=M cleared=K.<\/code>), plus warnings for partial failures. The Logs page supports level filtering, substring search, and pagination; entries are kept in a ring buffer sized by Settings \u2192 Spintax \u2192 Max log entries.<\/p><\/dd>\n<dt id=\"what%20wp-cli%20commands%20does%20the%20plugin%20add%3F\"><h3>What WP-CLI commands does the plugin add?<\/h3><\/dt>\n<dd><p>Five subcommands under <code>wp spintax bindings<\/code>:<\/p>\n\n<ul>\n<li><code>wp spintax bindings list [--format=table|json|csv]<\/code> \u2014 list all bindings on the site.<\/li>\n<li><code>wp spintax bindings apply --binding=&lt;id&gt; [--all|--post=&lt;id&gt;]<\/code> \u2014 run a binding against every matching post (<code>--all<\/code>) or a single post (<code>--post=&lt;id&gt;<\/code>). This is the synchronous fallback path for Bulk Apply.<\/li>\n<li><code>wp spintax bindings test --binding=&lt;id&gt; --post=&lt;id&gt;<\/code> \u2014 dry-run a binding against one post and report what would be written (target value, rendered preview, skip reason). Same logic as the admin Test panel; use this instead of <code>apply<\/code> when you want a preview.<\/li>\n<li><code>wp spintax bindings export {--binding=&lt;id&gt;|--all} [&gt; bindings.json]<\/code> \u2014 emit one binding or the full store as JSON to stdout, deduped by <code>(post_type, target.key)<\/code>.<\/li>\n<li><code>wp spintax bindings import --file=bindings.json [--overwrite] [--dry-run]<\/code> \u2014 import bindings from JSON. <code>--overwrite<\/code> updates matches on the same target triple; without it, duplicates are skipped. Use <code>--dry-run<\/code> to preview the plan without writing.<\/li>\n<\/ul>\n\n<p>The export\/import pair is the recommended staging\u2192production sync path; bindings are not exposed over REST in 2.0.<\/p><\/dd>\n<dt id=\"what%20variables%20can%20i%20use%20inside%20a%20bound%20template%3F\"><h3>What variables can I use inside a bound template?<\/h3><\/dt>\n<dd><p>A binding template sees four layered variable sources (later layers override earlier ones):<\/p>\n\n<ul>\n<li><strong>Global variables<\/strong> \u2014 the <code>#set<\/code> block in Settings \u2192 Spintax. Site-wide.<\/li>\n<li><strong>Per-binding overrides<\/strong> \u2014 a block of <code>#set<\/code> \/ <code>#def<\/code> lines in the binding's Variables tab. Applies to that binding only.<\/li>\n<li><strong>Post context<\/strong> (opt-in checkbox) \u2014 <code>%post_id%<\/code>, <code>%post_title%<\/code>, <code>%post_url%<\/code>, <code>%post_slug%<\/code>, <code>%post_date%<\/code>, <code>%post_modified%<\/code>, <code>%author_id%<\/code>, <code>%author_name%<\/code>.<\/li>\n<li><strong>ACF sibling fields<\/strong> (opt-in checkbox, ACF-target bindings only) \u2014 every top-level text \/ textarea \/ wysiwyg field in the same ACF group, available as <code>%acf_&lt;field_name&gt;%<\/code>. Siblings are always fresh on save: the binding runs after ACF persists.<\/li>\n<\/ul>\n\n<p>The binding's source can also use the rest of the Spintax syntax (<code>{a|b|c}<\/code>, <code>[a|b]<\/code>, <code>{?VAR?then|else}<\/code>, <code>{plural %N%: \u2026}<\/code>, <code>#include \"slug\"<\/code>, <code>\/#comment#\/<\/code>).<\/p><\/dd>\n<dt id=\"how%20do%20i%20schedule%20bindings%20to%20run%20automatically%3F\"><h3>How do I schedule bindings to run automatically?<\/h3><\/dt>\n<dd><p>Two trigger paths, both configurable per binding under \"Triggers\":<\/p>\n\n<ul>\n<li><strong>Fire on post save<\/strong> (checkbox, default on) \u2014 runs after the post (and ACF, if present) finishes saving. Skipped on autosaves, bulk-edits, batch REST imports, revisions, and trash flips.<\/li>\n<li><strong>Cron schedule<\/strong> (dropdown: disabled \/ hourly \/ twicedaily \/ daily) \u2014 each binding gets its own scheduled tick. With Action Scheduler installed the tick enqueues an async walk; without it, the walk runs synchronously on the cron worker.<\/li>\n<\/ul>\n\n<p>For a one-off \"apply now\", click <strong>Bulk Apply<\/strong> (async, needs Action Scheduler) or <strong>Run now<\/strong> (synchronous, admins) on the binding card.<\/p><\/dd>\n<dt id=\"how%20does%20the%20plugin%20handle%20manual%20edits%20to%20bound%20fields%3F\"><h3>How does the plugin handle manual edits to bound fields?<\/h3><\/dt>\n<dd><p>Each binding signs its last-rendered value and re-checks the target before every write. With <strong>Preserve manual edits<\/strong> enabled (default):<\/p>\n\n<ul>\n<li>If the current value still matches the last render, the binding is free to regenerate.<\/li>\n<li>If the value has been edited outside the binding, the run is skipped and the skip is logged.<\/li>\n<\/ul>\n\n<p>Pair this with <strong>Regenerate on every save<\/strong> for a \"refresh on save unless edited\" workflow. With <strong>Auto-seed empty fields<\/strong> alone, the binding only writes when the target is empty \u2014 manual edits are preserved by definition.<\/p>\n\n<p>Cold-start safety net: when a binding first sees a post with non-empty target content and no prior render on file, it treats the existing value as a manual baseline and skips that post until the field is cleared or the binding's \"Initialize from current value\" flow is run.<\/p><\/dd>\n<dt id=\"i%20edited%20a%20template.%20why%20aren%27t%20the%20changes%20showing%20up%20on%20the%20front%20end%3F\"><h3>I edited a template. Why aren't the changes showing up on the front end?<\/h3><\/dt>\n<dd><p>Bindings are a <strong>pre-generation<\/strong> system, not a render-on-read layer. The rendered string is stored in the target field; consumers (themes, blocks, REST readers) get that stored value directly. Editing the source template doesn't propagate to existing posts until a trigger writes a fresh value to each one.<\/p>\n\n<p>When you edit a template that has bindings pointing at it, the plugin:<\/p>\n\n<ol>\n<li>Bumps an internal render-cache version on each affected binding.<\/li>\n<li>Surfaces an admin notice on the template-edit screen (\"N bindings depend on this template\").<\/li>\n<li>Shows a \"Stale: source template edited\" badge on each affected binding's card.<\/li>\n<\/ol>\n\n<p>To push the new content to existing posts, click <strong>Bulk Apply<\/strong> on each affected binding (or run <code>wp spintax bindings apply --binding=&lt;id&gt; --all<\/code> from the CLI). The Stale badge only clears when the entire walk completes with zero failures \u2014 partial-failure walks keep the badge so you notice the divergence and retry.<\/p><\/dd>\n<dt id=\"is%20there%20a%20hard%20cap%20on%20bindings%3F\"><h3>Is there a hard cap on bindings?<\/h3><\/dt>\n<dd><p>200 bindings per site. The store is a single autoloaded option (~500 bytes per binding), and the cap keeps autoload memory bounded. If you genuinely need more, please open an issue with your use case.<\/p><\/dd>\n<dt id=\"which%20fields%20can%27t%20i%20bind%20to%3F\"><h3>Which fields can't I bind to?<\/h3><\/dt>\n<dd><p>The form rejects a handful of unsafe targets at save time:<\/p>\n\n<ul>\n<li>WordPress-internal meta keys (anything starting with <code>_wp_<\/code>, <code>_edit_<\/code>, <code>_oembed_<\/code>, etc.).<\/li>\n<li>Plugin-internal <code>_spintax_*<\/code> slots used to store source, signatures, and cache versions.<\/li>\n<li><code>wp_posts<\/code> columns like <code>post_title<\/code>, <code>post_content<\/code>, <code>post_excerpt<\/code>. These are not post-meta and writing to them via the meta API would silently create shadow rows.<\/li>\n<li>The same target name already bound by another binding \u2014 one binding per (post type, target field), whether the kind is ACF or post-meta.<\/li>\n<li>For ACF targets: the stable ACF field key must be present and resolvable when ACF is loaded.<\/li>\n<\/ul><\/dd>\n<dt id=\"on%20multisite%2C%20are%20bindings%20shared%20across%20the%20network%3F\"><h3>On multisite, are bindings shared across the network?<\/h3><\/dt>\n<dd><p>No \u2014 bindings are per-site. Each subsite manages its own. Use <code>wp --url=site2 spintax bindings import --file=site1-bindings.json<\/code> to copy bindings between subsites via the WP-CLI export\/import round-trip.<\/p><\/dd>\n<dt id=\"can%20i%20manage%20bindings%20via%20rest%3F\"><h3>Can I manage bindings via REST?<\/h3><\/dt>\n<dd><p>Not in 2.0; bindings are admin-only. The <code>wp spintax bindings<\/code> WP-CLI surface covers staging\u2192production sync scenarios. REST API exposure is tracked for a later release.<\/p><\/dd>\n<dt id=\"i%27m%20coming%20from%20%60nested-spintax-for-acf%60.%20is%20there%20a%20migration%20path%3F\"><h3>I'm coming from `nested-spintax-for-acf`. Is there a migration path?<\/h3><\/dt>\n<dd><p>Yes. After activating Spintax 2.0, a dismissible admin banner points to <strong>Tools \u2192 Spintax Migration<\/strong>. The wizard scans for predecessor data, shows a per-row preview, and creates bindings deduped by <code>(post type, target field)<\/code>. Per-post sources and variables are copied non-destructively \u2014 the old plugin's data stays in place until you delete it.<\/p><\/dd>\n<dt id=\"can%20i%20use%20the%20same%20engine%20outside%20wordpress%3F\"><h3>Can I use the same engine outside WordPress?<\/h3><\/dt>\n<dd><p>Yes \u2014 the engine is published as a family of standalone open-source libraries, so a template you author here renders identically elsewhere:<\/p>\n\n<ul>\n<li><strong>PHP:<\/strong> <code>composer require spintax\/core<\/code> \u2014 https:\/\/packagist.org\/packages\/spintax\/core<\/li>\n<li><strong>JavaScript \/ TypeScript:<\/strong> <code>npm i @spintax\/core<\/code> \u2014 https:\/\/www.npmjs.com\/package\/@spintax\/core<\/li>\n<li><strong>Python:<\/strong> <code>pip install spintax-core<\/code> \u2014 https:\/\/pypi.org\/project\/spintax-core\/<\/li>\n<li><strong>.NET:<\/strong> <code>dotnet add package Spintax.Core<\/code> \u2014 https:\/\/www.nuget.org\/packages\/Spintax.Core \u2014 <code>netstandard2.0<\/code> and <code>net472<\/code>, so .NET Framework 4.7.2+ and every .NET since<\/li>\n<li><strong>Object Pascal:<\/strong> <code>spintax-win<\/code> v0.8.1 \u2014 https:\/\/github.com\/investblog\/spintax-win \u2014 the engine inside Spintax Studio<\/li>\n<li><strong>OpenCart 3.x:<\/strong> a separate extension built on the same engine.<\/li>\n<\/ul>\n\n<p>All five libraries are MIT-licensed and dependency-free. Together with this plugin they make six independent engines held to a shared golden corpus \u2014 one set of fixtures every engine must reproduce, enforced in continuous integration \u2014 so \"renders identically\" is a verified guarantee rather than an intention. The family is described at https:\/\/spintax.net\/spintax-engines\/. Handy when a headless front end, a CLI job, or a non-WordPress site has to produce the same copy as your WordPress pages.<\/p><\/dd>\n<dt id=\"can%20i%20use%20spintax%20in%20n8n%2C%20or%20from%20an%20ai%20agent%3F\"><h3>Can I use spintax in n8n, or from an AI agent?<\/h3><\/dt>\n<dd><p>Yes \u2014 the same engine also ships as automation and agent tooling, held to the same corpus as this plugin:<\/p>\n\n<ul>\n<li><strong>n8n:<\/strong> the community node <code>n8n-nodes-spintax<\/code> renders, validates and checks templates inside a workflow \u2014 one unique message per row of a sheet, for example. Guide: https:\/\/spintax.net\/spintax-for-n8n\/<\/li>\n<li><strong>MCP (AI agents):<\/strong> <code>@spintax\/mcp<\/code> lets Claude, Cursor or any MCP-capable agent validate and render a template before it is published \u2014 hosted at <code>https:\/\/spintax.net\/mcp<\/code>, or locally via <code>npx @spintax\/mcp<\/code>. Guide: https:\/\/spintax.net\/spintax-mcp\/<\/li>\n<li><strong>Drafting with an LLM:<\/strong> <code>@spintax\/authoring-prompt<\/code> is the maintained prompt for writing templates in this syntax; the approach is written up at https:\/\/spintax.net\/ai-spintax-templates\/<\/li>\n<\/ul>\n\n<p>A template drafted anywhere in that toolchain pastes straight into this plugin and renders the same way.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>3.1.0<\/h4>\n\n<ul>\n<li><strong>Engine catch-up: the built-in engine is back in step with <code>spintax\/core<\/code> 0.8.0.<\/strong> Six fixes that already shipped across the family \u2014 Composer 0.6\u20130.8, npm (<code>@spintax\/core<\/code> 0.4\u20130.6), PyPI, Object Pascal and .NET \u2014 every one locked by the shared cross-engine corpus.<\/li>\n<li><strong>Fix: a 62-character template could exhaust memory in the validator.<\/strong> <code>#set %a% = %b% %b%<\/code> over <code>#set %b% = %a% %a%<\/code> doubles the text on every expansion pass, and there is no cycle, so the circular-reference guard never fired. Plural-form expansion now stops at 64 KB and reports the count as unknowable instead of dying. Every engine in the family had this.<\/li>\n<li><strong>Fix: the same template could exhaust memory at render.<\/strong> A render now expands at most 1 MB of <code>%variable%<\/code> text; past that a reference is left as a literal <code>%name%<\/code> \u2014 exactly what an undefined name already does. The budget is per render and shared by <code>#include<\/code>d templates, so a nested include cannot reset it.<\/li>\n<li><strong>Fix: plural forms are counted after definitions expand, the way rendering counts them.<\/strong> <code>#def %tail% = few|many<\/code> with <code>{plural 2: one|%tail%}<\/code> under <code>ru<\/code> rendered correctly yet was reported as the wrong number of forms. The validator now substitutes definitions first \u2014 and only where the count is provably fixed; a value carrying brackets suppresses the verdict instead of guessing. Templates that were wrongly flagged are now valid; the one new error is a <code>#set<\/code> whose value smuggles extra <code>|<\/code>-separated forms into a block, which always rendered as fullwidth braces anyway.<\/li>\n<li><strong>Fix: one circular-reference error per variable, not per path.<\/strong> A converging chain of definitions feeding a cycle produced an exponential number of identical errors \u2014 457 bytes of template, 524,288 diagnostics. Now one per name, with the printed route capped at eight names.<\/li>\n<li>New in the validator API: a <code>{plural \u2026}<\/code> block with a non-default form count validated <strong>without a locale<\/strong> now carries a warning instead of passing silently. The template editor always passes the template's own locale, so editors keep getting the definite verdict as before.<\/li>\n<li><strong>Fix: a render that hit the expansion budget is no longer stored in the object cache.<\/strong> A child template cut short inside a large parent could otherwise be served truncated, under its own cache key, to a page that renders it alone and could afford it in full. Found in the release review; a debug log line now records the truncation.<\/li>\n<li>Tested up to WordPress 7.1. Tests: +22 since 3.0.2 (711 PHPUnit); the shared cross-engine corpus stands at 248 cases.<\/li>\n<li>Listing refresh: the .NET engine (<code>Spintax.Core<\/code> on NuGet) joins the family list, Spintax Studio's engine version is current, and a new FAQ covers the n8n node and the MCP server.<\/li>\n<\/ul>\n\n<h4>3.0.2<\/h4>\n\n<ul>\n<li><strong>Engine catch-up: the plugin's built-in engine is back in step with the standalone <code>spintax\/core<\/code> 0.5.2.<\/strong> The standalone engines (Composer, npm, PyPI, Object Pascal) had moved ahead of the plugin; every change below ships identically across the family and is locked by the shared cross-engine corpus.<\/li>\n<li><strong>Fix: a circular <code>#set<\/code> no longer publishes an empty render.<\/strong> A template whose definitions reference each other in a cycle used to render as an empty string; it now stops expanding at the depth budget and emits the partially-expanded text with the unresolved reference left visible \u2014 what every other engine in the family already does. The validator still reports the cycle as an error.<\/li>\n<li><strong>Fix: directive and <code>#include<\/code> recognition follows the family grammar exactly.<\/strong> Variable names are ASCII, as documented \u2014 <code>#set %\u0438\u043c\u044f% = \u2026<\/code> was silently accepted (and expanded) by this engine alone while being an error to every other; the editor now reports it and the line renders as text. Likewise an <code>#include<\/code> separated by a non-breaking space is plain text rather than an include, a CRLF line ending no longer leaks a carriage return into a directive's value, and a stray control character before a <code>#set<\/code> no longer flags a valid template as malformed.<\/li>\n<li><strong>Much faster validation of large templates.<\/strong> The circular-reference walk and the plural-agreement analysis are now iterative: a 1,600-definition chain validates in 86 ms where it previously took 15.7 s, and definition shapes that previously hung the validator complete in seconds. Line-number reporting scales linearly too. Diagnostic output is byte-identical \u2014 order, count and messages verified against the previous engine on a 464-document differential.<\/li>\n<\/ul>\n\n<p>Earlier releases (3.0.1 back to 1.0.0) are listed in full in <code>CHANGELOG.md<\/code> in the plugin's GitHub repository: https:\/\/github.com\/investblog\/spintax\/blob\/main\/CHANGELOG.md<\/p>","raw_excerpt":"Generate unique content at scale with spintax \u2014 bind templates to ACF &amp; post-meta fields, pull WooCommerce product data, Bulk Apply, cron, WP-CLI.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/294363","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=294363"}],"author":[{"embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/301st"}],"wp:attachment":[{"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=294363"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=294363"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=294363"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=294363"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=294363"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ja.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=294363"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}