Building sites

Pages from designs

A design (a mock-up, a flat page, a hand-off from another tool) becomes a page through files, so a person or an agent can write it, check it and send it to a site without the builder. Core knows every widget a site can place and every setting each one reads; tools read that catalog, write a page against it, and core says exactly where the page goes wrong.

The page file

A page is a page@1 file, the same seed format a site folder keeps under seeds/pages/ (see The site folder):

{
  "gansey": "page@1",
  "$schema": "https://www.ganseycms.com/schema/page-1.json",
  "path": "/services/kitchens",
  "title": "Kitchens",
  "about": "Fitted kitchens in solid oak and ash.",
  "nav": { "show": true, "order": 2 },
  "parent": "/services",
  "sections": {
    "hero": {
      "settings": { "padding": "large", "min_height": "80vh", "background_image": "file:seeds/files/kitchen.jpg", "overlay": "rgb(0 0 0 / .4)", "text_color": "background" },
      "columns": [
        { "width": 7, "widgets": {
          "title": { "kind": "heading", "settings": { "text": "Kitchens made to ==measure==", "level": 1 } },
          "intro": { "kind": "text", "settings": { "html": "<p>Drawn, made and fitted by the same hands.</p>" } },
          "cta": { "kind": "button", "settings": { "label": "Book a visit", "href": "/contact" } }
        }, "order": ["title", "intro", "cta"] }
      ]
    },
    "work": { "columns": [ { "widgets": [ { "kind": "gallery", "settings": { "media_ids": ["photos/one.jpg", "photos/two.jpg"], "columns": 2 } } ] } ] }
  },
  "order": ["hero", "work"]
}

Sections, columns and widgets may each be a list or a map keyed by id with an order list beside it (a section's order orders its columns, a column's its widgets). Ids may be left out; core fills them in and stores the canonical shape: lists, every id set. A bare page document ({ "sections": [ … ] }, the shape The document shows) works too, with the path and title given on the command line.

A media setting (media_id, media_ids, a section's background_image) takes a library id, file:<path> (a file in the site folder), a file name next to the page, a URL, or a /media/… address the site already serves. Inside text, such as an <img> in rich text, file:<path> stands for the file's address.

$schema is optional. gansey site schemas <dir> writes the schema for every kind of site file (page-1.json, part-1.json, content-type-1.json, …); point an editor at them and it completes setting names and flags wrong values as you type. The page and part schemas carry the widget catalog, so they know each widget's settings.

The catalog

gansey widgets --json > catalog.json

lists every widget kind (core's, the program's extensions' and, with the site's database, its modules'), each with every setting its renderer reads: the name, its type (text, rich_text, number with min and max, choice with its values, colour, length, media, items with their fields, …), the default and a line on what it does. Beside the widgets it lists the settings of sections, columns and a widget's layout, animation and custom, and the built-in icon names. With a database it adds what settings may name on that site: the theme's colours and every --gsy-… custom property it defines, its content types with their fields (key and kind) and its taxonomies. Without one it is core's catalog alone. gansey widgets without --json prints the same as a short list.

Checking a page

gansey page check site/seeds/pages/services--kitchens.page.json

checks the file without saving anything: its shape, every widget kind, every setting against its type, file references against the files next to the page (or up the site folder, for file: paths) and, with a database, colour variables, content types, taxonomies and field keys against the site. Each problem names where it is as a JSON pointer into the file as written, what was expected and, where there is one, the nearest allowed value:

site/seeds/pages/services--kitchens.page.json: /sections/hero/columns/0/widgets/title/settings/level: 9 is out of range; expected a whole number from 1 to 6 (did you mean 6?)
site/seeds/pages/services--kitchens.page.json: /sections/work/columns/0/widgets/0/settings/colums: gallery has no setting “colums”; the renderer ignores it; expected one of media_ids, columns, ratio, size, captions (did you mean "columns"?) [warning]
1 error, 1 warning

An unknown widget kind, a value a setting does not take, or a file that is not there is an error; a setting no widget reads is a warning, because the renderer would ignore it. The command exits 1 on errors, 2 on warnings alone and 0 on a clean page. --json prints { ok, problems: [{ pointer, severity, message, expected, nearest }], media } for tools. Pages are checked only when they are checked, saved or pushed, never as they render.

Pushing a page

gansey user token --email designer@example.test --scope pages:draft   # on the site, once
gansey page push site/seeds/pages/services--kitchens.page.json --to https://staging.example.test --token "$TOKEN"
gansey page push hero.json --to https://staging.example.test --path /offer --title "Spring offer"

sends the page to the site's POST /api/v1/pages/drafts. It lands as an unpublished draft: a new page at its path, or a new revision of the page already there (whose published revision stays live, and whose title and navigation are left alone). The revision is noted "Pushed" and made by the token's user, who must be able to edit pages (and needs the manage site permission for custom code). Nothing is published, and the site's page cache is left as it is; review the draft in the builder and publish it there.

Files the page names go up with it: files next to the page, file: paths found in the page's folder or a folder above it, and URLs, which the command fetches. The site puts each in the media library (a file it already has, byte for byte, is reused) and rewrites the references to library ids, or to addresses inside text. The site checks the page against its own catalog first, modules included; if anything is an error nothing is saved, and the problems come back with their pointers, as page check prints them. --json prints the site's answer. The token can be given as GANSEY_API_TOKEN instead.

Working from a design

  1. Read the catalog. Run gansey widgets --json with the site's database before writing anything: widgets for what can be placed, elements for the sections, columns and boxes around them, theme and content_types for what settings may name. Build the page from what it lists.
  2. Map the design's variables onto the theme. Brand colours go into the theme's colour tokens (primary, secondary, accent, background, surface, text, muted), typefaces into the font tokens, spacing and widths into the layout tokens (see Tokens and custom properties); anything else the design names becomes a custom token, --gsy-<key>. Settings then use the token names ("color": "accent") or the properties ("background": "var(--gsy-color-surface)"), never the design's literal values, so a later theme change carries through every page.
  3. Write the page as a page@1 file (or push a bare document), with the design's images beside it or under seeds/files/.
  4. Check it, with the site's database when you have one, and fix each problem at its pointer, taking the nearest value where it fits. Repeat until page check exits 0.
  5. Push it and review the draft in the builder. On a site that follows its site folder, a page file under seeds/pages/ arrives as a seed instead, and a changed one as a draft.

Reach for the code widget only where no widget fits (a bespoke animation, an embed, a one-off layout the sections and columns cannot make): it is served unfiltered, needs the manage site permission to change beyond its images, links and text, and is invisible to the catalog's checks. A section's split_color, min_height, overlay and max_width, columns' tablet and phone widths, and a widget's layout cover most of what a design asks of the page around its content.