外掛說明
Spintax is a WordPress plugin for template-based content generation using spintax markup. Create reusable templates with randomised text variants, variable substitution, and permutation logic — then embed them anywhere on your site via shortcodes or PHP.
Key features:
- Enumerations
{a|b|c}— randomly pick one option, with nesting support - Permutations
[<config>a|b|c]— pick N elements, shuffle, join with custom separators - Variables
%var%— global, local (#set), and shortcode-level variable scopes - Conditionals
{?VAR?then|else}— render a branch based on whether a variable is set (also{?!VAR?then}inverted) - Plural agreement
{plural <count>: form1|form2|form3}— pick grammatically correct noun form by count. RU/UK/BE 3-form (one|few|many), EN-style 2-form (one|many). First spintax engine with first-class plurals. - Nested templates — embed templates within templates via
#includeor[spintax] - ACF / post-meta bindings (NEW in 2.0) — 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.
- Object cache — rendered output cached via WP Object Cache API (Redis/Memcached ready)
- Cron regeneration — optional scheduled cache refresh per template, plus per-binding cron walks
- WP-CLI —
wp spintax bindings list|apply|test|export|import - Validation — bracket matching, circular reference detection, syntax checking
- Admin UI — code editor, live preview, shortcode copy, settings page, bindings list
Syntax based on the GTW (Generating The Web) standard.
External services
This plugin does not connect to any external services, APIs, or third-party servers.
All content generation happens locally on your WordPress server. No data is sent externally. No remote requests are made during activation, rendering, or caching.
Privacy Policy
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.
Templates and their rendered output are stored entirely within your WordPress database and object cache.
Credits
- Syntax based on the GTW (Generating The Web) standard
- Developed by 301st
螢幕擷圖
安裝方式
- Upload the
spintaxfolder to/wp-content/plugins/ - Activate the plugin through the ‘Plugins’ menu in WordPress
- Create templates under the “Spintax” menu in the admin sidebar
- Embed templates using
[spintax slug="my-template"]in posts/pages orspintax_render('my-template')in theme files
Recommended optional dependency: install Action Scheduler 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 — Bulk Apply falls back to a WP-CLI command and cron walks run synchronously — but Action Scheduler gives you one-click admin Bulk Apply and chunked async cron walks. 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.
常見問題集
-
How do I create a template?
-
Go to Spintax > Add New in the WordPress admin. Enter a title and your spintax markup in the editor.
-
What syntax does the plugin use?
-
{a|b|c}— randomly picks one option[a|b|c]— permutation: picks N elements, shuffles, joins with space[<minsize=2;maxsize=3;sep=", ";lastsep=" and "> a|b|c|d]— configured permutation%variable%— variable reference#set %var% = value— local variable definition{?VAR?then|else}— conditional: render a branch by truthiness of%VAR%(also{?!VAR?then}inverted){plural %Count%: form1|form2|form3}— plural agreement: picks the correct grammatical form by count (RU 3-form, EN 2-form)/#comment#/— block comment (stripped from output)#include "slug"— embed another template
Full syntax reference with examples and a live playground: https://spintax.net/docs/syntax
-
Where can I learn more?
-
- Documentation hub: https://spintax.net/docs/ — guides, reference, recipes
- Compact syntax reference: https://spintax.net/docs/syntax — all primitives in one page (13 languages)
- Plural agreement guide: https://spintax.net/docs/plural-spintax/ —
{plural N: form1|form2|form3}in depth (EN/RU) - Conditional spintax guide: https://spintax.net/docs/conditional-spintax/ —
{?VAR?then|else}value-driven branching (EN/RU) - Authoring mindset: https://spintax.net/docs/authoring-mindset/ — write the final text first, add markup last (EN/RU)
- Live playground: https://spintax.net/play/ — write a template, set variables, render N variants in your browser (EN/RU)
-
Does caching require Redis or Memcached?
-
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.
-
Can I pass variables through shortcodes?
-
Yes:
[spintax slug="greeting" name="Alice" city="Moscow"]makes%name%and%city%available inside the template. -
What are ACF / post-meta bindings?
-
A binding pairs a Spintax template (or a per-post inline source) with one target field on one post type — for example “Posts ACF: hero_subtitle”. Configure it once under Spintax 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.
-
Can I bind to ACF fields?
-
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 — 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.
-
Do I need Action Scheduler?
-
It’s a recommended optional dependency for binding-heavy sites. The plugin works without it, but two features degrade:
- The admin Bulk Apply button uses Action Scheduler to dispatch chunked async jobs. Without AS, the button returns an error pointing at the WP-CLI fallback (
wp spintax bindings apply --binding=<id> --all). - Per-binding cron schedules still fire, but the cron callback runs the walk synchronously instead of enqueueing an async job. On large catalogues that risks PHP-FPM timeouts on the cron worker.
Many WP shops already ship Action Scheduler bundled with WooCommerce or other plugins — check Plugins 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; install Action Scheduler to make one-click admin Bulk Apply and async cron walks available.
- The admin Bulk Apply button uses Action Scheduler to dispatch chunked async jobs. Without AS, the button returns an error pointing at the WP-CLI fallback (
-
What WP-CLI commands does the plugin add?
-
Five subcommands under
wp spintax bindings:wp spintax bindings list [--format=table|json|csv]— list all bindings on the site.wp spintax bindings apply --binding=<id> [--all|--post=<id>] [--dry-run]— run a binding against all matching posts (or a single post), with optional dry-run. This is the no-Action-Scheduler fallback path for Bulk Apply.wp spintax bindings test --binding=<id> --post=<id>— dry-run a binding against one post and report whatBindingApplier::plan()would do (would_write, current value, rendered preview, skip reason). Same logic as the admin Test panel.wp spintax bindings export [--format=json] [> bindings.json]— emit the full bindings store as JSON, deduped by(post_type, target.key).wp spintax bindings import --file=bindings.json [--overwrite] [--dry-run]— import bindings from JSON.--overwriteupdates matches on the same target triple; without it, duplicates are skipped. Use--dry-runto preview the plan without writing.
The export/import pair is the recommended stagingproduction sync path; bindings are not exposed over REST in 2.0.
-
What variables can I use inside a bound template?
-
A binding template renders with four layered variable sources (later layers override earlier ones — see spec §4.3):
- Global variables — the
#setblock in Settings Spintax Global Variables. Site-wide. - Per-binding overrides — a
#setblock in the binding’s Variables “Per-binding #set overrides” textarea. Applies to that binding only. - Post context (opt-in via the binding’s “Expose post context as %vars%” checkbox) —
%post_id%,%post_title%,%post_url%,%post_slug%,%post_date%,%post_modified%,%author_id%,%author_name%. - ACF sibling fields (opt-in via “Expose ACF sibling fields as %acf_%”) — every top-level ACF text/textarea/wysiwyg field in the binding’s post type group, available as
%acf_<field_name>%. Reads happen after ACF persists its values (save_post priority 20 hook), so siblings are always fresh on save_post triggers. Only meaningful for ACF-target bindings.
A binding’s source can also use the rest of the Spintax syntax (
{a|b|c},[a|b],{?VAR?then|else},{plural %N%: ...},#include "slug",/#comment#/). - Global variables — the
-
How do I schedule bindings to run automatically?
-
Two trigger paths, both configurable per binding under “Triggers”:
- Fire on post save (checkbox, default on) — hooks
save_postpriority 20. Runs after ACF persists its own field values, so sibling reads see fresh data. Skipped during autosave / bulk-edit / REST batch imports / revisions / trash flips. - Cron schedule (dropdown: disabled / hourly / twicedaily / daily) — each binding gets its own WP-Cron hook
spintax_binding_cron_<binding_id>. On the scheduled tick, the callback enqueues an Action Scheduler walk (or runs synchronously if AS isn’t installed — see the Action Scheduler FAQ above). Independent of save_post; use this to refresh content periodically without an editor touch.
For one-off “apply now” operations, click Bulk Apply on the binding card. The button needs Action Scheduler; without it, the admin notice points at the WP-CLI fallback.
- Fire on post save (checkbox, default on) — hooks
-
How does the plugin handle manual edits to bound fields?
-
Each binding tracks a SHA-1 signature of its last-rendered value in post-meta
_spintax_last_render_sig_<binding_id>. On every subsequent run withPreserve manual editsenabled (default), it compares the current target value’s hash to the stored signature:- If the hashes match, the value hasn’t been touched outside the binding — safe to regenerate.
- If they differ, treat it as a manual edit; skip with
SKIP_MANUAL_EDIT_DETECTEDand log the skip.
Combined with
Regenerate on every save, this gives a “refresh on save unless edited” workflow. WithAuto-seed empty fieldsinstead, the binding only writes when the target is empty — manual edits are preserved by definition because they’re never overwritten.There is a “cold-start” exception: when a binding first sees a post with non-empty target content and no signature yet, it treats the existing value as an unwritten manual baseline and skips (
SKIP_COLD_START_MANUAL) until the editor clicks the binding’s “Initialize from current value” path (a later UI addition) or accepts the regeneration by clearing the field first. -
I edited a template. Why aren’t the changes showing up on the front end?
-
Bindings are a pre-generation 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.
When you edit a template that has bindings pointing at it, the plugin:
- Bumps an internal render-cache version on each affected binding.
- Surfaces an admin notice on the template-edit screen (“N bindings depend on this template”).
- Shows a “Stale: source template edited” badge on each affected binding’s card.
To push the new content to existing posts, click Bulk Apply on each affected binding (or run
wp spintax bindings apply --binding=<id> --allfrom the CLI). The Stale badge only clears when the entire walk completes with zero failures — partial-failure walks keep the badge so you notice the divergence and retry. -
Is there a hard cap on bindings?
-
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.
-
Which fields can’t I bind to?
-
The form rejects five tiers of reserved keys at save time:
- WordPress-internal meta — keys starting with
_wp_,_edit_,_oembed_, plus_pingme,_encloseme,_thumbnail_id. - Plugin-internal meta —
_spintax_*prefixes (source, signature, cache-version slots used by other bindings). - wp_posts columns —
post_title,post_content,post_excerpt,post_name,post_status,post_date,post_modified,post_parent,post_author,post_type,post_password, etc. These aren’t post-meta and writing to them viaupdate_post_meta()silently creates shadow rows. - Cross-binding uniqueness — only one binding per
(post type, target key), regardless of whether the kind is ACF or post_meta (they share the same database row). - ACF field key validity — when binding to an ACF field, the stable field key (e.g.
field_5f8a1234abcd) is required, and verified againstacf_get_field()when ACF is loaded.
- WordPress-internal meta — keys starting with
-
No — bindings are per-site. Each subsite manages its own. Use
wp --url=site2 spintax bindings import --file=site1-bindings.jsonto copy bindings between subsites via the WP-CLI export/import round-trip. -
Can I manage bindings via REST?
-
Not in 2.0; bindings are admin-only. The
wp spintax bindingsWP-CLI surface covers stagingproduction sync scenarios. REST API exposure is tracked for a later release. -
I’m coming from `nested-spintax-for-acf`. Is there a migration path?
-
Yes. After activating Spintax 2.0, a dismissible admin banner points to Tools Spintax Migration. The wizard scans for predecessor data, shows a per-row preview, and creates bindings deduped by
(post type, target field). Per-post sources and variables are copied non-destructively — the old plugin’s data stays in place until you delete it.
使用者評論
這個外掛目前沒有任何使用者評論。
參與者及開發者
變更記錄
2.0.3
- Fix: ACF target validation now runs on every apply, not just at form save.
BindingApplier::plan()rejects bindings whose storedtarget.field_keyno longer resolves to a field with the expected name (deleted, renamed, or re-assigned in ACF). Two new return codes:skip_acf_not_loaded(ACF deactivated since the binding was saved) andskip_invalid_acf_field(key + name disagreement). Closes a path where CLI-imported or imported-while-ACF-inactive bindings could write throughupdate_field()to the wrong field. - Fix:
BindingApplier::read_target()and::write_target()no longer fall back to plainupdate_post_meta()/get_post_meta()forkind = acf_fieldwhen ACF isn’t loaded. The applier short-circuits at the runtime guard above, so the low-level methods are the sole writer for verified targets. Pre-2.0.3 the silent fallback could write the rendered value to a post-meta row ACF would never see again. - Fix: Bulk Apply now tracks failures cumulatively across chunks via a persistent
_spintax_binding_walk_failed_v_<id>flag. The final chunk gatesstamp_last_applied_version()on the cumulative flag. 2.0.1 only checked the current chunk, so a multi-chunk walk that failed in chunk 1 and succeeded in the final chunk would still clear the Stale badge. - Fix: Concurrent Bulk Apply walks on the same binding are now refused with
WP_Error 'walk_in_progress'. Bothenqueue()andrun_synchronously()acquire a per-binding lock (option_spintax_binding_walk_lock_<id>) at walk start; stale locks older than one hour are auto-overwritten so a crashed walk doesn’t permanently jam the binding. - Internal: 11 new PHPUnit cases — runtime ACF guard, multi-chunk failure tracking, walk-lock acquisition / release, stale-lock recovery. 441 tests total (was 430).
- Tooling:
npm run lint:phpandlint:php:fixmoved toscripts/lint-php.sh/scripts/lint-php-fix.sh. The inline command tripped over bash-c quoting on Windows..gitattributesenforces LF endings on shipped text files. - Internal: CLI
wp spintax bindings import --overwritehelp text updated to reflect the 2.0.1(post_type, target.key)uniqueness contract.
2.0.2
- Docs: new FAQ entries — Action Scheduler dependency, full
wp spintax bindingsWP-CLI surface, variable scopes (global / per-binding / post context / ACF siblings), trigger options (save_post + per-binding cron), manual edit detection, template-edit propagation, reserved-key tiers. - Docs: Installation section now flags Action Scheduler as a recommended optional dependency with the specific features it enables.
- UX: Spintax Bindings shows an info notice at the top of the page when Action Scheduler isn’t loaded, explaining the two features that degrade (admin Bulk Apply, async cron walks) and linking to the install screen. Notice disappears when AS is loaded by any source (direct install, WooCommerce / Jetpack bundle, mu-plugin, etc.).
- Internal: no functional changes to the bindings engine or core spintax engine — patch is documentation + a single admin-page notice.
2.0.1
- Fix: ACF and post-meta bindings on the same
(post_type, field name)no longer coexist — they wrote to the same database row and silently raced. Tier 4 uniqueness now ignorestarget.kind. Existing pre-2.0.1 conflicts remain in the data store but the next save of either binding will reject. - Fix: ACF bindings now require a non-empty
target.field_keyand validate it against the live ACF field when ACF is loaded. Previously a missing or mistyped field key could routeupdate_field()writes to a different field. - Fix: Test panel and Bulk Apply now report
skip_out_of_scope_type/skip_out_of_scope_statusfor posts that wouldn’t match the binding’s scope in live triggers. Two new applier return codes — total now 11 instead of 9. - Fix: Bulk Apply only clears the Stale badge when the walk had zero failures. Partial-failure walks keep the binding flagged so editors notice the divergence and retry.
- Fix: Binding form validation errors no longer throw the editor back to the list view — the form re-renders with submitted values via a short-lived transient flash, with the specific error inline.
- Internal: 21 new PHPUnit cases covering each fix path; bindings unit suite is now exhaustive on scope-filter, cross-kind dedup, ACF field_key validation, and Bulk Apply stamp gating.
2.0.0
- ACF / post-meta bindings — a Spintax template (or a per-post inline source) can now be bound to any ACF text/textarea/wysiwyg field or post-meta key on a post type. Configure once under Spintax Bindings and the plugin populates the field on save, cron, or via Bulk Apply.
- Decision-tree write behaviour with four flags:
auto_seed_empty(default on; never clobbers existing content),regenerate_on_save,preserve_manual_edits(hash-tracks the last rendered value so external edits are detected),clear_on_empty. Cold-start behaviour documented to avoid false manual-edit positives. - Per-binding cron schedules (hourly / twicedaily / daily) registered as individual
wp_schedule_eventhooks per binding. - Bulk Apply via Action Scheduler with chunked processing; a clean WP-CLI fallback when Action Scheduler isn’t installed.
- New
%post_id%,%post_title%,%post_url%,%post_slug%,%post_date%,%post_modified%,%author_id%,%author_name%post-context variables — opt-in per binding. - New
%acf_<field_name>%variables — opt-in per binding, exposes ACF sibling fields in the same group. - Template-edit cascade — editing a Spintax template that is referenced by bindings bumps an internal cache version and surfaces a notice telling the editor that stored target fields will refresh on the next Bulk Apply / cron / save_post.
wp spintax bindings list|apply|test|export|import— full WP-CLI surface for stagingproduction workflows and Action-Scheduler-less environments.- One-shot migration helper at Tools Spintax Migration for users coming from the predecessor plugin
nested-spintax-for-acf. Detects, previews, and imports legacy data deduped by(post_type, target.key). Original predecessor data is never deleted by the migration. - Reserved-key guard rejects WP-internal meta keys, plugin-internal
_spintax_*prefixes, wp_posts column names, and duplicate(post_type, target.kind, target.key)triples at form save. - Hard cap of 200 bindings per site (single autoloaded option size budget).
- Per-binding chunk size override in the Advanced form section.
- Uninstall cleans every bindings option family and sibling post-meta — no orphan rows left behind.
- Internal: 398+ PHPUnit tests, including exhaustive decision-tree coverage and migration import edge cases.
1.5.0
- Add: plural agreement primitive
{plural <count>: form1|form2|form3}— pick the correct grammatical form by count. RU/UK/BE = 3 forms (one|few|many); EN/ES/PT/DE etc. = 2 forms (one|many). Count is a%var%reference or literal integer (resolved after variable expansion, so helper-var patterns via#setwork). Locale comes from per-template post meta_spintax_localeor the WordPress site locale. Lenient at runtime: malformed constructs render verbatim with fullwidth braces instead of crashing the page. First spintax engine to treat plural as a first-class primitive. - Add: validator surface for plural blocks — structural check (form slot rejects nested
{},[]) always on; arity check (RU expects 3, EN expects 2) when locale is known. - Internal: 74 PHPUnit cases mirroring the canonical TS implementation (
spintax-plurals.test.tsin casino-platform). Engine classesPlurals,PluralArityError,PluralFormErrorship alongsideConditionalsfrom 1.4.0.
1.4.0
- Add: conditional syntax
{?VAR?then|else}— render a branch based on whether a variable is set/non-empty (also{?!VAR?then}for inverted, optional else). Resolves both before and after%var%expansion, so conditionals inside variable values work too. - Add: single-token abbreviation whitelist in post-processing — known shorthands like
соц.,эл.,Mr.,Inc.no longer trigger sentence-end capitalisation of the next word. Covers Russian editorial/address/unit shorthands plus English titles and business suffixes. - Fix:
#setdirective with an empty value (#set %x% =) no longer silently swallows the next directive on the following line. - Fix: HTML start tags inside permutation alternatives (e.g.
[<li>item</li>|<li>...]) are no longer mis-parsed as a<config>block. - Improve: cache description in template meta box and global settings now explains that visitors see the same generated variant per runtime context until expiry or regeneration.
- Internal: regression tests for IDN domains flanked by Cyrillic letters and for randomisation behaviour across renders.
1.1.0
- Add: per-element permutation separators — assign custom separator to each element via
< sep >before| - Add: auto-spacing for purely alphabetic word separators (e.g.
<and>,<или>) - Security: sanitize raw spintax input with custom sanitize_spintax() — strips invalid UTF-8, null bytes, and control characters while preserving angle-bracket syntax
1.0.1
- Fix: permutation minsize/maxsize logic when only one parameter is specified
- Fix: preview rendering no longer strips spintax config from template input
- Fix: child templates no longer inherit parent’s local #set variables
- Improve: global variables editor now uses #set textarea (paste full blocks)
- Improve: validation errors displayed on template edit screen with line numbers
- Improve: “Regenerate Public Cache” now forces fresh subtree render
- Add: demo template created on first activation
- Add: SECURITY.md with responsible disclosure policy
- Add: Privacy Policy and External Services sections in readme.txt
- Code: PHPCS 0 errors, full WP.org review compliance
1.0.0
- Initial release
- GTW-compatible spintax engine with nested enumerations and permutations
- Template CPT with code editor and admin preview
- Shortcode and PHP rendering API
- Object cache with versioned keys and cascade invalidation
- Per-template cron regeneration
- Global and local variable scopes
- Settings page with global variables editor



