R rawmark v0.3.0 Authoring Referenceverified against the current codebase, not a roadmap ↓ Download .zip ↓ Download SKILL.md
Start here

What Rawmark renders

A Rawmark "Code Page" is a standalone HTML document. No theme header, no theme footer, no theme stylesheet, no block-library CSS. What you write in the three panes, plus whatever WordPress plugins add through wp_head/wp_footer, is the entire page.

This is the whole document Rawmark builds. Nothing else is added, nothing is filtered:

<!DOCTYPE html>
<html lang="{site language}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{the page's title}</title>
  <!-- wp_head() fires here -->
  <style>
    /* your CSS pane */
  </style>
</head>
<body class="rawmark-page rawmark-page--{slug}">

  <!-- your HTML pane -->

  <script>
    /* your JS pane */
  </script>
  <!-- wp_footer() fires here -->
</body>
</html>

Your CSS loads in <head>, so there's no flash of unstyled content. Your JS runs after the DOM is parsed but before wp_footer. The <body> carries a per-page class, so page-scoped CSS never needs an extra wrapper <div>. There is no reset, no normalize, no theme default: a bare <h1> looks like a browser default <h1>. That's the trade for a genuinely clean document.

The three panes

HTML: body content only

No <!DOCTYPE>, <html>, <head>, or <body>. Rawmark supplies those. Start with a single root element:

<main class="lp">
  <section class="lp__hero">...</section>
</main>

CSS: plain rules, no <style> tag

Rawmark wraps it for you. No preprocessor, no build step. Write the CSS you want shipped.

JS: plain script, no <script> tag

It runs as a classic script at the end of <body>: the DOM is already parsed, so no DOMContentLoaded wrapper is needed. It is not a module, so import doesn't work; pull in a library with a <script src> tag in the HTML pane instead. Wrap your own code in an IIFE to keep the global scope clean:

(function () {
  "use strict";
  // your code
})();

Static pages, plainly

Most Code Pages are entirely static: you write exact HTML/CSS/JS, and exactly that renders. Nothing is substituted, queried, or computed on the way out.

The JS pane is not required for a page to work. Leave it empty and the page still renders, still styles correctly, still ships its content. JS is only for genuine client-side behavior: things that have to happen in the visitor's browser, after the page has loaded.

  • Toggling a mobile nav open and closed
  • Animating something on scroll
  • Validating a form before submit
  • Fetching something after the initial page load

