Bootstrap a new Typeroll site
This is the tr-new-site recipe exactly as the Typeroll MCP server gives it to agents through list_skills and read_skill. See Skills and recipes for how agents load recipes.
Site tokens before block overrides
Section titled “Site tokens before block overrides”Core 0.2.106 / template capabilities 0.54.0 adds palette names in all colour
fields and named typography/spacing steps. Check the connected capability
version; on older renderers use var(--color-surface) rather than a bare name.
Set colours and font families in site settings, then use named styles and
palette names in blocks. Do not write hex, rgb or font-family literals in block
content. Prefer primary, secondary, accent, background, surface, text,
text_light, and on_primary/on_secondary/on_accent for text on brand colours.
Use surface_hover for a darker interactive surface (derived automatically
unless explicitly configured). Existing semantic roles should be reused; a
new visual shade is not automatically a new semantic role. For a derived shade
use color-mix(in srgb, var(--color-surface) 92%, var(--color-primary)).
For an additional role not in the native palette, define a named site style
or a site CSS variable derived from existing palette variables, and document it.
Choose heading, body or mono for fonts. Font families belong only in the
site’s fonts settings. Use type-size steps xs, sm, md, lg, xl, 2xl,
3xl; spacing none, xs, sm, md, lg, xl; line height tight, snug,
normal, relaxed, loose. Presentation fields accept responsive maps of these
names, for example font_size_px: {mobile: "md", desktop: "lg"}. Read the field
schema: counts, widths and breakpoints are not spacing steps. Pixel values are
explicit exceptions, not the default authoring method. Configure scales in
design_scales, then reuse them in role styles and blocks.
Record deliberate exceptions in decisions.md, with source/block, reason and
review criteria (for example white text on a photograph). Run lint_design_tokens
before launch and after major design changes. It warns, never blocks launch.
Use the starter’s scripts/typeroll/color_tokens.py for an exact-match dry run;
review the plan before applying it in a dedicated Version. Equal hex values can
mean different roles: never automatically choose between background and text.
Verify a primary/surface change in a test Version across header, footer, cards,
menus and buttons, then restore the test settings. Do not republish automatically.
The buffer model (draft writes). Every content write in this recipe (pages, blocks and partials) lands in an unsaved per-doc DRAFT — deploys and plain previews only see SAVED content. For recipe-style build work, pass
save: trueon write calls (the work is pre-approved by the task itself), or runcommit_working_copyper doc before anytrigger_deploy. Preview your drafts withinclude_working_copy: true.
Start here when the site already exists as a database record (created via the portal UI or API) but has no design, no header/footer, and no pages. The goal is to go from blank to a working 4-page site with correct brand identity in a single session.
Pages are built in block mode — the platform default. Blocks give structured, per-field editing, native full-bleed sections, responsive breakpoints, and templates. HTML mode is the secondary path for hand-crafted one-offs and migrated content (section at the end).
Workspace and context
Section titled “Workspace and context”An optional agent-neutral workspace stores the brief, decisions and QA. It does not create a Site or replace CMS content. See the public Agent workspace guide. For compact MCP: search_tools, describe_tool, then the appropriate read/write/admin wrapper. Load only the relevant guide section and recipe. Discover site apps with read_app_documentation when needed; do not bundle private app guides locally.
No Site yet? Use hosted MCP with an Organization key and create_site, or create it
in the portal. To start from an AI site plan instead of a blank Site, use
create_site_and_plan, poll get_workflow and approve its structure review only
with the user’s consent. That AI plan needs the Organization’s own portal AI
key for new Sites; without it the call answers 403 ai_not_enabled and the
site_planning workflow fails with failure_code: "ai_not_enabled". Then plan
it yourself: create_site, propose the site name, tagline and page list (title,
slug, purpose, target keywords) from the business description, get the
user’s approval, and build with this recipe. Tell the user you planned it
yourself; don’t retry the workflow. Read back its ID before editing. Use one explicit Site/Version for
related operations. Larger changes to an existing site should use a branch.
Before importing content or a media library, require get_import_readiness to pass. Publishing accounts may remain unconfigured while authoring new drafts and previews.
Preconditions
Section titled “Preconditions”@typeroll/mcp-serverconfigured with a validTYPEROLL_API_KEY.- The site exists (confirm with
get_site). - You have the customer brief: company name, industry, 2–3 key brand colors, tone of voice, and a list of initial pages.
Recipe
Section titled “Recipe”1. Audit current state
Section titled “1. Audit current state”get_siteget_site_capabilities # template_capabilities_version — what this deployment supportsread_site_settings # see what (if anything) is already configuredlist_pages # don't overwrite pages that already existlist_partials # check if header/footer already have contentlist_block_types # the per-site block palette — NEVER assume, always listDo not choose a content mode or file a platform gap before these calls
return. For every required visual/behavioral element, map it to an existing
block or composition first. If the summary suggests a match, use
read_block_type to inspect the exact schema. Only call something “missing”
after checking capabilities, the complete palette, and custom block types.
Common requirements that are easy to misclassify:
| Requirement | Existing Typeroll primitive |
|---|---|
| Full-bleed hero flush below the header | core/section + core/hero; block-mode sections already own the full width and have no page-shell padding |
| Responsive icon/card grid | core/grid + core/icon_box, or core/feature_grid; set responsive fields with set_block_responsive |
| Custom cards backed by Pages | core/repeater / core/page_list with a site-authored item_compatible block type as item_block |
| Grouped Page listing | core/repeater with group_by; array-valued fields place an item in every matching group |
| Breadcrumbs in a page template | template/page_breadcrumbs; page and item routes supply a server-rendered trail |
| Shared article typography | Set font_size, line_height and paragraph_spacing on template_content_slot; blank values retain block defaults (Core 0.2.2+) |
| Generated heading index | core/table_of_contents; choose heading levels; links always come from the current Page headings |
| Previous/next Page links | template/page_navigation; defaults to content type sort order and can bind explicit neighbor fields |
| Download CTA that disappears without a file | template/show_if around a context-bound core/button; a dedicated download block is only editor convenience |
| Sticky/custom header and multi-column footer | Block-mode header/footer partials plus layout blocks, or one reusable custom block type |
| Cookie notice | settings.cookie_consent, not a page block |
| Consent copy owned by an installed Extension | list_extension_installations → read_extension_installation → update_extension_installation_config, using exact manifest schema keys; the update queues a production deploy by default |
| One-off HTML + JavaScript embed | core/embed; reusable widgets use a custom block type with script |
| CTA/button variants | core/cta and core/button, styled from site tokens or a narrowly scoped class |
| Two-column image/text | core/media_card, core/feature_row, or core/columns |
| Figures, captions and tables | core/prose / richtext; the sanitizer preserves these semantic tags |
| Full-width block page without a content shell | Native block mode; top-level core/section is unconstrained |
| Brand colors and typography | Site colors/fonts tokens consumed by core blocks |
Real gaps should say what was checked and why the nearest primitive is not enough. “I did not see a dedicated block name” is not evidence by itself.
2. Brand + settings
Section titled “2. Brand + settings”One update_site_settings call with every field you know:
{ "site_name": "Acme Studio", "tagline": "Short, punchy tagline", "language": "sv", "trailing_slash": "always", "colors": { "primary": "#1a1a2e", "secondary": "#16213e", "accent": "#e94560", "background": "#f5f5f5", "surface": "#ffffff", "text": "#1a1a2e", "text_light": "#6b7280" }, "fonts": { "heading": "Playfair Display", "body": "Inter", "size_base": 16 }, "contact": { "email": "hej@acme.se", "phone": "+46 8 123 456" }, "social": { "instagram": "https://instagram.com/acme" }}Read it back with read_site_settings. Google Fonts names are
case-sensitive display names (“Plus Jakarta Sans”, not “plus jakarta”). Fonts
are self-hosted on the site by default; a name Google does not serve falls
back silently, so verify fonts on the published address (tr-brand Step 7).
Site icons are part of brand setup — upload favicon (32–64px), a
180×180 apple touch icon, and a 192×192 app icon; set favicon,
apple_touch_icon, and icon_192. No icon assets? Derive a proposal (see
tr-brand). Also set settings.logo to the uploaded brand mark — it feeds
OG/schema even if the header uses a different lockup.
Launch basics (ask early)
Section titled “Launch basics (ask early)”Ask the user for these once, early in the project. Read read_site_settings first and ask only for
what is missing. Each item is optional. Record a skipped item in
decisions.md, so the launch check in the last step holds no surprises.
- Default share image. Ask the user to choose or create the image that
social networks and chat apps show when a page has no image of its own:
upload one (
create_upload_urlorupload_media_from_url), pick an existing one (list_media), or create one withtr-imagegen. Use a 1200×630 px PNG or JPEG and set it asdefault_og_imagewithupdate_site_settings. Never generate a title card from the page title instead. The user may skip this. - Organization. The business name, logo and official profile URLs
(LinkedIn, Instagram, GitHub and similar). Set
organizationwithsame_as; it becomes the home page’s Organization schema.
Set iframe_allowed_hosts when customer content embeds a provider outside
the built-in YouTube/Vimeo/Google Maps/Calendly set. Values are exact domain
hostnames, never URLs or wildcards. Read the setting back before deciding that
an iframe cannot be represented.
2b. Styles before pages
Section titled “2b. Styles before pages”Set the site’s look as named styles before building any page:
list_styles. New sites already have the standard set (body, H1–H6, link, lead, eyebrow, small, quote, buttons, section). If roles are missing,apply_standard_styles.- Adjust them to the brand with
update_style: the font, the type scale and spacing forbase(phones) andat.tablet/at.desktop, and colours from palette tokens. Keep text readable: contrast is checked with APCA and WCAG 2; text that fails both is refused, and text below its APCA level comes back as a warning. - Add a named style for every look the design repeats: a card title, a price note, a highlighted section, a CTA button variant. Use the site’s language for names.
Pages then choose styles with each block’s style_id field. A per-block
font size, colour or class is only for something clearly unique.
To change a sub-element a block styles itself (the underline on formatted
heading links, a hero’s heading or buttons), use its parts:
read_block_type lists them. A style with parts: ["core/rich_heading.link"]
applies to that part on every block; data.part_styles on one block replaces
it there.
3. Header + footer partials
Section titled “3. Header + footer partials”Start from the native preset — don’t hand-roll navigation. Read
tr-header-footer and use its template/site_logo + core/navigation_menu with core/navigation_links slots
composition. The server-rendered landmark, current-page state, no-JS links,
mobile disclosure, focus treatment, and responsive behavior are Core
contracts rather than tenant CSS/JavaScript.
Stage the complete tree with update_partial partial_id="header" patch={blocks:[...]} save=true, then call set_partial_mode partial_id="header" to="blocks". Repeat for the footer and read both partials back. The inactive
HTML representation is retained for rollback; changing content_mode through
ordinary PATCH/PUT is rejected intentionally.
Use HTML mode only when preserving legacy authored markup that cannot yet be
represented natively. Partials receive the same {{site.*}} render context as
page blocks. Keep the first section and header backgrounds intentional; do not
add a decorative border merely to compensate for mismatched spacing.
4. Homepage — block tree
Section titled “4. Homepage — block tree”create_page with the whole tree in one call. Omit block ids — the
platform assigns them (blk_…); you read them back for later
update_block calls.
create_page title="Start" slug="" status="draft" content_mode="blocks" blocks=[...]A proven landing-page skeleton (every top-level block is a core/section
— sections are natively full-bleed: the background runs edge-to-edge,
content is constrained by the section’s own inner container via the
width field. NEVER use 100vw negative-margin hacks. Anchor ids and
custom classes via style_overrides are safe on sections from
template_capabilities_version ≥ 0.15.3; on 0.14.x–0.15.2 they wrap the
section in a div and silently break full-bleed — there, put the anchor
on a block inside the section instead):
[ { "type": "core/section", "data": { "background": "surface", "padding_y": "lg" }, "children": [ { "type": "core/media_card", "data": { "image": "MEDIA_URL", "image_alt": "…", "image_side": "right", "heading": "Huvudrubriken", "heading_level": "h2", "text": "<p>Ingress …</p>", "button_label": "Kontakta oss", "button_url": "/#kontakt" } } ] }, { "type": "core/section", "data": { "padding_y": "lg" }, "children": [ { "type": "core/heading", "data": { "text": "Så funkar det", "level": "h2", "align": "center" } }, { "type": "core/grid", "data": { "cols": { "mobile": 1, "tablet": 2, "desktop": 3 }, "gap": "lg" }, "children": [ { "type": "core/step_card", "data": { "number": "1", "title": "…", "text": "<p>…</p>" } }, { "type": "core/step_card", "data": { "number": "2", "title": "…", "text": "<p>…</p>" } }, { "type": "core/step_card", "data": { "number": "3", "title": "…", "text": "<p>…</p>" } } ] } ] }, { "type": "core/cta", "data": { "heading": "Redo att börja?", "primary_label": "Kontakta oss", "primary_url": "/kontakt" } }]Block-palette guidance (verify against list_block_types — the source of
truth):
- Hero:
core/media_cardinside a white section (image beside copy), orcore/hero(eyebrow/heading/subheading +primary_*/secondary_*buttons — rendered server-side;layout: split-rightputs the image beside the text). For a plain text hero: section + heading + prose + button. - Alternating image+text rows (“zig-zag” feature/step lists):
core/feature_row(template_capabilities_version ≥ 0.29.0) — balanced halves that hug the center gutter so wide screens never open dead air, natural-aspect image,image_side: left|rightper row, mobile stack order as a field. Prefer it over hand-buildingcore/columnswith an unbalanced ratio + width-capped text (the classic dead-air trap). Usecore/media_cardinstead when you want a boxed card with a filled/cropped image. core/headingdecoupleslevel(h1–h6, semantics) fromsize(visual) — exactly onelevel: h1per page.- Images:
core/imagewith an uploaded media URL. The build pipeline emits responsive output from prepared media. Upload through a signed direct grant, finalize it, and use returned IDs/URLs. Follow media preparation status; do not routinely regenerate unchanged images or use legacy maintenance tools. Use theradiusfield for rounded corners. - Repeaters/listings:
core/page_list,gallery,feature_gridetc. — alias blocks overcore/repeater. Use these for Pages of a content type instead of hand-writing listing markup. The base repeater also supportsgroup_by, group ordering/headings, multi-valued membership, filters, customitem_block, anditem_overrides. - Long-form navigation:
core/table_of_contentsbuilds an anchor list from page headings. Page templates usetemplate/page_navigationfor deterministic previous/next links. - Forms:
create_form, then placecore/formwith itsform_id. HTML-mode pages use<x-form id="…" />; both paths render the same signed shell and runtime. Seetr-forms. core/htmlis the escape hatch for the genuinely unique thing — not a default. If you reach for it more than once or twice per page, note why (that’s block-library feedback).
Slot containers (core/columns, core/tabs): populate them either by
passing the whole tree inline (block={ type: 'core/columns', slots: [[…],[…]] }) or incrementally with add_block parent_id=<columns-id> slot_index=0|1. Requires template_capabilities_version ≥ 0.15.2 — on
older sites use core/grid (children flow into columns) instead.
Partial last rows: keep last_row: 'start' on core/grid. Incomplete rows
stay left-aligned, preserving the reading edge. Center only after an explicit
owner design request, never as an automatic fix. Do not widen one peer card
or invent six-column CSS to fill a gap.
Icons (template_capabilities_version ≥ 0.16.0): type: 'icon' fields on
core/icon, core/icon_box, and core/step_card render inline SVG when
the value is a name from get_site_capabilities → core_icon_names (a
curated Lucide subset — check, star, shield-check, mail,
arrow-right, truck, chart-line, …). Any other value (emoji, plain
text) renders as text, so emoji stand-ins keep working. Icons inherit
size from font-size and color from currentColor/the block’s color
field. On older sites icons don’t render — use emoji or CSS markers.
Editor schemas (template_capabilities_version ≥ 0.39.0): labelled enum
options, newline-edited string lists, nested object/repeating-array fields,
and internal-page pickers for URL fields are first-class. Do not flatten an
Extension’s props_schema merely to make it editable.
For an Extension placed in an HTML header or footer, require
supports_extension_html_partial_directive: true. The broader
supports_extension_html_directive flag covers HTML page bodies and is not
proof that an older static build expands partial directives.
For a cross-page Extension flow, require both
supports_extension_site_navigation and supports_extension_storage. Use
context.site.navigate("/path/") and installation-scoped
context.storage.session rather than direct root-path navigation, Web Storage,
or personal data in query parameters. This keeps navigation inside the current
preview and preserves state without exposing it through URLs or referrers.
To prefill an Extension component from a native core/navigation_form banner
on the previous page, require supports_extension_page_handoff, check that the
installation grants page_handoff:read, and set the component block’s
page_handoff_key to the banner’s handoff_key; the banner’s destination
must be the component’s page. The component reads the values with
context.handoff.read(). Do not add query parameters for this.
Known limitations (honest list — don’t fight them):
core/tabslabel icons don’t render (the tab strip is built client-side without the icon pipeline). Text labels only.
Theming: block primitives render neutral. Brand color/typography comes
from settings (step 2) and named styles (step 2b). For page-specific polish
(e.g. a colored card treatment) that no style covers, give the block a
custom_class and write the rule in the page’s custom_css. Never put a
<style> in a core/html block or target [data-bid]/[data-block].
5. Inner pages
Section titled “5. Inner pages”Same pattern: create_page with content_mode: "blocks" and a section
tree. Standard set: Om oss, Tjänster, Kontakt — or what the brief says.
Default new pages to status: "draft"; publish after review.
Sections that repeat across pages: a call to action or contact strip that
must stay identical becomes a global block (make_block_global, then
core/global_block on the other pages). A section shape you will reuse with
different content (a pricing row, a feature trio) becomes a block template
(save_block_template, then insert_block_template).
5b. If the legacy site is still live, scrape canonical content
Section titled “5b. If the legacy site is still live, scrape canonical content”For pages with canonical text (privacy policy, terms, about), WebFetch
the live page and carry the text verbatim — don’t rewrite legal copy
from memory. Convert to prose blocks (or one core/html for complex
legacy markup).
6. Preview + iterate
Section titled “6. Preview + iterate”get_preview_link # signed URL for browser reviewget_page_preview page_id="home" # rendered HTML for structural checksSelf-review the visuals before you call it done — appearance AND readability, not just structure. Screenshot the deployed/preview site at desktop (~1440px) and mobile (~390px) and look: logo FULLY VISIBLE (not clipped by a header’s overflow:hidden) + legible + brand-compliant against its actual background — screenshot the header IN CONTEXT, not the logo element in isolation (an element shot hides layout clipping); a light wordmark must not sit bare on a light surface. Text contrast everywhere, no horizontal scroll or mid-word breaks, every image rendered, mobile layout actually collapsed. “No overflow + copy present” is not a design review — never report a build as done/perfect off structural metrics alone.
Share the preview link. Iterate on feedback with update_block /
add_block / move_block — that’s the point of block mode: surgical
edits, not full-page rewrites.
7. Launch (required)
Section titled “7. Launch (required)”A site is not finished until this step has run. Call read_site_readiness
before the production publication and again after it. For every item with the
status warning or suggestion, either fix it with the tool its fix names,
or record in decisions.md (or the starter’s qa/ folder) why it stays open.
Tell the user which items remain. A missing default share image is only a
suggestion; leave it if the user chose to skip it.
The report warns and never blocks a publication. A clean report is not approval to publish: publish only when the user has approved it.
When the user approves, verify read_publishing_readiness and save the
intended Version before deploying. A published CMS status is not a live
deployment.
read_site_readinesstrigger_deployget_deploy_status job_id=<id>read_site_readinessRun read_site_readiness again after the publication. It reads saved content
and settings, so confirm the rest on the live site: the AI crawler zone check
needs the site’s website host, and a Search Console property verified by DNS
is not visible to Typeroll. The
launch checklist
explains every item.
Secondary path: HTML mode
Section titled “Secondary path: HTML mode”For migrated legacy pages or a hand-crafted one-off, content_mode: "html" still works. Rules that apply there (and only there):
- Wrap the body in a page-scope
<article class="my-page">and prefix selectors with it — the.page-contentshell sets width/padding at normal specificity. - HTML-mode bodies are container-constrained; full-bleed requires the
negative-margin escape (
margin-left: calc(50% - 50vw); width: 100vw) — never combine withoverflow-x: clipon a wrapper. set_page_modeflips a page between modes and does not convert HTML. Typeroll has no HTML-to-blocks conversion: to move a page to blocks, build its content with blocks.
Pitfalls
Section titled “Pitfalls”- Don’t create pages that already exist —
list_pagesfirst; useupdate_pageif the slug is taken. update_pagetakes apatchobject, not flat fields.- Don’t hardcode block field names from memory — schemas are
per-site (
list_block_types/read_block_type). - One
level: h1per page (core/heading) — SEO + screen readers. - Don’t simplify data during import — preserve Swedish characters in
labels (
affärsutveckling, not the ASCII-folded slug), keep titles verbatim; slugs are derived for URLs only. - Minimal JS. Inline
<script>in page content is stripped by the sanitizer. One-off interactivity usescore/embed; reusable interactivity uses a block-typescript; site-wide code belongs inscripts_body_end.
Organization access
Section titled “Organization access”When a Cloud write reports organization_trial_ended or organization_access_ended, use read_organization_access on the selected Site. Explain the deadline and recovery URL; keep reads and export available when permitted. Do not repeatedly retry or create another organization to bypass the lock. Self-hosted installations are unrestricted by default. A trial does not limit AI credits: the organization supplies its own provider key.
Fluid typography and component rhythm
Section titled “Fluid typography and component rhythm”Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales. Check the connected versions before using this contract. New sites use render version 10. For an existing site, test a Version first; upgrading rendering and replacing existing roles are separate, deliberate choices.
Use fonts.size_base (default 16), typography.ratio_mobile (1.2),
typography.ratio_desktop (1.25), typography.rhythm (1), and
typography.line_length (65 ch). Set the controls once, then use named type,
spacing and line-height steps. apply_standard_styles uses those steps;
overwrite: true resets existing roles, so review their current values first.
Explicit sizes and breakpoint overrides still win and are not silently migrated.
Heading role margins belong to editorial flow. Component titles use their own
padding/gap, and opening headings in containers/sections add no top margin.
Use role-linked core/heading size auto or theme; explicit hero sizes remain
deliberate exceptions. Do not compensate with per-card negative margins.
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
and webfonts. Boxes should get their outer air from padding. Keep exceptions
in decisions.md, and run lint_design_tokens before launch.