Guides · Editing

Canvas

Every page of your site side by side on one board, the real site in each frame. Make a site editable with the bridge, then pan, zoom, click a page to edit it with its form or by prompt, and save one page or all of them.

11 min read

The Canvas (in the sidebar) shows every page of the environment you're editing on one board: your site, rendered at the environment's preview URL (Settings) in a frame per page, with the page's path appended (with {locale} in the preview URL, like https://example.com/{locale}, the locale chosen at the top goes there and the path is the one in that locale). A product page opens at the environment's product path instead (/products/<handle> by default).

Each page type is a column, with the home page's first. A column with more than six pages shows five of them and a sixth frame with a page switcher over it. Edit on page on an entry's page opens the board with that page selected and in view (/canvas?entry=<id>).

Making a site editable

  1. Include the bridge on every page. With the React SDK that is <ContentBridge /> in the root layout; with a bundler, getContentBridge() from @obelism/content/bridge; without one, a script tag:

    <script src="https://content.obelism.studio/bridge.js"></script>

    It does nothing outside the canvas. Inside it, it only talks to the CMS origin it was loaded from. The script tag puts it on window.ObelismBridge. Options go on the tag: data-prompt="false" leaves the prompt box of editing on your site out, and data-url names the CMS when the file comes from an npm CDN.

  2. Mark blocks with their uid, which every block in a delivery response has:

    <section {...editable(block)}>…</section>
    // or, without the SDK:
    <section data-cms-uid={block._uid}>…</section>
  3. Show drafts in preview. Every frame's URL carries cms_preview=1, cms_entry=<id>, cms_environment=<name> and cms_token: a token made for the person editing, each time the canvas opens. Read the page with cms_token as the bearer token, on your server, naming your space and environment (space and environment on ContentClient, the Obelism-Space and Obelism-Environment headers otherwise), and the delivery API answers drafts. Without them, a cms_token someone made for their own space would put that space's drafts on your site. It only reads, acts as that person (with their role at the time) and expires after 8 hours. A missing, made-up or expired token gets a 401: show the published page then. Previews and draft mode has the code, and the rules that keep drafts off your public pages.

    Watch out

    Decide on drafts by cms_token, never by cms_preview alone with a preview token of your own: anyone can add cms_preview=1 to a URL.

  4. Re-render on live updates (optional, recommended): useLiveEntry(entry) in React, or

    ObelismBridge.on('update', ({ entry }) => render(entry))

    entry has exactly the shape of a delivery API entry for a preview token, built from the unsaved draft. Without an update listener the page reloads after every save instead.

  5. Allow framing by the CMS: if your site sends Content-Security-Policy: frame-ancestors, include https://content.obelism.studio, and drop X-Frame-Options: DENY for preview requests. The preview URL has to be reachable by the editor's browser too: a private host — Vercel deployment protection, Cloudflare Access, a private Oxygen environment — shows its login page in the frame instead of your site (Shopify Oxygen).

Products

With a Shopify store connected to the space, the canvas has a Products and a Collections column after the others. Products holds the product pages of every product page type and ends with Any product: your site's product route (the environment's product path, /products/<handle> by default) for a product of the store, the store's first to begin with. Select it and Choose a product to see another. Collections does the same for collection pages and Any collection, at the collection path (/collections/<handle> by default).

For a product (and a collection the same way):

  • A product with a page: Show its page selects that page's frame, where you edit it like any other page. Choosing a product that has one goes straight there.
  • A product without one: Add a product page makes a draft page of the product page type for it, with the product filled in and its handle as slug, and opens it. The space needs a product page type for this (Settings → Shopify).

Products without a page are shown as visitors see them; there is nothing to edit on them in Content.

Moving around

The board fills the page beside the navigation, which shrinks to a rail of icons there. Each column is headed by its page type in the type's colour, with how many pages it has. At the bottom is the prompt bar, Ask Obelism. At the top left, the environment, a page type filter (All page types, or one), Go to page (any page of the board), New page, and Save all and Publish all while pages have changes. At the top right are the board's controls, and a pointer button whose tip sums them up:

  • Scroll or drag to move the board, ⌘ or Ctrl and scroll (or pinch on a trackpad) to zoom, or − and +. Fit shows the whole board. On the canvas, pinching zooms the board, not the browser.
  • Desktop and Mobile render every frame 1440 or 390 pixels wide, so your site's own breakpoints apply. With more than one locale, the locale select shows every page in that one.
  • Only the frames near what you're looking at load your site, at most eight at a time, each marked Rendering until it has loaded. The last dozen you've seen keep theirs when you move on; the others show an outline of a page until you move to them.

Editing a page

Click a page to select it. Its fields show in a panel floating on the right, in the same form as on the entry page, and clicks inside it now reach your site: clicking a block selects it. Hold Space to move the board over the selected page, and press Esc to clear the selection.

