Documentation

Visual editing & live preview

The entry editor can render your real site side-by-side with the form and stream draft edits into it live — unsaved changes, personalization variants and all. Your site opts in with a small postMessage listener; no SDK required.

1. Configure the collection's preview

In the collection's settings, set:

baseUrlYour site's origin, e.g. https://www.example.com. Must be http(s).
pathTemplateThe page path, composed from entry fields with {fieldApiName} tokens — e.g. /blog/{slug}. Defaults to /{slug}.
slugFieldWhich field is the entry's slug (default slug). Also used by the public API's get-by-slug endpoint.

The editor then loads baseUrl + pathTemplate in an iframe with ?preview=1 appended. Open in new tab opens the same URL without the flag — the published page.

Turn it on from the entry editor's ⋮ (More actions) menu → Visual editing. A preview pane opens beside the form; the same menu item (now Hide preview) closes it.

2. The bridge protocol

Two message types, both plain postMessage:

gluso:preview-readyYour page → editor, once your listener is set up. Tells the editor to (re)send the current draft.
gluso:previewEditor → your page, on ready and debounced (~250 ms) after every edit. Carries { status, data } where data is the draft's field values keyed by API name.

When personalization is enabled, data arrives with the editor's currently-selected variant combination already resolved — flipping variants in the editor re-renders your page with that audience's values, using the same fallback rules as production (see Variants & fallbacks).

3. Implement the listener

On your page, when ?preview=1 is present: announce readiness, then re-render from each message. Framework-agnostic version:

// Runs on your page when it's loaded by the editor with ?preview=1
const GLUSO_ORIGIN = 'https://your-gluso-app.com'

if (new URLSearchParams(location.search).has('preview')) {
  window.addEventListener('message', (event) => {
    if (event.origin !== GLUSO_ORIGIN) return
    if (event.data?.type !== 'gluso:preview') return
    // event.data.data = draft field values keyed by API name
    render(event.data.data)
  })
  // Ask the editor to send the current draft
  window.parent.postMessage({ type: 'gluso:preview-ready' }, GLUSO_ORIGIN)
}

In a component framework, feed the message into state and let the page re-render — a complete React hook:

// lib/usePreview.ts
'use client'
import { useState, useEffect } from 'react'

const GLUSO_ORIGIN = 'https://your-gluso-app.com'

export function usePreview<T>(initial: T): T {
  const [data, setData] = useState<T>(initial)
  useEffect(() => {
    if (!new URLSearchParams(location.search).has('preview')) return
    const onMessage = (event: MessageEvent) => {
      if (event.origin !== GLUSO_ORIGIN) return
      if (event.data?.type === 'gluso:preview') setData(event.data.data)
    }
    window.addEventListener('message', onMessage)
    window.parent.postMessage({ type: 'gluso:preview-ready' }, GLUSO_ORIGIN)
    return () => window.removeEventListener('message', onMessage)
  }, [])
  return data // published data initially; live draft while previewing
}

Same idea everywhere: Astro needs a client:load island (or inline script) since preview data is inherently client-side; in Nuxt/Vue put the listener in onMounted and write into a ref. For Next.js App Router, the hook must live in a 'use client' component that the server page passes data into — see the Next.js guide.

Security notes

  • Always check event.origin against your gluso origin before trusting a message — anyone can iframe your preview URL.
  • The editor does the same in reverse: it only posts to and accepts messages from the configured baseUrl origin.
  • Preview data is draft content. If drafts are sensitive, gate the preview route (e.g. behind a token or auth) — the bridge works the same either way.
  • Your site must be allowed to render inside an iframe from the gluso origin — if you set X-Frame-Options or a frame-ancestors CSP, add your gluso app's origin (scoped to the preview route is fine).

Component & section pages

Pages built from component fields — a hero, an array of stacked sections, a sidebar panel — preview live with no extra work. The whole page's draft data is what streams over the bridge, so data.heroSection, data.sections[] and the rest arrive on every edit. Render each block by its componentTypeId and editing any nested field re-renders just that section. See the section-registry pattern in the Next.js guide.

What updates when

  • Typing in any field — including a nested component/section field → a fresh gluso:preview after the ~250 ms debounce.
  • Switching the previewed variant combination → resolved data for that combo.
  • Saving the draft → the editor reloads the iframe so server-rendered parts refresh too.
  • Publishing → the live page (no preview=1) picks it up through your normal data fetching.