Skip to content

Version pages and regions as code

Goal: get your site’s structure, its pages and its global regions (header, footer), into version control next to your components, and make the repository a place changes can be made, not just a mirror.

The Canvas quickstart pushed components: the building blocks. What editors assemble from them in Drupal Canvas (pages and the regions around them) also pulls, versions, and pushes as JSON specs.

  • Every Canvas page and global region from your site in the repository, as reviewable JSON.
  • A change made in a page spec locally, live on the site after one push.
  • External image references in specs resolved to site media with canvas reconcile-media.
  • A component project connected to your site with working Canvas CLI credentials (Canvas quickstart)
  1. Terminal window
    npx canvas pull

    The scaffold’s canvas.config.json syncs pages and regions by default (sync.pages, sync.regions), and the pull plan says exactly what’s coming:

    │ Plan
    │ Components: 24 pull (24 new)
    │ Assets: global CSS pull
    │ Pages: 6 pull (6 new)
    │ Global regions: 2 pull (2 new)

    Pages land in pagesDir (pages/) as one JSON spec per page, named by slug; regions land in regionsDir (regions/header.json, regions/footer.json). Pulling requires the canvas:page:read and canvas:page_region scopes; flag details are in the Canvas CLI reference.

  2. // pages/homepage.json (trimmed)
    {
    "uuid": "20354d7a-e4fe-47af-8ff6-187bca92f3f7",
    "title": "Homepage",
    "path": "/homepage",
    "description": "",
    "elements": {
    "cc1b44a4-bd8e-4dae-8552-4bc34147b064": {
    "type": "js.card_container",
    "props": { "layout": "33-33-33", "heading": "What we offer." },
    "slots": {
    "content": [
    "6e6d696c-b0c5-46a7-b072-250ef5295fc1"
    ]
    }
    },
    "6e6d696c-b0c5-46a7-b072-250ef5295fc1": {
    "type": "js.card",
    "props": { "heading": "Feature or benefit" }
    }
    }
    }

    elements is a flat map keyed by element UUID. Each element names its component (type: "js.<machineName>"), its prop values, and, for components with slots, which other elements fill each slot, as an ordered array of UUIDs. Nesting is by reference, not by indentation: the tree is reconstructed from those arrays.

    Media props carry a _provenance entry alongside (target_id: the site’s media entity ID), which is how a spec references an image that lives on the site.

  3. Edit a prop in the spec (a heading, a card’s text), then:

    Terminal window
    npx canvas push

    The push plan lists the pages and regions it will update alongside components and CSS. Reload the page on the site: the edit is live. This is the loop that makes the repository authoritative; the browser editor and the codebase edit the same structure. Pushing pages uses the canvas:page:create / canvas:page:edit scopes; regions use canvas:page_region.

    A page created only as a local JSON file pushes the same way. Starting from a pulled page’s spec beats writing one from a blank buffer: the editor is the fastest place to compose structure, and the spec format is easiest to get right by example.

  4. Regions are smaller specs with the same element map, plus a status flag:

    regions/footer.json
    {
    "status": true,
    "elements": {
    "bf4d99cc-0951-4b69-a323-04f21c7d53ce": {
    "type": "js.footer",
    "props": { "text": "Built with Source CMS" }
    }
    }
    }

    A region renders on every page: where it appears is fixed by the project’s layout (src/layout.jsx in the scaffold, <Region name="header" /> / <Region name="footer" />), and what it contains is this spec. Edit and canvas push; the change appears site-wide.

  5. A spec can reference an image by external URL (an editor pasted one, or a page was composed elsewhere). canvas validate fails on these:

    Unreconciled external media URL "https://placehold.co/[email protected]…".
    Run `canvas reconcile-media` to resolve.
    Terminal window
    npx canvas reconcile-media

    The command uploads each external image to the site as a media entity and rewrites the spec in place: src becomes a site-local file URL, and _provenance records the created media entity (target_id) and the original source_url. Commit the rewritten specs; validate now passes and the page no longer depends on a third-party host.

canvas validate fails with Unreconciled external media URL

expected for any external image reference; run npx canvas reconcile-media (step 5).

A slot edit made components vanish from a page

the arrays under slots are the tree. A UUID removed from a slot array orphans that element (and its own children). Restore the UUID, or delete the orphaned elements too.

Push succeeds but the page looks unchanged

check you edited the page the site actually serves; path in the spec is the routing truth, and two specs can have similar titles.

A pulled page references media you don’t have locally

that’s fine. Specs reference media by site-side target_id; nothing image-related needs to exist in the repository.

Was this page helpful?