Step 4 of 10 (40% complete)

How the SDK Works

Step Code

The code for this specific step can be found on the following branch:

Click on a link to view the code for this step on GitHub.

The @optimizely/cms-sdk is built around three core ideas: defining content types in code, registering React components against those types, and letting the SDK resolve the correct component at render time. Let's look at each.

Defining a Content Type

Every block, page, experience, and section starts with contentType(). This function does two things simultaneously:

  1. It generates a TypeScript type you can use in your component props
  2. It defines the CMS schema that gets pushed to Optimizely via the CLI
import { contentType, ContentProps } from '@optimizely/cms-sdk'

export const HeroBlockContentType = contentType({
  key: 'HeroBlock',
  displayName: 'Hero Block',
  baseType: '_component',
  properties: {
    title: { type: 'string', displayName: 'Title', localized: true },
    subtitle: { type: 'string', displayName: 'Subtitle', localized: true },
    showDecoration: { type: 'boolean', defaultValue: true },
  },
})

type Props = { content: ContentProps<typeof HeroBlockContentType> }

export default function HeroBlock({ content: { title, subtitle } }: Props) {
  return (
    <section>
      <h1 data-epi-edit="title">{title}</h1>
      <p data-epi-edit="subtitle">{subtitle}</p>
    </section>
  )
}

Notice the data-epi-edit attributes on the JSX elements. These are what enable in-context editing in the Optimizely CMS preview — when an editor is viewing the page in preview mode, they can click directly on these elements to edit the content.

The baseType Values

Every content type must declare what kind of content it is:

baseTypeWhen to use
_componentA reusable block placed inside pages
_pageA full page type (e.g. CMSPage, StartPage)
_experienceA Visual Builder experience
_sectionA Visual Builder section (rows and columns)

Rendering Content Dynamically

Once components are registered (we will look at registration in the next lesson), the SDK can automatically resolve the correct component for any piece of content. You do this with OptimizelyComponent:

import { OptimizelyComponent } from '@optimizely/cms-sdk/react/server'

// This renders any content type — the SDK figures out which component to use
<OptimizelyComponent content={block} />

You never need to write a switch statement or a if (type === 'HeroBlock') check. The SDK reads the __typename or _metadata.types field from the content object and calls the right component automatically.

This becomes especially powerful when rendering lists of blocks:

export default function CMSPage({ content }: Props) {
  return (
    <main>
      {content.blocks?.map((block, i) => (
        <OptimizelyComponent key={i} content={block} />
      ))}
    </main>
  )
}

A page can have any combination of blocks in any order, and this single line handles all of them.

Adding a New Block: The Full Flow

Adding a new block to the project follows a consistent five-step process:

  1. Create the component in components/optimizely/block/my-block.tsx with contentType() and a React component
  2. Register it in lib/optimizely/init.ts in both initContentTypeRegistry and initReactComponentRegistry
  3. Allow it in pages by adding it to AllBlocksContentTypes in lib/optimizely/content-types.ts
  4. Push to CMS with npm run opti-push
  5. Use it — the block is now available in the Optimizely editor and renders automatically

That is the entire workflow. No GraphQL fragment updates, no manual schema changes in the CMS UI, no component mapper updates. Once you register it, the SDK handles the rest.

Official documentation: This lesson covers the most common patterns used in this starter. For more advanced cases — custom display templates, property types, Visual Builder configuration, and the full API reference — see the official Optimizely documentation: https://docs.developers.optimizely.com/content-management-system/v1.0.0-CMS-SaaS/docs/model-content-types

Have questions? I'm here to help!

Contact Me