Docs

Einblick SDK In-Page Editing

Register external sites, understand framework support, and integrate Einblick in-page editing without breaking SSR or host-site caching.

sdkapicmseditingexternal-sites

Einblick In-Page Editing

Open System Settings → API to register external sites, manage allowed origins, and copy the publishable site key used by the in-page editing runtime.

Framework support

  • @einblick/sdk is framework-agnostic. Use it for typed reads, schema generation, public command calls, and server-side CMS fetching in any JavaScript or TypeScript stack.
  • @einblick/editor contains framework-neutral bindings, inert DOM attributes, and the lazy browser runtime. It does not require the data SDK.
  • @einblick/editor/react provides the editor provider and editable markers for Next.js, Remix, plain React, and Astro React islands. @einblick/editor/next provides the App Router boot.
  • @einblick/sdk/react remains the home of the data-rendering EinblickImage component. SDK 0.x keeps its previous editor symbols only as deprecated compatibility copies.
  • The hosted editing routes use protocol 2 and retain protocol 1 for one rolling-deployment compatibility window. New integrations always use the proof-bound protocol 2 flow; versions outside the current/previous window are rejected.

Typed reads and response shapes

  • Collection reads support limit, cursor, sort, include, fields, and locale. Use comma-separated field keys for sort and prefix a field with - for descending order. Without an explicit sort, CMS collections follow Einblick's manual record order, and each record exposes that order as sort in record meta.
  • Use fields: ['title', 'thumbnail'] on collection, singleton, or record reads to fetch only the readable attributes that a page needs. Generated clients type the array to the resource's readable field keys; comma-separated strings also work. Unknown or unreadable fields return 400.
  • For localized native resources, locale accepts one language tag or an ordered, Accept-Language-style string. Localized built-in and custom text fields fall back through Workspace and source languages; omitting locale preserves the stored source values.
  • Relation includes only work for readable relation fields and when the same API key also grants the related collection. Included records are returned in included.
  • CMS richtext values are returned as structured document JSON, not flattened HTML.
  • Asset payloads expose stable URLs plus optional width, height, title, copyright, description, tags, and sort. When a CMS video has a generated web playback variant, the public asset URL resolves to that browser-compatible MP4 or WebM file. Browser-origin requests for non-transformed files are proxied with CORS headers and range support; server-side requests without an Origin header can follow short-lived storage redirects.
  • Put shared fetch settings such as cache and next.revalidate in the client's requestInit. Per-request fetch.next settings merge with those defaults, so adding resource-specific next.tags does not discard the shared revalidation interval.

Image asset transformations

Use getEinblickAssetUrl(asset, transform) to derive an image variant from the stable asset URL. You can also pass the name of a transform preset included in the asset payload.

import { getEinblickAssetUrl } from '@einblick/sdk'
 
const socialImage = getEinblickAssetUrl(asset, {
  width: 1200,
  height: 630,
  fit: 'cover',
  quality: 82,
  format: 'webp',
})
  • A transform uses width, height, fit, quality, and format, which become the w, h, fit, q, and fmt parameters on the asset URL. Explicit dimensions and quality are not snapped to a fixed ladder. Delivery clamps each edge to 12,000 pixels, quality to 1–100, and proportionally scales a width-height pair that would exceed 100 megapixels.
  • Supported fit modes are scale-down, contain, cover, crop, pad, squeeze, and scale-up. The SDK also accepts fill as squeeze, and inside or outside as contain.
  • Supported formats are auto, webp, avif, jpeg, baseline-jpeg, png, and png-force; the SDK accepts jpg as jpeg. png is a preference and can preserve a JPEG source as JPEG, while png-force guarantees PNG output.
  • Every distinct source-and-parameter combination can create another delivered variant. When dimensions come from arbitrary layout measurements, call snapEinblickAssetTransform(...) explicitly to keep the variant set bounded. With next/image, a short deviceSizes list usually provides the same control.

Resource writes

  • Use client.createResource(...), client.updateResource(...), and client.deleteResource(...) for the standard resource API. CMS resources use /api/v1/cms/{slug}; native resources use /api/v1/{slug}.
  • Resource writes require a matching resource write grant on the API key plus writable-field grants for create and update payloads. CMS resources support create, update, and delete. Native resource writes currently support buildings, events, and festivals; native deletes archive the record.
  • Pass an idempotencyKey for retryable resource writes. Reusing the same key for the same route and payload returns the stored result; reusing it with a different payload returns a conflict.

Public commands

  • Public commands are separate from resource reads and require explicit write-action grants on the API key.
  • Use client.getCommandSchema() to inspect the commands available to the current token.
  • Use client.command(name, input) or typed helpers such as client.jobs.submitApplication(...) for granted actions. Pass an idempotencyKey for retryable submit-style calls.
  • Newsletter signup is available through client.newsletter.signup(...) when the key grants newsletters.signup. It creates or updates a contact, can apply published contact-group slugs, and returns either subscribed or pending_confirmation depending on Workspace newsletter settings.

