# Version pages and regions as code

**Goal:** get your [site](/start-here/glossary/#site)'s structure, its pages and its [global regions](/start-here/glossary/#global-region) (header, footer), into version control next to your [components](/start-here/glossary/#component), and make the repository a place changes can be made, not just a mirror.

The [Canvas quickstart](/source-cms/canvas-components/quickstart/) pushed components: the building blocks. What editors *assemble* from them in [Drupal Canvas](/start-here/glossary/#drupal-canvas) (pages and the regions around them) also pulls, versions, and pushes as JSON specs.

## What you'll have when you're done

- 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`.

## Prerequisites

- A component project connected to your site with working Canvas CLI credentials ([Canvas quickstart](/source-cms/canvas-components/quickstart/))

## Steps

<Steps>

1. ### Pull the site's structure

   ```bash
   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:

   ```text
   │  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](/start-here/glossary/#scope); flag details are in the [Canvas CLI reference](/source-cms/reference/canvas-cli/).

2. ### Read a page spec

   ```json
   // 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](/start-here/glossary/#slot), 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. ### Make a change locally and push it

   Edit a prop in the spec (a heading, a card's text), then:

   ```bash
   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. ### Edit a global region

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

   ```json
   // 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. ### Reconcile external images

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

   ```text
   Unreconciled external media URL "https://placehold.co/800x600@2x.png…".
   Run `canvas reconcile-media` to resolve.
   ```

   ```bash
   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.

</Steps>

## When something goes wrong

**`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.

## Next steps

- [Push to a live site](/source-cms/canvas-components/push-to-a-live-site/): what a push does to a site editors are already using, and how to roll one back.
- [Render CMS content with content templates](/source-cms/canvas-components/content-templates/): the same authoring loop, for content-driven rendering.
- [Canvas CLI & schema reference](/source-cms/reference/canvas-cli/): every command and flag this guide used.