Pulling in real WordPress content (a post's title, a list of recent posts) is never one of those things in Rawmark. That's the dynamic-content system below, and it happens entirely on the server, before your JS pane would even get a chance to run.

Dynamic, mechanism 1 of 3

Snippets

A Snippet is a reusable fragment of HTML/CSS/JS, stored once and referenced from any number of pages. Insert one with a self-closing marker:

Self-closing
<!-- rawmark:snippet id='42' -->

Snippets are live, not copied. The marker resolves to the snippet's current content on every render. Edit the snippet once, and every page referencing it updates immediately: there is no "paste and diverge" step. If you want a one-off variation, don't insert the snippet; copy its content into the page's own panes and edit that instead.

A snippet referencing a deleted or otherwise invalid ID resolves to nothing (see the fail-safe rule).

Dynamic, mechanism 3 of 3

Post Template

One Snippet, designated from the Snippets screen, becomes the layout for every ordinary WordPress Post that hasn't been individually flagged as its own Code Page. Write the layout once; seven merge tags substitute each Post's real data at render time:

TagResolves to
<!-- rawmark:post_title -->The post's title, escaped
<!-- rawmark:post_content -->Full content, through WordPress's real the_content filter chain (blocks, shortcodes, embeds all run)
<!-- rawmark:post_excerpt -->The excerpt, escaped
<!-- rawmark:post_date -->The formatted date, escaped
<!-- rawmark:featured_image -->Featured image markup, or nothing if none is set
<!-- rawmark:permalink -->The post's URL, escaped
<!-- rawmark:author_name -->The author's display name, escaped

An individually-flagged Post always renders its own source instead of the template. The specific case always wins over the general one.

Post Loop

Repeats a block of your own HTML once per matching Post: a filtered list, not a fixed layout. You write the markup; Rawmark supplies the data.

Paired
<!-- rawmark:post_loop category='news' tag='featured' count='5' -->
  <h2><!-- rawmark:post_title --></h2>
  <p><!-- rawmark:post_excerpt --></p>
  <a href="<!-- rawmark:permalink -->">Read more</a>
<!-- /rawmark:post_loop -->
AttributeDefaultNotes
categoryanyCategory slug. Omit to match every category.
taganyTag slug. Omit to match every tag.
count5Clamped to a maximum of 50 server-side, regardless of what's typed.

All seven Post Template tags work inside the loop body, resolving to that iteration's post. If nothing matches, the whole block resolves to nothing, same as an unset template.

Only works in a flagged Page or Post's own top-level source, not inside a Snippet inserted elsewhere, not in a header or footer.

Every post_loop needs its closing tag. Miss <!-- /rawmark:post_loop --> and the block you meant as a repeating template renders once, literally, as ordinary page content. Not a template, just text. The editor's lint indicator catches a mismatched open/close count; don't ignore it.

Loops don't nest. A post_loop placed inside another one closes the outer loop early. That's not supported, and it isn't silently fixed for you.

Shortcodes

Rawmark expands WordPress shortcodes — the [shortcode] bracket syntax — anywhere in a flagged Page or Post's rendered HTML, including content pulled in through a header, footer, or inserted Snippet. This runs through WordPress's own do_shortcode(), not the full the_content filter chain: shortcodes execute, but wpautop() and the other content filters never touch your markup.

This is what lets a plugin's own shortcode — WooCommerce's [woocommerce_cart], for example — render and stay interactive inside otherwise hand-authored HTML. See WooCommerce below for the specific recipe.

The live preview iframe does not expand shortcodes. It's built client-side from your draft HTML with no server round-trip, so a shortcode shows as literal bracket text while you're editing. It renders correctly once viewed on the real, published URL. Not a bug, just not wired up yet.

Any shortcode you reference runs whatever PHP that shortcode's plugin registered for it — the same trust boundary as the rest of a Rawmark pane. See Trust model.

Marker reference

Every marker lives inside an HTML comment on purpose, so it can never collide with a template-engine syntax like {{ }} that you might legitimately paste from somewhere else. Attributes are always single-quoted.

MarkerShapeWhere valid
Insert a Snippet<!-- rawmark:snippet id='N' -->Any page's HTML pane
Post data (7 tags)<!-- rawmark:post_title --> etc.Post Template source, or inside a post_loop body
Post Loop<!-- rawmark:post_loop ... --> ... <!-- /rawmark:post_loop -->Top-level flagged Page/Post source only
Integration

WooCommerce

Rawmark can own the markup and styling of a WooCommerce store's pages while WooCommerce keeps running everything dynamic underneath it: cart totals, coupon fields, payment gateway rendering, account forms. None of this touches WooCommerce's data model — Rawmark never reads or writes a product, order, or setting. It's markup layered on top of WooCommerce's own shortcode output, nothing more.

Cart, Checkout, My Account

These three work today with no extra step. WooCommerce already sets each one up with its page content being the relevant shortcode:

[woocommerce_cart]
[woocommerce_checkout]
[woocommerce_my_account]

Flag the page in Rawmark, keep that shortcode in the HTML pane, and wrap it in whatever markup and CSS the design calls for: a page-width container, a two-column layout, custom spacing. WooCommerce still renders the interactive form and totals wherever you place the shortcode, and its AJAX cart updates and localized script data (wc_cart_params and friends) keep working, because wp_head()/wp_footer() fire on a Rawmark page by default (see Not built yet — the toggle that could disable this has no editor UI, so it can't get switched off by accident).

Shop & Single Product

These two are different, and it's a WooCommerce constraint, not a Rawmark one: WooCommerce's own template loader (archive-product.php / single-product.php) runs for the Shop page and every product URL regardless of that page's content. Flagging WooCommerce's default Shop page in Rawmark has no effect; the theme's product templates still render underneath.

The working pattern is a fresh Page, not the default one:

  • Create a new Page, flag it in Rawmark, build the layout you want.
  • Drop in [products limit="12" columns="4"] for a shop-style grid, or [product_page id="107"] for a single product's full buy box.
  • Point navigation and any "Shop" links at this new page instead of WooCommerce's default one.

WooCommerce's product shortcodes take the same attributes documented in WooCommerce's own shortcode reference (category, orderby, columns, and so on). Rawmark doesn't add or restrict any of them; it only expands the shortcode, per Shortcodes above.

Don't try to restyle the locked Shop/Single Product templates directly. WooCommerce ignores their page content by design. Build a new Page with the shortcode approach above instead of fighting the template loader.

Integration

SureCart & blocks

Most of SureCart is shortcodes — sc_product_title, sc_product_price, sc_product_cart_button, and friends all render fine through the same do_shortcode() pass everything else in this guide relies on. Cart and Checkout are the exception: SureCart only ever registered them as Gutenberg blocks, no shortcode fallback, so pasting their block-comment markup into a Rawmark page rendered as inert HTML comments by default.

An opt-in enable_blocks setting fixes this: when on, Rawmark also runs do_blocks() on the composed page before do_shortcode(), so real block markup — copied straight out of the block editor or exported from the live install — renders and functions.

<!-- wp:surecart/checkout-form {"id":134} --><!-- /wp:surecart/checkout-form -->

enable_blocks has no editor UI yet. Same gap as the SEO fields below — it exists in the data model and the render path honors it, but there's no toggle in the three-pane editor to switch it on. It has to be set directly through the REST API or the storage layer until that UI exists.

SureCart's cart itself is a slide-out drawer (surecart/slide-out-cart), not a routable page — there's no standalone "Cart" URL to flag the way Checkout has one. Only turn enable_blocks on for a page that actually needs it; it's extra rendering work every other page skips by default.

Naming & formatting

Not enforced by the editor. This is what keeps a page readable when you return to it later.

  • Class names: kebab-case. A block__element--modifier pattern survives being read out of context.
  • Prefix per page: a short prefix per page (lp__, pricing__). Costs nothing, and matters the moment content moves between pages.
  • Never use the rawmark- prefix for your own classes. It's reserved for classes Rawmark itself adds (like the rawmark-page body class).
  • Indentation: two spaces, in all three panes.
  • Attributes: lowercase, double-quoted.
  • Section banners: a comment above each major block. In a single-file page with no folder structure, comments are the only navigation you get.

Accessibility baseline

A visual builder handles some of this automatically. Rawmark handles none of it, so treat this as the minimum bar:

  • Exactly one <h1>, heading levels that descend without skipping
  • Landmarks: <main>, and <nav>/<header>/<footer> where they apply
  • Every image has alt (empty alt="" for decorative images, never a missing attribute)
  • Every form input has a real <label>, not a placeholder standing in for one
  • Visible focus states: replace outline: none, don't just remove it
  • Text contrast at 4.5:1 or better
  • Interactive elements are <button> or <a>, never a <div> with a click handler

The fail-safe rule

One rule, applied identically everywhere in the system: a broken or missing reference resolves to nothing. Never a fatal error, never a warning banner on the live page.

A Snippet marker pointing at a deleted Snippet, a Header/Footer Template pointing at a deleted Snippet, a Post Template that's never been set, a Post Loop whose category slug has a typo and matches nothing: all of these render as empty, exactly as if that content were never there. Write your templates and loops knowing that an empty result is a valid, unremarkable outcome, not a signal that something crashed.

One exception: this silence applies to references, not to malformed markup. An unclosed post_loop doesn't fail safe. See the callout in Post Loop above.

Trust model

Writing to the HTML/CSS/JS panes requires the rawmark_edit_code capability, granted to Administrators only by default. Pane content is not sanitized on output, by design: the same trust level as editing a theme's PHP files directly, not the trust level of a comment field. Anyone holding that capability can write arbitrary JavaScript that runs for every visitor, or reference any registered shortcode, which runs whatever PHP that shortcode's plugin defined.

Grant it accordingly. It is not a role for a general content editor.

Size limits

Soft warning

256 KB combined across all three panes. The editor flags it, but saving still works.

Hard cap, per pane

1 MB. A save past this is rejected.

Hard cap, combined

2 MB across HTML + CSS + JS together.

Post Loop count

Clamped to 50 matching posts, regardless of what's requested.

Editor conventions

The three-pane editor adapts to what you're editing:

  • A Page or Post: title is editable, the status pill shows Draft/Published, and there are two save actions: Save draft and Publish/Update. "Save as Snippet," the header/footer selects, and "Insert Snippet" all appear only here.
  • A Snippet: title is read-only (renaming isn't supported yet), no status pill, and a single Save button. Snippets have no draft/publish state to choose between.

"Insert Media" (the image icon in the toolbar) works in both contexts and inserts at the cursor in whichever pane is active.

Not built yet

Stated plainly, so nothing here gets assumed into existence:

  • No placeholder tokens. A Snippet has no {{variable}} substitution system. Its content is exactly what's stored, resolved live per the rule above.
  • No shared/global stylesheet feature. Nothing writes a site-wide CSS file for you; link your own if you want one shared across pages.
  • No MCP or AI-agent abilities are registered. Nothing in the plugin currently exposes page/snippet operations to an external agent.
  • SEO title, SEO description, the wp_head/wp_footer toggles, and enable_blocks exist in the data model but have no editor UI yet. In practice every page today uses the real post/page title, no meta description, both hooks always fire, and block rendering is off by default. See SureCart & blocks.