Inserts
An insert is a WordPress block comment in the op/ namespace that drops live markup into a frame: a menu, a list, the page content, another frame. Here is the syntax and every insert with its attributes.
Syntax
A frame is fixed markup, while menus, languages, lists and the page itself change. The live parts are inserts: a standard WordPress block comment in the op/ namespace, the same grammar every Gutenberg block is stored in.
<!-- wp:op/menu {"place":"header","depth":1} /-->
There is no template language of our own. Parsing is the core parse_blocks(), substitution is render_block(), and each insert is registered with register_block_type() from its block.json. A block comment is invisible in the browser, survives an export from a page builder, does not clash with the curly braces of JS templates, and passes through the markdown converter untouched.
Two forms exist. A self-closing insert ends with /-->. An insert with a body is a pair, and the body is its template or content:
<!-- wp:op/repeat {"source":"faq"} -->
<details><summary data-op-bind='{"text":"item.q"}'></summary></details>
<!-- /wp:op/repeat -->
Attributes are JSON right after the name; an insert without attributes takes all defaults. The JSON must be valid, and quotes are the usual trap: a double quote inside a string value ("text":"© "Acme"") breaks the JSON, and WordPress then silently drops all attributes of that insert and renders it with defaults. There is no error and no warning. Escape the quote as \u0022, or use typographic quotes. For the same reason do not put -- inside a value: it ends the HTML comment. After uploading, check the numbers in inserts of the answer, and look at the page.
A value inside a tag attribute (href, src, element text) is not an insert’s job; that is what bindings do.
Where inserts work
In a frame, which is their main place: the frame is printed on every request, or baked ahead of time, and every insert is expanded. In the content of a regular page too, since inserts are registered block types: a markdown page may contain a line with <!-- wp:op/frame {"ref":"docs__promo"} /--> or an op/callout. In page content the page is taken from the request.
An insert that is not registered stays a comment and is named in the bake report: вставка op/foo не зарегистрирована, останется комментарием. An insert that has nothing to print does not disappear silently either: it leaves an HTML comment explaining why, for example <!-- op/menu: для области «header» меню не назначено -->. Open the page source and search for <!-- op/. The texts of those comments are in Russian, as the plugin prints them; they are quoted below exactly.
Rendering on request. Some inserts depend on which page is printed. A frame containing any of them gets renders_on_request: true and no baked copy, see Frames. Each insert below says whether it does this.
| Group | Inserts |
|---|---|
| Page shell | op/content, op/frame, op/partial (deprecated) |
| Navigation and site settings | op/menu, op/lang-switcher, op/socials, op/footer-columns, op/copyright |
| Lists and data | op/repeat, op/faq-schema |
| Documentation | op/docs-nav, op/outline, op/pager, op/search, op/share-ai |
| Article | op/featured-image, op/terms |
| Other | op/callout, op/assistant |
Page shell
op/content
Prints the content of the page itself: the place in the shell where the text of the post goes. It is printed exactly as a theme template would print it, through the_content, and the headings get the same anchors that op/outline links to.
| Attribute | Default | Meaning |
|---|---|---|
page | 0 | page id; 0 means the page being printed |
Empty states: <!-- op/content: страницы в контексте нет, печатать нечего --> (no page, for example at bake time), <!-- op/content: записи 123 не существует -->, and <!-- op/content: вложенный вызов, содержимое печатается только раз --> when content ends up inside content. Renders on request: yes.
<article class="article__body"><!-- wp:op/content /--></article>
op/frame
Prints another frame of the site right here. It is the only link between frames, and it also works in page content.
| Attribute | Default | Meaning |
|---|---|---|
ref | "" | slug or id of the frame; frame, slug and id are accepted as aliases |
type | "" | legacy: header, footer or partial, resolved through old site defaults; only until site 233 is migrated |
class | "" | wrap the result in a div with this class |
The nested frame is printed from its own source with the context of the outer run (the same page and language). Protection: a frame that is already being printed is not printed again (recursion), nesting stops at 8 levels, a run prints at most 64 nested frames. If the nested frame is emptied by "empty":"frame", the insert prints an empty string. If the nested frame is a whole document with <!doctype> or <html>, a warning comment is printed before it.
Empty states, all as <!-- op/frame: … -->: не указан ref — это id или слаг фрейма, фрейма «site__header» на сайте нет, рекурсия, фрейм уже печатается: a → b → a, глубже 8 вложенных фреймов не печатаем, у типа «header» нет назначенного фрейма (легаси; ссылайтесь op/frame {"ref":…}). Renders on request: only if the nested frame does.
<!-- wp:op/frame {"ref":"site__header"} /-->
<!-- wp:op/frame {"ref":"article__faq","class":"article__faq-wrap"} /-->
Renaming a frame rewrites these references automatically, see Frames.
op/partial (deprecated)
Compatibility alias of op/frame {"type":"partial"}: the page meta _onpress_frame_partial, then the old site default. No attributes. Kept for old frames only; write op/frame {"ref":…}. Empty state: <!-- op/partial: странице не назначен фрейм-вставка и дефолта у сайта нет -->. Renders on request: yes.
<!-- wp:op/partial /-->
Navigation and site settings
op/menu
The menu assigned to a navigation location of the theme, printed by core wp_nav_menu(). For a non-default language the menu comes from the Polylang map of that language. Menus are managed with the menu endpoints.
| Attribute | Default | Meaning |
|---|---|---|
place | header | location: header, footer_1, footer_2, footer_3; location is accepted as an alias |
id | "" | id of the list; empty gives menu-<menu slug>; a repeated id in one run gets -1, -2 |
display | list | list or dropdown (a details and summary wrapper, no JS) |
label | "" | caption of the dropdown; empty gives «Меню» |
lang | "" | language of the menu; empty means the language being printed |
depth | 0 | nesting depth, 0 is unlimited |
class | op-menu | class of the list; the dropdown gets op-menu--dropdown, op-menu__summary |
Empty state: <!-- op/menu: для области «header» меню не назначено -->. Renders on request: no; a menu change rebakes the frames that insert it.
<nav class="site__nav"><!-- wp:op/menu {"place":"header","depth":1,"class":"site__menu"} /--></nav>
op/lang-switcher
The site’s languages from Polylang. When a page is being printed, each link leads to the published translation of that page; without a translation, to the home page of the language.
| Attribute | Default | Meaning |
|---|---|---|
display | list | list (a ul) or dropdown (details and summary, current language in the summary) |
label | "" | aria-label of the dropdown and its fallback caption |
show | name | name (English, Русский) or slug (EN, RU) |
hide_current | false | leave the current language out of the list |
class | op-lang-switcher | root class; items get __item, __link, the current one is-current and aria-current |
Empty states: <!-- op/lang-switcher: Polylang не активен -->, <!-- op/lang-switcher: языков нет -->. Renders on request: no.
<div class="site-header__lang"><!-- wp:op/lang-switcher {"class":"oplang","show":"name","display":"dropdown"} /--></div>
op/socials
Links to social networks from the site settings (the same fields the settings endpoint writes): twitter, facebook, telegram, whatsapp, linkedin, instagram, reddit, gitlab, github, youtube, pinterest. Only filled fields are printed, as a ul with __item and __link--<network>.
| Attribute | Default | Meaning |
|---|---|---|
place | footer | accepted but does not change the output |
class | op-socials | root class |
Empty states: <!-- op/socials: ACF не активен -->, <!-- op/socials: в настройках сайта нет ни одной ссылки -->. Renders on request: no.
<!-- wp:op/socials {"class":"site-footer__socials"} /-->
op/footer-columns
Footer menus as columns: one column per location, the menu name as the column title, the menu as a list. The menu for each location is found the same way as in op/menu.
| Attribute | Default | Meaning |
|---|---|---|
places | footer_1,footer_2,footer_3 | locations, comma-separated; empty ones are skipped |
lang | "" | language of the menus |
class | op-footer-columns | root class; columns get __column and __column--<location>, titles __title, lists __list |
Empty state: <!-- op/footer-columns: меню подвала не назначены -->. Renders on request: no.
<div class="site-footer__columns"><!-- wp:op/footer-columns /--></div>
op/copyright
A copyright line in a span. Never empty.
| Attribute | Default | Meaning |
|---|---|---|
text | "" | the text; {year} and {name} are replaced with the current year and the site name; empty gives © <year> <site name> |
class | op-copyright | class of the span |
Renders on request: no. The year is fixed in a baked copy until the next bake.
<!-- wp:op/copyright {"text":"© {year} {name}. All rights reserved."} /-->
Lists and data
op/repeat
The repeater prints its body once per item of a list. This is how a frame shows an FAQ, footer links, a tariff table or the children of a page. The list comes from one resolver in a fixed order: a system source (menu.header, site.languages, pages.children, breadcrumbs and others), then a site dataset with that name, then inline items. Sources and datasets are described in Data.
| Attribute | Default | Meaning |
|---|---|---|
source | "" | name of a system source or a site dataset |
items | [] | an inline list, for a list needed in exactly one place |
lang | "" | language of the dataset variant; empty means the page language, then the language of the run; without a variant the shared dataset is used |
statuses | publish | page statuses for the pages.* sources, comma-separated |
page | 0 | page for contextual sources; 0 is the page being printed |
match | {} | filter: {"field":"token"}, keeps items whose field equals the token value as a string; tokens as in bindings, or raw:<value> |
empty | "" | frame: an empty list makes the whole nearest frame print nothing |
Inside the body an item field is item.<field> in a binding, nested fields through a dot: item.q, item.prev.title, item.children.0.url. The repeater substitutes item fields itself for each copy; outside the loop item.* has no value and the general bindings pass leaves such markup alone. This is the real footer fragment of site 235, reading the footer_legal dataset:
<nav class="site-footer__legal" aria-label="Legal">
<!-- wp:op/repeat {"source":"footer_legal"} -->
<a class="site-footer__legal-link" data-op-bind='{"text":"item.text","href":"item.url"}'></a>
<!-- /wp:op/repeat -->
</nav>
match filters any source. All pairs must match; numbers compare as strings, true as "1", false as ""; arrays, objects and missing fields never match; a token without a value empties the list. One FAQ dataset for the whole site, filtered by the page slug, with the block hidden where there are no questions:
<!-- wp:op/repeat {"source":"faq","match":{"page":"page.slug"},"empty":"frame"} -->
<details><summary data-op-bind='{"text":"item.q"}'></summary><div data-op-bind='{"html":"item.a"}'></div></details>
<!-- /wp:op/repeat -->
Empty states, when empty is not frame: <!-- op/repeat: список «faq» пуст -->, <!-- op/repeat: список «breadcrumbs» пуст, источник контекстный и позван без страницы -->, <!-- op/repeat: пустой шаблон элемента -->. With "empty":"frame" the nearest frame prints nothing; outside a frame only the repeater disappears. The bake report lists every repeater with source, origin, items, binds and unresolved, so “the list is empty” is distinguishable from “the insert did not work”.
Renders on request: yes when source is contextual (pages.children, pages.siblings, pages.adjacent, breadcrumbs) or match uses a page.* token; otherwise no, and a dataset change rebakes the frame. "empty":"frame" alone does not make a frame render on request. Recipe: FAQ from data.
op/faq-schema
JSON-LD FAQPage built from the same list as the repeater next to it: same source, language and match, so the questions in the markup and in the accordion always agree. HTML is stripped from questions and answers; items without a question or an answer are skipped.
| Attribute | Default | Meaning |
|---|---|---|
source | "" | source or dataset, as in op/repeat |
match | — | filter, as in op/repeat |
lang | "" | dataset language |
page | 0 | page for the filter and contextual sources |
question | q | field with the question, dotted path allowed |
answer | a | field with the answer |
Output: one <script type="application/ld+json">. An empty list prints nothing, without a comment. Renders on request: yes.
<!-- wp:op/faq-schema {"source":"faq","match":{"page":"page.slug"}} /-->
Documentation
These five print parts of the theme’s documentation layout, so a documentation section is assembled by a frame rather than by fixed slots of a template. All of them take the page from the run context and accept a page attribute to set it explicitly.
op/docs-nav
The left tree of documentation pages: active item, expanded ancestor, drafts for editors, drag handles and the “add child” button. It is the theme part template-parts/docs-aside-left; its order is the order of the docs sidebar.
| Attribute | Default | Meaning |
|---|---|---|
search | true | false removes the tree’s own search field, when the frame places op/search separately |
Empty state: <!-- op/docs-nav: шаблонная часть template-parts/docs-aside-left ничего не напечатала -->. Renders on request: yes.
<nav class="opdoc__sidebar" aria-label="Pages"><!-- wp:op/docs-nav /--></nav>
op/outline
The right-hand outline of the H2 to H6 headings of the page, with the same anchors that op/content puts into the text.
| Attribute | Default | Meaning |
|---|---|---|
title | "" | heading of the block; empty gives «On this page», or «На этой странице» on a Russian page |
bare | false | print only the ul list, without the wrapper and the heading |
Empty states: <!-- op/outline: страницы в контексте нет, оглавлению не по чему строиться -->, <!-- op/outline: на странице нет заголовков H2–H6 -->. Renders on request: yes.
<div class="opdoc__aside"><!-- wp:op/outline /--></div>
op/pager
Links to the previous and next page in the same order as the left tree. An empty side keeps a spacer so that “next” stays on the right.
| Attribute | Default | Meaning |
|---|---|---|
prev_label | "" | caption of the back link; empty gives core «Previous» in the page’s locale («Назад» in Russian) |
next_label | "" | caption of the forward link; empty gives «Next» («Далее») |
label | Documentation pages | aria-label of the nav |
Empty states: <!-- op/pager: страницы в контексте нет, соседей не у кого спрашивать -->, <!-- op/pager: у страницы нет ни предыдущей, ни следующей -->. Renders on request: yes.
<!-- wp:op/pager /-->
op/search
The search field of the tree, taken from the same theme part so that classes and icons match. The filtering script lives in the theme bundle and works only inside the tree container; placed elsewhere, it is just an input.
| Attribute | Default | Meaning |
|---|---|---|
placeholder | Search | placeholder and aria-label of the fallback field |
Never empty. Renders on request: no.
<!-- wp:op/docs-nav {"search":false} /-->
<!-- wp:op/search {"placeholder":"Search the docs"} /-->
op/share-ai
The “Copy” button with the “Explore with AI” drop-down: seven links to AI assistants built from the address of the current page, plus “Copy Markdown”.
| Attribute | Default | Meaning |
|---|---|---|
copy | true | false prints only the list of AI links, for a frame with its own button |
label | Copy | caption of the button |
Empty state: <!-- op/share-ai: шаблонная часть template-parts/aside-open-in-ai ничего не напечатала -->. Renders on request: yes.
<div class="opdoc__copy-panel"><!-- wp:op/share-ai {"copy":false} /--></div>
Article
op/featured-image
The featured image of the page as <picture> with sizes and WebP sources, printed by the same code that prints every image of the site. Without the image handler it falls back to a plain <img>.
| Attribute | Default | Meaning |
|---|---|---|
class | "" | class of the img |
picture_class | "" | class of the picture |
loading | lazy | lazy or eager; put eager for an image above the fold |
fetchpriority | "" | high for the main image of the page |
link | false | wrap in a link to the page |
figure | false | wrap in figure with the attachment caption as figcaption |
The alt is the attachment’s alt text, or the page title when it is empty. Empty states: <!-- op/featured-image: страницы в контексте нет -->, <!-- op/featured-image: у записи нет изображения -->, <!-- op/featured-image: файла изображения нет -->. Renders on request: yes.
<!-- wp:op/featured-image {"loading":"eager","fetchpriority":"high","figure":true} /-->
op/terms
The terms of a taxonomy of the current post as a list of links.
| Attribute | Default | Meaning |
|---|---|---|
taxonomy | category | taxonomy: category, post_tag or any registered one |
class | op-terms | root class; also op-terms--<taxonomy>, items __item--<slug> |
link | true | false prints names in span without links |
limit | 0 | at most this many terms, 0 is all |
label | "" | caption before the list |
Empty states: <!-- op/terms: страницы в контексте нет -->, <!-- op/terms: таксономии «x» на сайте нет -->, <!-- op/terms: у записи нет терминов таксономии «post_tag» -->. Renders on request: yes. For a single category above the title there are also the bindings page.category_name and page.category_url.
<!-- wp:op/terms {"taxonomy":"post_tag","label":"Tags:","limit":5} /-->
Other
op/callout
A callout with a real nested body: paragraphs, lists, code blocks. Markup and classes are those of onpress/standard-alert, so the styles are shared.
| Attribute | Default | Meaning |
|---|---|---|
variant | default | default, info, success, warning, destructive; anything else becomes default |
icon | "" | Material Symbols name; empty gives the variant’s icon |
title | "" | heading |
showIcon | true | show the icon |
showTitle | true | show the heading |
Empty state: <!-- op/callout: ни заголовка, ни содержимого -->. Renders on request: no. It is mostly used in page content:
<!-- wp:op/callout {"variant":"warning","title":"Single quotes only"} -->
<!-- wp:paragraph -->
<p>Keep the JSON of data-op-bind in single quotes.</p>
<!-- /wp:paragraph -->
<!-- /wp:op/callout -->
op/assistant
The embedded Framesuite chat assistant, the same code as the onpress/ai-assistant block of v1 content. One per page. The plugin also finds the insert in the page’s frame, including nested frames, and adds the assistant’s import map and loader to <head>.
| Attribute | Default | Meaning |
|---|---|---|
id | "" | assistant id |
assistantId | "" | alias of id, so a block moved from v1 content keeps working |
Empty state: <!-- op/assistant: плагин onpress-blocks не активен на сайте -->. Renders on request: yes. The real landing frame of site 235:
<section class="opland__assistant"><!-- wp:op/assistant {"id":"bDXYZ0st8QIM"} /--></section>