Guides · Editing
Editing on your site
Editors sign in on your own site at /cms/login, see drafts in draft mode and change the page by prompt, then save or publish. One route handler with the SDK.
The canvas shows your site inside the dashboard. Editors can also work on the site itself: they open /cms/login on your site, sign in to Obelism Content, and are sent back to the page in draft mode. The page then shows drafts, and a prompt bar at the bottom lets them change it by typing ("make the hero heading shorter"), then Save or Publish.
yoursite.com/cms/login?return_to=/pricing
→ content.obelism.studio/site/connect?… (signed out? → www.obelism.studio/login, and back; then Continue)
→ yoursite.com/cms/callback?code=…&state=… (code → site token, draft mode on)
→ yoursite.com/pricing (drafts + prompt bar)Setting it up
With the SDK it is one route and a few lines in the code that already reads content.
-
Set the preview URL of the environment (Settings) to your site. Sign-in only sends people back to that origin.
-
Mount the handler on
/cms/*. In Next.js (App Router):app/cms/[...path]/route.ts import { createSiteEditing } from '@obelism/content/site' import { draftMode } from 'next/headers' const handler = createSiteEditing({ spaceId: process.env.CONTENT_SPACE_ID!, environment: 'production', onSignIn: async () => (await draftMode()).enable(), onSignOut: async () => (await draftMode()).disable(), }) export { handler as GET, handler as POST }In React Router (framework mode) it is one resource route, and no draft mode: the site token's cookie is the switch, so there is nothing to turn on.
app/routes/cms.tsx import { createSiteEditing } from '@obelism/content/site' import type { Route } from './+types/cms' const handler = createSiteEditing({ spaceId: process.env.CONTENT_SPACE_ID!, environment: 'production', }) export const loader = ({ request }: Route.LoaderArgs) => handler(request) export const action = ({ request }: Route.ActionArgs) => handler(request)Add
route('cms/*', 'routes/cms.tsx')toapp/routes.ts: it ranks above the splat that serves your pages. Return itsResponseas it is: it sets several cookies, and rebuilding the response loses them. On Oxygen the two settings come from the Worker'senvbinding (how).The handler takes a standard
Requestand returns aResponse, so it works the same in any framework that has those. -
Read drafts with the session's token, which works with the delivery API like a preview token. In Next.js, while draft mode is on:
import { ContentClient } from '@obelism/content' import { readSiteToken } from '@obelism/content/site' import { cookies, draftMode } from 'next/headers' const token = (await draftMode()).isEnabled ? readSiteToken(await cookies()) : null const content = token ? new ContentClient({ token, space: process.env.CONTENT_SPACE_ID!, environment: process.env.CONTENT_ENVIRONMENT!, init: { cache: 'no-store' }, }) : deliveryIn React Router there is no draft mode: read the token from the request's cookie on every request (
readSiteToken(request.headers.get('cookie'))), and answer draft responses withCache-Control: private, no-storeandX-Robots-Tag: noindexyourself. Previews and draft mode has the function.Once the token stops working (sign-out, 8 hours, or the person leaving the organization), the API answers 401. Show the published page then. After 8 hours the token's cookie is gone while Next.js keeps draft mode on; see Next.js draft mode.
-
Mark the page's entry next to its blocks, so the prompt bar knows what it is editing:
<main {...editable(entry)}> <section {...editable(block)}>…</section> </main>A block inside another marked entry (a navigation, say) is edited in that entry.
-
Render live changes with
useLiveEntry(entry)(React) orbridge.on('update', …), as for the canvas. Prompts change content in the browser's memory, andupdateshows it without saving. A page without anupdatelistener shows the change once it is saved, after reloading.
<ContentBridge /> (or getContentBridge()) starts the prompt bar on its own whenever the canvas isn't around and the editing cookie is set.
What editors get
- Prompt: as in the canvas, about the selected block (click one while the bar is open) or the whole entry, in the page's language (
<html lang>, when the space has it). "Add a section on eligibility" adds a block, of the type whose description fits best; with a block selected it goes right after it, and only that block can change. Undo steps back through the prompts. - Form: switch the bar from Prompt to Form to edit the selected block, or the whole entry, with the same form as the dashboard and the canvas: every field, the asset and entry pickers, uploads, and blocks with + Add block, in the page's language. Edits show on the page as you type and stay unsaved until Save; a prompt, an undo or another selection shows in the form. It replaces the JSON editor of 0.13 (
bridge.view()andbridge.applyJson()remain). (@obelism/content0.14 and up) - Pages: the page menu lists every page, drafts included, and opens one, so a new draft that nothing links to yet can be reached. (
@obelism/content0.13 and up) - New page: a path (
/pricing, or/solutions/agenciesunder an existing page) and what the page is for. The model writes the whole page, with the content type of the page the editor is on and that page as an example of tone and blocks, and it is stored as a draft right away; the bar then opens it. On a page that isn't an entry, the bar only offers New page, and the space's page content type is used when it has just one. - Add a product page: on a product route that marks its product with
editableProduct(product)and has no product page yet, the bar offers this instead of a prompt. It makes a draft of the space's product page type for the product (titled after it, its handle as slug, underproductswhen the space has that page or folder), and the page reloads to show it, ready for a prompt or the form. Product pages open from the page menu at your product route (productPath, default/products/<handle>). (@obelism/content0.18 and up) - Add a collection page: the same on a collection route that marks its collection with
editableCollection(collection)and has no collection page yet, undercollections; collection pages open atcollectionPath(default/collections/<handle>). (@obelism/content0.20 and up) - Save stores the changed content as the draft. If someone else saved the entry since it was loaded, the save is refused and nothing is lost: the changes stay in the browser.
- Publish saves first, then publishes every entry changed here (or the page's entry when nothing was).
- Exit editing signs out (
/cms/logout) and turns draft mode off. Leaving the page with unsaved changes asks first. - People who may only read content see drafts and an exit, and no prompt bar.
How it works
/cms/loginsends the browser to/site/connectwith the space, the environment, aredirect_uri(/cms/callbackon your site), a randomstateand a PKCE challenge (S256). Signed out, that goes through the Obelism login on www.obelism.studio first. Signed in, Content checks the person may open the space and thatredirect_uriis on the preview URL's origin, then shows them that origin, the space, the environment and their role. Only when they choose Continue does it redirect there with a one-time code, valid for a minute./cms/callbackchecks thestate, and trades the code and the PKCE verifier for a site token (POST /api/site/v1/token). It keeps the token in an httpOnly cookie (obelism_content_token), and sets a readableobelism_content_editingcookie that only tells the bridge to start.- The site token acts as that person in that space environment, with their role at the time of each request, like a management token acts as its creator. It expires after 8 hours.
- The prompt bar calls
/cms/api/…on your site. The handler forwards those calls to the site API with the token, so the token never reaches page scripts. Writes need anx-obelism-content: 1header, which a cross-site form can't send. - Form is a page of Content (
/site/formon content.obelism.studio) in an iframe in the bar. To open it, the handler asks the site API for a URL with a one-time code (POST /cms/api/form→POST /api/site/v1/form); the page trades the code for a form token of its own, kept in memory, which only works on the form's routes and ends with the site token (signing out, expiry). The page and the bar exchange the content bypostMessage, the page only with your site's origin. References and shared blocks offer Open; since the dashboard refuses to be framed, that opens the entry in a new tab rather than in the panel. Content lets any site frame that page, so nothing on your side needs changing; if your site sets aContent-Security-Policy, itsframe-src(orchild-src/default-src) has to allowhttps://content.obelism.studio.