What the runtime does

  • Anonymous visitors keep the normal SSR, caching, and performance profile of the external site.
  • Signed-in editors get hover indicators, inline editing for simple fields, and a hosted drawer for more complex fields.
  • The publishable site key only identifies the registered site and origin. After sign-in, navigator results and editor saves use the editor's normal Einblick Workspace role permissions; browser editing never uses a secret API key or API-key resource grants.
  • By default, authenticated edit mode renders a fixed bottom Einblick control bar with the connected site, one general Collections button, and a More menu for opening Einblick or logging out. Use chrome="badge" for the previous launcher style or chrome="none" when the host site provides its own entry point.
  • Use accentColor on EinblickEditProvider when the default Einblick orange does not have enough contrast on a host site. The color replaces SDK-owned orange in hover outlines, quick edit buttons, badge chrome, and the bottom control bar. EinblickNextEditBoot forwards the same prop and can read NEXT_PUBLIC_EINBLICK_ACCENT_COLOR.
  • The collections panel lets editors search all available resources for the external site session. CMS collections are grouped under an expandable CMS item, while supported native resources appear on the same level as that group. Editors can expand or collapse nested CMS collections, open the matching resource in Einblick, edit or delete existing records in the hosted drawer, and create records from the host site. Native drawer editing currently uses the normal Einblick forms for buildings, events, and festivals. Search expands matching branches so matching children stay reachable. Record labels use the configured display field when available, then fall back to title, name, headline, another displayable field, the slug, or a generic record label. Singleton resources only show the create action until their first record exists.
  • Editors can reorder CMS records in the collections panel with the drag handle unless sorting is disabled for that collection.
  • Wrap rendered list surfaces with EditableCollection and a createEditableCollectionBinding({ resourceSlug }) binding when a surface renders multiple records. Set showCreateButton on EditableCollection when that surface should render an edit-mode Add record button at the end of the list that opens the hosted drawer in create mode without leaving the host site. Keep item-level EditableRegion bindings inside that wrapper so each record still has its own hover edit state.
  • In the hosted drawer, editors can reorder multi-value files and tags fields. Saved files arrays keep that order in SDK and public API responses.
  • Relation and multi-relation fields in the hosted drawer load selectable records through the current edit session, scoped to the field's configured target collection. The picker uses the target collection's display field for labels, currently returns up to 100 records from the same Workspace, and lets editors create or edit related records without opening the CMS separately. When a related record is created and the target collection has a relation field back to the current collection, Einblick pre-fills that back-reference.
  • Field-level saves patch currently visible text-like bindings immediately.
  • Single-value image and file bindings can upload and replace media directly from the external editor. Multi-value images and files fields open the hosted drawer instead, so editors can see the full list, add or remove files, and reorder where supported. CMS image fields accept images; CMS file and files fields can restrict accepted MIME types and max size. Generic CMS file fields default to 500 MB and cannot exceed the normal 5 GB per-file limit for signed-in uploads.
  • The host site's onSave refresh/refetch path is still needed when the rendered page depends on resolved asset URLs, SSR output, or cached data.

Login and edit sessions

  • EinblickLoginButton and the provider login() action open sign-in from the user's click. The ?einblick-login and ?login URL triggers first show a prompt on the host page, and only its Sign in button opens the popup. This keeps popup creation tied to an explicit browser gesture.
  • If the popup is blocked, closed, or the secure handshake does not finish within two minutes, edit mode stays off and the host receives a sign-in error that can be retried. A blocked popup no longer triggers an automatic full-page redirect.
  • Protocol 2 uses a short-lived, proof-bound broker between the host, Einblick popup, and embedded bridge. The edit-session bearer is claimed inside the bridge and is never returned through the host site's URL or hash.
  • After successful sign-in, the popup closes and the editor activates on the current page.
  • The provider stores the edit session per site key on the current device, so editors can keep working on the same registered origin until the session expires or they leave edit mode.
  • Edit sessions are origin-bound. If you use multiple allowed origins for one site, editors sign in separately on each origin.

What the host site still owns

  • The host site still owns server-side reads, page rendering, and any long-lived cache layers. Keep secret API keys server-side for reads and code generation; the browser edit runtime uses only the publishable site key and edit session.
  • The SDK can patch some currently visible DOM immediately, but it cannot automatically purge framework-specific SSR caches, CDN caches, or static generation outputs on every host.
  • For the best experience, wire the provider onSave callback to the host site's cache invalidation plus the smallest refresh/refetch mechanism that re-renders fresh data. The callback also fires after hosted-drawer creates and deletes, with deleted records reported as value: null.
  • The default control bar reserves bottom body space and sets --einblick-editor-bottom-offset plus data-einblick-editor-bar-active on <html>. Move fixed bottom host UI with that variable, or set reserveBottomSpace={false} if the host site manages spacing itself.

