Quickstart

From an empty project to an editable page in about ten minutes.

Before you start

You need a workspace and its public key. ContentBind is invite only, so the workspace is created for you; the key is in Settings → Environments, one per environment, and it is safe to ship in client code. It grants read access to published content and nothing else.

1. Install

sh
npm install @contentbind/sdk

2. Add your public key

sh
# .env.local
NEXT_PUBLIC_CONTENTBIND_KEY=pk_live_...

Find it in your workspace under Settings, per environment. Env-file syntax: no quotes, no colon, and restart the dev server afterwards - NEXT_PUBLIC_ variables are inlined at build time, so a running server never sees a new one.

3. Render a page

Fetch on the server, render on the client. The fetch is a plain HTTP call with no runtime, so it works in any framework; this example is the Next.js App Router.

tsx
// app/[[...slug]]/page.tsx
import { fetchOneEntry, isContentBindError } from '@contentbind/sdk'
import { Content } from '@contentbind/sdk/react'

const KEY = process.env.NEXT_PUBLIC_CONTENTBIND_KEY!

export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
  const { slug } = await params
  const urlPath = '/' + (slug ?? []).join('/')

  const entry = await fetchOneEntry({ model: 'page', publicKey: KEY, urlPath })
  if (isContentBindError(entry)) {
    console.error(`ContentBind ${entry.kind}: ${entry.message}`)
    return null
  }

  return <Content model="page" content={entry} publicKey={KEY} />
}

That is a working integration. Every page an author creates at a URL your app routes to now renders, and the built-in component library is available to them immediately.

The package has two entry points, split by where the code runs: @contentbind/sdk is server and build-time code with no React in it (fetchOneEntry, fetchEntries, isPreviewing, getPreviewToken, isContentBindError); @contentbind/sdk/react is components and hooks (Content, useIsPreviewing, useTrackExperimentAssignment). The near-twins differ on purpose: isPreviewing(cookieJar) is the server function, useIsPreviewing() is the client hook.

Two things the type system will hold you to:

  • Every fetch can return an error object. fetchOneEntry returns Entry | ContentBindError and fetchEntries returns Array<...> | ContentBindError - never a thrown exception, never null. Always narrow with isContentBindError(result) before touching the data; calling .map or reading .blocks straight off the result will crash the moment a key is wrong or the network hiccups.
  • The URL parameter is `urlPath`. Passing url compiles in loosely-typed code and silently fetches / instead.
tsx
import { fetchEntries, isContentBindError } from '@contentbind/sdk'

const entries = await fetchEntries({ model: 'page', publicKey: KEY })
if (isContentBindError(entries)) {
  console.error(`ContentBind ${entries.kind}: ${entries.message}`)
  return []
}
return entries.map((e) => e.url)

4. Titles and descriptions

Authors set a page title and description in the editor (click the page name in the top bar). They arrive on the entry as entry.meta.title and entry.meta.description - the SDK deliberately leaves the document head to your framework, because search engines need it server-rendered.

App Router:

tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const entry = await fetchOneEntry({ model: 'page', publicKey: KEY, urlPath })
  if (isContentBindError(entry)) return {}
  return { title: entry.meta.title, description: entry.meta.description }
}

Pages Router, in the page component:

tsx
import Head from 'next/head'

<Head>
  {entry.meta.title ? <title>{entry.meta.title}</title> : null}
  {entry.meta.description ? <meta name="description" content={entry.meta.description} /> : null}
</Head>

5. Open the editor

In ContentBind, create a page whose URL matches a route in your app, then open it. The canvas is your site in an iframe, loaded from your own origin, with ?contentbind.editor=1 appended. Nothing is proxied and nothing is re-hosted: what an author sees is your application running your code.

If the canvas stays blank, run the doctor:

sh
npx contentbind doctor

It checks the key, the API, the delivery call and the editor origin, and tells you which of the four is wrong.

6. Register your own components

The built-ins get a page shipped. Your components make it yours.

tsx
import { Content } from '@contentbind/sdk/react'
import { PricingTable } from '@/components/PricingTable'

const COMPONENTS = [
  {
    component: PricingTable,
    name: 'PricingTable',
    friendlyName: 'Pricing table',
    category: 'Marketing',
    inputs: [
      { name: 'heading', type: 'string', defaultValue: 'Plans' },
      { name: 'highlight', type: 'string', options: [
        { label: 'Starter', value: 'starter' },
        { label: 'Growth', value: 'growth' },
      ] },
    ],
  },
]

<Content model="page" content={entry} publicKey={KEY} customComponents={COMPONENTS} />

The component stays in your repository. ContentBind never sees its source, only its name and the inputs you declared: it sends props, your code renders them. See Custom components for the full input vocabulary.

All documentation · llms.txt · llms-full.txt · contentbind.com