Skip to content

Dynamic Shortcodes

Turbo does not expand Dynamic Shortcodes at bake/render time for visitor-specific values. Instead, raw {type:…} expressions (and optional [dsh]…[/dsh]) become client stubs that hydrate through WordPress — same idea as xAPI actor session / Gravity Forms submit.

  • Plugin Dynamic Shortcodes active.
  • Filter wp2turbo/dsh/enable (default: on when the plugin is present).

Write Dynamic Shortcodes as usual in Gutenberg content:

Привет, {user:display_name}!
{if:{user:ID} "Вы вошли" "Гость"}
  • Embed another post’s body as a Turbo fragment (not raw Gutenberg markup):
{post:content@id=123}

Also: {post:post_content@id=123}, {post:content-rendered@id=123}. Without @id, the expand context post is used.

Put the shortcode in its own block (not inside a paragraph with other text). Block-level embeds are hoisted out of <p> automatically, but a dedicated block is cleaner.

Blocking: embed HTML is wrapped in [data-turbo-dsh-embed]. Nested tabs/details/questions inside the embed do not gate the parent page’s «Продолжить» (requireContentComplete). Blocking gates inside the embed are initialized after hydrate.

In Custom HTML (core/html / «Произвольный код»), shortcodes inside tags stay valid — the whole element becomes one island. If the markup has width/height (or those in style), the skeleton uses them:

<img src="{user:avatar-url}" width="96" height="96" alt="">

Optional WP wrapper still works as one island:

[dsh]Привет, {user:display_name}[/dsh]

<script>, <style>, and HTML comments are skipped so CSS/JS braces are not wrapped.

  1. StaticPageRenderer → DynamicShortcodesContentProcessor parses via DSH API and emits <span data-turbo-dsh … data-turbo-dsh-post-id="…">.
  2. StaticAssetCollector loads dsh-expand.js + inline window.wp2turboDsh.
  3. Client waits for wp2turbo:xapi-actor-session (if enabled), then reads a sessionStorage cache keyed by decoded island src + viewer scope (xAPI actor email, else guest). Cache stores { html, assets?, hash? }; hits call ensureAssets then apply with no network.
  4. Misses refresh nonces via wp2t_boot (same as GF), then batch-expand. Response may include aggregated assets (styles/scripts for Turbo post embeds). Successful HTML (+ assets/hash) is written back to sessionStorage (max 100 entries). Cache clears when the xAPI viewer identity changes.
  5. {post:content*}: server runs StaticPageRenderer::renderFragment (content pipeline, no page shell). Fragments are object-cached by postId + contentHash + skin + lang (stubs inside stay stubs). Limits: max depth 3, max 10 unique embed posts per expand request; identical @id batched once. Nested stubs re-hydrate (depth ≤ 3).
  6. Shared transport with Gravity Forms (assets/src/transport.js): REST first; on static package path or after 405 → heartbeat data[wp2t_dsh]. Flag window.wp2turboTransport.restDead stops further REST attempts for the page.
  7. Privileges none (Power Shortcodes {pw:…} work as in DSH). Draft/private posts are not embedded.
  • Config: window.wp2turboDsh = { restUrl, nonce, postId }
  • Event after hydrate: wp2turbo:dsh-expanded (detail.cached / detail.fetched)
  • Expand response: { ok, expansions: [{ id, html, hash? }], assets?, nonce }
  • JS: onepix/wp2lms/src/Modules/Turbo/assets/src/dsh-expand.js, dsh-cache.js
  • CSS stub: .wp2turbo-dsh[aria-busy="true"] in skin components.css
  • During Turbo render, native DSH expand on render_block / the_content (and the [dsh] WP shortcode) is temporarily suspended so raw expressions reach the stub processor. Hydration then expands them with the live user/session.
  • If you rely on DSH expand outside Turbo pages, those filters stay intact after render.