CodelabDocs
Model

Page

A routed container whose element tree is what visitors see at a URL.

A Page is a routed tree inside an app. Metadata (name, segment, role, publish state) plus an element tree. The published URL is not stored as a string — it is the chain of routePattern values walked down parentId.

The page tree is rooted at body. Add children under that root. Elements are nodes; props are attributes.

Fields

NameTypeDefaultDescription
routePatternstringderivedThis node's segment, not a URL. "about", "events/$id", or "" for an index leaf. No leading slash. $name segments become {{page.params.*}}.
type"page" | "layout""page""page" is a leaf that renders content. "layout" is a named folder that wraps children through exactly one codelab.outlet. Layouts never match a URL alone.
parentIdstring | emptyemptyThe layout this node nests under, or empty at the root. Only a layout can be a parent.
isHomebooleanThe app's home. Every app has exactly one.
status"draft" | "published""draft"Draft is not on the published site.
visibility"public" | "private""public"Gating after publish. Ignored while draft. Private pages run accessPolicy; a failed gate looks like a 404.
fieldsfield listAuthor-declared props for this page. Route facts (id, appId, params, search) are available as {{page.*}} without being listed here.

Home page tree rooted at body, with the page catalog open
Home page tree rooted at body, with the page catalog open

A new page always gets a body root. Add children under that root with add_element — do not call add_root_element on a page.

A layout must contain one codelab.outlet (and only one). Without it, nested pages have nowhere to render. Promoting a page to "layout" does not insert the outlet — add a codelab.outlet primitive yourself, then set children's parentId.

The layout's own URL is an index child: a "page" under that layout with routePattern: "". Empty is not a layout path; a layout must have a segment ("dashboard", "events").

Inside the page tree, {{props.*}} is this page's props (entering a component replaces that frame). Ambient route facts are also {{page.id}}, {{page.appId}}, {{page.params.*}}, {{page.search.*}}.

How it relates

  • Applist_pages / create_page run on the active app from set_context.
  • Layout pages — shared chrome (header, nav, footer) plus codelab.outlet. Leaves nest with parentId.
  • Elements — the page is an element tree. Pass container: { _tag: "Page", id: pageId } on add_element / move_element / remove_element.
  • Components — drop in as elements with kind { _tag: "Component", value: componentId }. Bind instance values with update_props on that node.

MCP tools

All of these need contextId except the outline/search tools that take a pageId (those check you own the page).

ToolWhat you use it for
list_pagesLightweight list: id, name, routePattern, type, parentId, isHome. No elements. Rebuild the tree from parentId.
create_page{ name, routePattern?, type?, parentId?, isHome? }. Omit routePattern to derive from the name — omit is not "". "" is an index page.
update_pagePartial: name, status, visibility, type, parentId, routePattern, accessPolicy, SEO, loader. Omitted fields stay. Not isHome — promoting home demotes the previous one.
delete_pageCascades the page's elements. Cannot delete the home page or the last remaining page.
load_page_outlineIndented tree (kind, id, aria-label, styles/props when set). Heavy — prefer search when you know the target.
find_element_in_pageSearch by aria-label, text, or kind → elementIds.

Then mutate the tree with the element tools, passing container: { _tag: "Page", id: pageId }.

Layout workflow:

  1. create_page with type: "layout" and a segment (routePattern: "dashboard").
  2. Add chrome plus one codelab.outlet under body.
  3. create_page an index leaf with routePattern: "" and parentId of the layout.
  4. update_page other leaves to the same parentId.

Gotchas

  • Segment vs URL. "events/$id" is this node's piece. The published path is the parent chain (/events/42). Re-parenting rewrites descendant URLs for free.
  • "" vs omitted routePattern. Omit → auto-derive from the name ("About""about"). Pass "" only for an index leaf. A layout cannot use "".
  • Outlet is required, not inferred. type: "layout" without codelab.outlet is rejected on placement. Exactly one outlet.
  • Draft is invisible. New pages are draft + public. Publish with update_page { status: "published" } or the live site 404s.
  • Same add-element rules as everywhere. aria-label for purpose, omit targetPosition to append, inspect_element_type before Radio / Field / Dialog / Menu. See Element.
  • Do not add_root_element on a page. The body root already exists.

On this page