Extending & security
Custom & licensed blocks
Additional blocks arrive as packages (plugins) and register via the API:
export default function register(cms) {
cms.addBlock({
type: 'pricing',
title: 'Pricing table',
category: 'design',
scope: ['home'],
css: '.pricing{ … }',
render: (props, node, ctx) => `<section class="pricing">…</section>`,
});
}
The render callback receives the properties, the block node and a context (including ctx.e for escaping and ctx.renderChildren). A package can be licensed and show a locked placeholder without a valid licence; the key is entered under Design → Bookable block packs.
Security & SEO
- HeroML is data, not code: the parser only builds an object tree — nothing is executed.
- Block renderers escape every value. Raw HTML exists only via
html/richtextand is sanitised by an allowlist. - The
<head>(title, meta, Open Graph, Twitter, JSON-LD, favicon) is generated centrally; blocks can contribute their own JSON-LD structured data. - Output is server-rendered, semantic HTML;
sitemap.xmlandrobots.txtremain unchanged.
Cheat sheet
# comment indent: 2 spaces type attr=value attr2="two words" flag # block line key: value # field line child-block # deeper = child grid cols=3 · cell span=2 # grid with-sidebar side=right · sidebar sticky # sidebar featured # highlight 50/25/25 posts layout=grid excerpt=20 mode=loadmore recent-posts count=5 · category-list # dynamic widgets ad slot=header format=leaderboard # ad slot meta / animate=fade-up / {{ site.title }}