Shift-click (or ⌘/Ctrl-click) adds pages to the selection or takes them out, and ⌘A (Ctrl A) selects every page of the last clicked page's column. The prompt bar shows what it will change: the selected pages as chips, which you can take out one by one or with Clear selection. A store column's last frame (Any product, Any collection) has no page of its own, so the bar asks you to select a page while it's selected.

  • Edits show on the page as you type, before they are saved.
  • The prompt bar acts on the selected page, or the selected block (a chip of its own): type what to change and it changes it by prompt. The change shows on the page at once, unsaved, and Suggested by Obelism above the bar says what changed: Keep it, Try another (undo it and ask again) or Undo, with the first text it changed before and after. Until you decide, the changed pages carry a Preview chip.
  • With several pages selected, each gets the same prompt, one after another; the panel lists them as they land.
  • New page asks for a path and what the page should say, and the model writes it as a draft of the selected page's type (with no page selected, the first page's of the board), which then opens selected (save or undo other changes first).
  • With a block selected, Entry fields at the top of the panel goes back to the page's own fields.
  • Every page keeps its unsaved changes while you edit others; a page with changes is marked Unsaved. The panel's Save saves the selected page and Publish saves and publishes it; Save all and Publish all do the same for every changed page, each with its own check that nobody saved it in the meantime. A page that can't be saved or published is named. Closing the tab with unsaved changes asks first.
  • Where publishing is reviewed and you may not publish, Publish and Publish all ask for review instead.

Variants side by side

A page with an A/B test or flag block lists its tests and flags under Variants in the panel. Side by side adds a column after the others with a frame per option of that test or flag, each showing the page as it is with that option's content, while you edit the page in its own frame. Hide takes the column away again.

Each variant frame gets the page's unsaved content with every block of that test or flag holding only its option's blocks, so whatever option your site picks for the visitor, it can only render that one. This needs a site that re-renders on live updates (useLiveEntry, or ObelismBridge.on('update', …)); a site that only reloads after saving shows its own pick in every frame.

Changing the whole site

With no page selected, the prompt bar takes a request about the whole site, several pages at once: "make every hero shorter", "end each journal post with a call to action". It works in two steps:

  1. A plan: the model reads an outline of the pages on the board (their names, paths, types and the start of each block) and answers which of them the request is about, and what to ask about each, at most 12.
  2. Every step then runs as that page's own prompt, one after another, with the same checks as a prompt on one page.

The panel lists the steps as they land; click one to go to its page. The changes are unsaved, marked on their frames: look them over, Undo them all from the suggestion above the bar, and Save all or Publish all when they're right. A request reaches the pages on the board, so in a column with a switcher only the page it shows.

Whole pages

A frame is as tall as its page. The bridge tells the canvas the page's height, and the canvas tells the bridge the screen height the frame stands for (900 pixels on desktop, 844 on mobile). The bridge then rewrites vh, svh, lvh and dvh sizes in your stylesheets as pixels of that height, so a 100vh hero stays one screen high instead of growing with the frame. Stylesheets from another origin can't be read and keep their units. This needs @obelism/content 0.20 or later (or Content's own /bridge.js); with an older bridge, frames are one screen high and scroll inside.

Click rates

With Show click rates on the page in the panel, the selected page shows how often each of its blocks is clicked, as a label on each block. The switch shows once the page has click rates. The switch is remembered in your browser. The numbers come from Obelism numbers, the events service every Obelism product shares:

  1. Load numbers' script on every page of the site (its Setup page has the tag for your Organization and environment).

  2. Tag a block's clickable element with its uid, in production too:

    <a href={linkHref(block.link) ?? undefined} data-numbers-id={block._uid}>
    	{block.label}
    </a>

    The script sends a click event with that uid for every click on (or inside) the element.

A block's click rate is the share of the visitors who viewed a page it was clicked on who clicked it, over the last 30 days, in the environment you're editing. Blocks nobody clicked show nothing.

When a frame shows published content

A frame that shows the published page, or no page, instead of the draft almost always comes from the 401 fallback in step 3 of making a site editable. Log the response's body before falling back (error.body on the SDK's ContentApiError): it says which of these it is.

  • The entry was never published. The fallback has nothing to show, so the frame shows the page as if it had no entry: on a product route, the product without its blocks.

  • cms_token doesn't reach the client, and the error is Invalid or revoked token. Check what your server actually reads, especially after a rewrite or a redirect. A Next.js rewrite that captures the query with has and a bare key names the value by its letters only (cms_token is :cmstoken in the destination). Name it with a group instead:

    next.config.ts
    {
    	source: '/products/:handle',
    	has: [{ type: 'query', key: 'cms_token', value: '(?<cmsToken>.+)' }],
    	destination: '/preview/products/:handle?cms_token=:cmsToken',
    }
  • The space or environment doesn't match (token_place_mismatch). The token is for the environment you're editing, and the site names its own (CONTENT_SPACE_ID, CONTENT_ENVIRONMENT). Point each environment's preview URL at a deployment configured for that environment. With a connected deployment, that is the host environment whose rule leads there, such as a preview deployment for staging.

Bridge reference

window.ObelismBridge, or what getContentBridge() returns:

  • isEditing: true inside the canvas.
  • entryId: the entry being edited.
  • on(event, listener): update ({ entry }), saved, published, and change for the prompt state.
  • prompt(text), undo(), createPage(path, prompt): when editing on your site; see prompt editing.

The bridge and the canvas talk over postMessage. The site sends { source: 'obelism-content-bridge', type } with ready (with the page's height), size ({ height }, whenever it changes) and select; the canvas sends { source: 'obelism-content-editor', type } with connect, viewport ({ height }, the screen height the frame stands for), update, select, saved, published or stats. stats carries the blocks' click rates and the switch, { stats: { [uid]: { clicks, clickers, viewers, rate } }, visible }, which the bridge shows on the page next to each data-cms-uid while visible is true (@obelism/content 0.7 and up; see the SDK's README, "Click rates"). Both sides check the other's origin.

Editors can also work on the site itself, outside the dashboard: see editing on your site.

On this page

Marks in the margin

  • Note
  • Watch out
  • Updated

Scribes marked doubtful lines in the margin. That practice is called obelism.

Stuck on a step?