Search documentation

Search documentation

Visual Builder

Setup

Connect Contoprix block codes to your React component registry.

What you need#

Visual Builder setup has three matching pieces:

  1. A CMS Component Type and block code, such as hero_banner.
  2. A React component that can render that block's data.
  3. A registry entry with the exact same code.

The CMS stores data and page placement. Your frontend owns the visual design.

1. Create a component registry#

Keep block mappings in one module.

src/contoprix/components.ts
import type { ComponentRegistry } from "@contoprix/react";

import HeroBanner from "@/components/contoprix/HeroBanner";
import CallToAction from "@/components/contoprix/CallToAction";

const components: ComponentRegistry = {
  hero_banner: HeroBanner,
  call_to_action: CallToAction,
};

export default components;

The key is the Component Type code from Contoprix. It is case-sensitive from the registry's point of view, so copy the code exactly instead of using the display name.

2. Add one component#

A Component Type determines the fields editors can fill in. For example, a hero_banner type might contain eyebrow, heading, description, and button_url.

src/components/contoprix/HeroBanner.tsx
import type { ContoprixComponentProps } from "@contoprix/react";

type HeroSettings = {
  eyebrow?: string;
  heading?: string;
  description?: string;
  button_label?: string;
  button_url?: string;
};

export default function HeroBanner({
  settings,
  previewAttributes,
}: ContoprixComponentProps) {
  const hero = settings as HeroSettings | undefined;

  if (!hero?.heading) return null;

  return (
    <section {...previewAttributes}>
      {hero.eyebrow ? <p>{hero.eyebrow}</p> : null}
      <h1>{hero.heading}</h1>
      {hero.description ? <p>{hero.description}</p> : null}
      {hero.button_label && hero.button_url ? (
        <a href={hero.button_url}>{hero.button_label}</a>
      ) : null}
    </section>
  );
}

Spread previewAttributes on the root element. They are harmless on public delivery and let the Visual Builder select this block in preview.

3. Add schema fallback#

A pulled Contoprix schema lets PageRenderer render an unregistered component block with the generic renderer while a developer prepares custom UI.

src/contoprix/schema.ts
import { buildSchemaRegistry } from "@contoprix/react";
import type { SdkSchema } from "@contoprix/client";

import pulledSchema from "../../.contoprix/schema/schema.json";

export const schemas = buildSchemaRegistry(pulledSchema as SdkSchema);

Then pass the schema registry to the renderer:

src/contoprix/ContoprixRenderer.tsx
"use client";

import { PageRenderer } from "@contoprix/react/client";
import type { ContoprixPage } from "@contoprix/types";

import components from "./components";
import { schemas } from "./schema";

export function ContoprixRenderer({ page }: { page: ContoprixPage }) {
  return <PageRenderer page={page} components={components} schemas={schemas} />;
}

The generic fallback does not replace custom components for forms or content-entry blocks. Register a custom renderer for those shapes.

4. Fetch and render a published page#

Use the server helper to fetch by public route path, then pass the complete page to your renderer.

app/[...slug]/page.tsx
import { getContoprixPage } from "@contoprix/next/server";

import { ContoprixRenderer } from "@/contoprix/ContoprixRenderer";

export default async function CmsPage({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;
  const page = await getContoprixPage({
    slug: "/" + slug.join("/"),
    languageCode: "en",
  });

  return <ContoprixRenderer page={page} />;
}

The root page can use client.pages.get(). See the Pages Routing guide for 404 handling and root-route setup.

5. Pull and validate the schema#

After a content model changes, refresh the local schema and generated types.

Terminal
npx contoprix pull
npx contoprix generate
npx contoprix components
npx contoprix validate
  • pull downloads the current schema.
  • generate creates TypeScript helpers for the schema.
  • components scaffolds custom React overrides and a registry.
  • validate reports custom overrides versus generic fallback coverage.

Generated files are a starting point. Review component field types, use your design system, and add error/empty states before treating a scaffold as production UI.

Setup checklist#

  • CMS Component Type code and registry key match exactly.
  • Every important component block has a custom renderer or a known generic schema.
  • Forms and content-entry blocks have an intentional custom renderer.
  • PageRenderer is imported from @contoprix/react/client.
  • The page is fetched on the server with delivery credentials.
  • Schema files are refreshed after model changes.
  • The page has been tested in preview and published delivery.

Common setup issues#

ProblemCauseFix
A block is a missing-component placeholderThe code is absent or mismatched in the registry.Match the exact CMS code.
A new component looks genericThe schema is available but there is no custom override yet.Add a custom component when design is ready.
A new component is missing entirelyThe local schema was not refreshed.Run npx contoprix pull and check the type/code.
Visual editing cannot select a componentThe root element omits previewAttributes.Spread it onto the component's outer element.
Preview credentials appear in browser codeServer/client boundaries are mixed.Fetch preview data only in server code or a protected server route.