Cache invalidation best practices

  • If your site has no persistent SSR or CDN caching, text-like field edits and inline media replacements may already look correct immediately. Still refresh or refetch after save when the page renders resolved asset URLs or cached data.
  • If your site uses tagged data caches, revalidate the smallest tag scope that covers the edited resource.
  • If your site statically renders routes, also revalidate the affected route output. Tags refresh the data cache; paths refresh the prerendered page or layout cache.
  • If your site uses a CDN or reverse proxy cache, purge the relevant cache key or path after a successful save.

Next.js

  • Keep CMS fetching on the server.
  • Tag resource fetches with next.tags, preferably through createEinblickCmsTags() from @einblick/sdk/next/cache.
  • Expose POST /api/einblick/revalidate with createEinblickRevalidateHandler({ tags, secret }). The secret is required, must stay server-only, and must match the token configured for the external site in Einblick. For route handlers and Einblick webhooks, use immediate expiration (revalidateTag(tag, { expire: 0 })); profile: "max" can serve stale content once before refreshing.
  • For statically rendered App Router pages, make sure the same route also invalidates affected paths. The SDK helper defaults to revalidatePath('/', 'layout'), which is a solid small-site default. Larger apps can pass a narrower paths map.
  • Mount EinblickNextEditBoot in the root layout. It calls router.refresh() after saves; the authenticated Einblick webhook performs server-side cache invalidation.
  • In the external site settings, keep the revalidation endpoint set to /api/einblick/revalidate so direct edits made inside Einblick invalidate the same tags. Einblick posts { resourceSlug } with the configured secret after CMS creates, updates, deletes, reorders, and external-editor file changes.
// lib/einblick-cache.ts
import { createEinblickCmsTags } from '@einblick/sdk/next/cache'
 
export const einblickTags = createEinblickCmsTags({
  fanOut: { 'gedanken-sections': ['gedanken'] },
})
// app/api/einblick/revalidate/route.ts
import { createEinblickRevalidateHandler } from '@einblick/sdk/next/cache'
import { einblickTags } from '@/lib/einblick-cache'
 
export const POST = createEinblickRevalidateHandler({
  tags: einblickTags,
  secret: process.env.EINBLICK_REVALIDATE_SECRET,
  // Default: paths: [{ path: '/', type: 'layout' }]
})
// app/layout.tsx
import { EinblickNextEditBoot } from '@einblick/editor/next'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <EinblickNextEditBoot>{children}</EinblickNextEditBoot>
      </body>
    </html>
  )
}

Remix and custom SSR

  • Keep CMS fetching on the server or in loaders.
  • After save, invalidate your loader data, route cache, or CDN cache in the smallest possible scope, then trigger the smallest refresh/refetch path that re-renders fresh server data.
  • If you use client-side query caches, trigger a refetch after save when needed.
  1. Fetch CMS or native resource data on the server with @einblick/sdk.
  2. Map raw records into view models and keep bindings from @einblick/editor alongside them.
  3. Wrap CMS-backed output with EditableText, EditableImage, or EditableRegion. For repeated lists, wrap the full list in EditableCollection and keep item-level regions inside it. For native records, set sourceType: 'native' on the binding and pass the native resource slug plus record ID.
  4. Mount EinblickEditProvider with the publishable site key and choose the default control bar, chrome="badge", or custom host chrome. Set accentColor only when the default orange is hard to see against the host site.
  5. Use EinblickLoginButton or ?einblick-login / ?login as the editor entry point.
  6. Use onSave to revalidate caches and trigger the host site's refresh/refetch path when the page reads resource data through SSR or other caches.

Content modeling tips

  • Prefer first-class fields for editor-facing content: string for labels, text or markdown for prose, media fields for images/files, and related collections for repeatable items. Avoid storing copy, CTA labels, or prose segments in json; it is difficult for non-technical editors and inline editing cannot target individual JSON properties.
  • Keep one complete source field in one field marker. Do not attach the same binding to values produced by split, map, slice, [0], excerpts, or fallbacks. Render raw multiline text once with preserved whitespace; render complete Markdown, rich text, semantic paragraphs, lists, and links under one EditableRegion.
  • If a host site renders only a subset of Markdown, keep that rule in the host mapper. For example, a site may treat **bold** as highlighted text and flatten all other Markdown syntax to plain text.
  • When seeding or importing CMS media, put each file in the folder for the CMS record that owns the image/file field, or in a collection folder allowed by that field's existing-file-selection policy. Public API asset resolution is scoped to the resource, field, and allowed folder roots; reusing a file ID from a different record can make that media field resolve to null.