Watch Webinar: Figma - Design to Code in 80% Less Time

Announcing Visual Copilot - Figma to production in half the time logo
Talk to Us
Talk to Us










Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required



Get StartedLogin

‹ Back to blog

Custom Components

Drag and drop to build pages visually with your React components

September 23, 2021

Written By Kapunahele Wong

Last updated July 6, 2023.

If you've ever had to stop coding for minor UI component edits, you know it'd be so much more efficient if non-dev teammates could do it themselves.

Just as developers reuse components, non-dev teammates can too — in an intuitive, drag-and-drop UI.

With your custom components in Builder, you can:

  • Create any component your team needs
  • Configure options for variability

Your non-dev teammates can:

  • Use and reuse your components with drag-and-drop
  • Iterate endlessly
  • Publish when they're ready


This tutorial shows you how to integrate your custom React components in the following sections:

  1. Integrating with Builder
  2. Creating a custom React component
  3. Registering your component with your Builder app
  4. Using your new component in Builder's drag-and-drop editor

This tutorial uses Next.js, but you can integrate with any framework, including React-based frameworks such as Remix, Hydrogen, Gatsby, App Router, as well as other non-React-based frameworks such as Qwik, Vue, Nuxt, Svelte, and Angular.

For instructions on integrating with other frameworks, read Integrating Pages and Registering Custom Components.

Before following these steps to generate a Next.js app, make sure you have Node.js 12.22.0 or later on your computer.

Tip: This section is optional. If you already have a Next.js app, you can skip to the next section, Adding Builder as a Dependency.

At the command line, run the following command to generate a Next.js app:

npx create-next-app@latest

The following is the configuration that this tutorial uses. Note that this example is using all of the defaults, except for no for the App Router. If you prefer to use the App Router instead, read the guide for integrating the Next.js App Router.

* ✔ What is your project named? **my-app**
* ✔ Would you like to use TypeScript with this project? **Yes**
* ✔ Would you like to use ESLint with this project? **Yes**
* ✔ Would you like to use `src/` directory with this project? **No**
* ✔ Use App Router (recommended)? **No**
* ✔ Would you like to customize the default import alias? **No**

Change directory with the cd command:

cd my-app

Open this directory in your favorite code editor.

At the command line, use npm to install Builder:

npm install ""

Start the development server with npm:

npm run dev

Keep your editor open because we'll come back after some set up in Builder.

For more detailed information on Next.js, check out the official Next.js Getting Started.

Create a page in the pages directory called [].tsx (make sure to include the brackets and ellipses).

Tip: If you don't have a pages directory, be sure to create one. If you prefer to use the App Router instead, read the guide for integrate the Next.js App Router.

Paste the following code:

// pages/[].tsx
import React from "react";
import { useRouter } from "next/router";
import { BuilderComponent, builder, useIsPreviewing } from "";
import { BuilderContent } from "";
import DefaultErrorPage from "next/error";
import Head from "next/head";
import { GetStaticProps } from "next";

// Replace with your Public API Key

// Define a function that fetches the Builder
// content for a given page
export const getStaticProps: GetStaticProps = async ({ params }) => {
  // Fetch the builder content for the given page
  const page = await builder
    .get("page", {
      userAttributes: {
        urlPath: "/" + ((params?.page as string[])?.join("/") || ""),

  // Return the page content as props
  return {
    props: {
      page: page || null,
    // Revalidate the content every 5 seconds
    revalidate: 5,

// Define a function that generates the
// static paths for all pages in Builder
export async function getStaticPaths() {
  // Get a list of all pages in Builder
  const pages = await builder.getAll("page", {
    // We only need the URL field
    fields: "data.url",
    options: { noTargeting: true },

  // Generate the static paths for all pages in Builder
  return {
    paths: => `${}`).filter(url => url !== '/'),
    fallback: 'blocking',

// Define the Page component
export default function Page({ page }: { page: BuilderContent | null }) {
  const router = useRouter();
  const isPreviewing = useIsPreviewing();

  // If the page content is not available
  // and not in preview mode, show a 404 error page
  if (!page && !isPreviewing) {
    return <DefaultErrorPage statusCode={404} />;

  // If the page content is available, render
  // the BuilderComponent with the page content
  return (
      {/* Render the Builder page */}
      <BuilderComponent model="page" content={page || undefined} />

This section explains what the code in is doing at a high level. For more detailed information, see the Next.js documentation.

After the imports, you specify the Builder Public API Key and, with builder.init(), initialize connect your app to Builder.

The getStaticProps() function tells you what paths the app is building. Here, Builder gets the page and creates the URL, otherwise, if there's no page, you'll get null and a 404.

export async function getStaticProps({
}: GetStaticPropsContext<{ page: string[] }>) {
  const page = await builder.get('page', {
    userAttributes: {
      urlPath: '/' + (params?.page?.join('/') || ''),
  .toPromise() || null

  return {
    props: {
    revalidate: 5,

revalidate: 5 means that Next.js attempts to re-generate the page under these conditions:

  • When a request comes in
  • At most once every 5 seconds
  • To check if Builder has updates to the page

The getStaticPaths() function returns a list of page URLs, omits unnecessary data for creating the list.

export async function getStaticPaths() {
  const pages = await builder.getAll('page', {
    options: { noTargeting: true },
    omit: "data.blocks"

  return {
    paths: => `${}`),
     fallback: true,

The last section is a regular React component called Page(). It gets the page data and checks that Builder is present. If there's no page and no Builder, Next.js returns a 404. Otherwise, you get your Builder page.

// React Component
export default function Page({
}: InferGetStaticPropsType<typeof getStaticProps>) {
  const router = useRouter()
  if (router.isFallback) {
    return <h1>Loading...</h1>
  const isPreviewing = useIsPreviewing();

  if (!page && !isPreviewing) {
    return (
          <meta name="robots" content="noindex" />
          <meta name="title"></meta>
        <DefaultErrorPage statusCode={404} />

  return (
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      <BuilderComponent model="page" content={page} />

Builder adds the ability for your team members–even those who don't code–to create pages and iterate on ideas with a drag-and-drop interface.

Head over to to sign up for an account if you don't already have one. Come back when you're logged in.

To enable Builder to open your site in the visual editor, you will need to provide a URL that we can open which has the Builder rendering component in it.

Go to the /models page in Builder and choose your page model.

Then, set the Preview URL to http://localhost:3000. Be sure to include the http://.

For more information on preview URLs, read Editing and Previewing Your Site.

You can find and copy your Public API Key with the following steps:

  1. Within your Builder Space, press Cmd/Ctrl + k to open the Command Palette.
  2. Start to type the letters API into the search field to filter results.
  3. Click your API key to copy to your clipboard.

Alternatively, you can also find your Public API Key in Account Settings for the Space:

  1. Within your Builder Space, go to the Account Settings section.
  2. Click the copy icon to the right of the Public API Key field.

The video below shows both ways to find the Public API Key.

Now, head back to pages/[].tsx and paste this key as the value for BUILDER_API_KEY.

For more information, read Using Builder API Keys.

Currently, when you go to http://localhost:3000, you should get a 404 page. This is because your app is not ready for you to provide a page to render.

To give your app something to work with, click on the Content icon in the left sidenav:

  1. Go to the Content section of Builder.
  2. Click + New Entry and select Page.
  3. Name the page; for example, home. Builder auto-generates a URL based on the name you provide, but you can customize the URL if you like. This example uses the URL of / to specify localhost:3000.
  4. Choose the blank template.
  5. Drag in a Text block and something like, "I did it!".
  6. When your page is ready, click the Publish button.

The following video demonstrates creating and publishing a Page:

Go to http://localhost:3000 and check out your work. Well done!

If you're getting a 404 but aren't sure why, check these things:

  • Make sure you've published your page in Builder by clicking the Publish Draft button on the upper right corner.
  • Check the URL. If you name the page test2 for example, Builder adds a hyphen, so that the URL segment is test-2.
  • Check that your browser allows insecure content.
  • Make sure your dev server is running (sometimes restarting helps).

For more information on using Next.js or other frameworks with Builder, read the Integrating Pages official documentation.

In the root of your project, create a folder called components.

Inside the components directory, create a file called heading.tsx.

Paste the following code into heading.tsx and save.

import { Builder } from "";
import dynamic from "next/dynamic";

export const Heading = (props) => (
  <h1 style={{ color: props.color }}>{props.title}</h1>
// Register heading component with
  // dynamic() is a Next.js feature that loads components asynchronously 
  dynamic(() => import("./heading").then((module) => module.Heading), {
    // Render component server-side.
    ssr: true
    name: "Heading",
    // Define component inputs
    inputs: [
        name: "title",
        type: "text",
        defaultValue: "I am a heading!",
        name: "color",
        type: "color",
        defaultValue: "black",

Here, we register a Heading component with using the Builder.registerComponent() method. Our component returns an <h1> with color and title props.

We load the component asynchronously using the Next.js' dynamic() function, which imports the ./heading file and extracts the Heading component. Next, we define the title and color inputs with default values. The ssr option set to true specifies that the component should be rendered server-side (SSR).

In [].tsx, import your new custom component.

// Add your heading import after all your other imports
import "../../components/heading";

Log into Builder and open the space that you have connected to your app. If you need some guidance on how to configure your app and Builder together, read Setting up visual drag-and-drop page building with Next.js.

  1. Go to Content and open a page.
  2. In the Insert tab, open the Custom Components section.
  3. Drag in the custom component, here called Heading. The heading displays on the page.
  4. With the heading selected, open the Options tab to display the block options where you can edit the title and color.

On the right of the screen, the Options panel contains the inputs or props we told Builder about in builder.registerComponent(). The panel includes the title and color along with their default values.

The following video shows this process:

Tip: To develop your Builder work on localhost, you have to configure Builder by pointing to a page that has a <BuilderComponent /> on it and your components registered.

For step-by-step instructions on configuring Builder to use localhost, check out the Getting your API Key and setting your URL section of that article.

The following image shows how the code corresponds to what's in Builder's UI. Everything we declared in builder.registerComponent() is present in the Insert and Options panels. The name Heading along with our inputs, and even our default values populate the Builder interface.

Screenshot of Builder Visual Editor with custom component code snippet with arrows from code to their corresponding elements in the UI.

To display your component in the browser, click the green Publish button in the upper right of the browser and being sure you're serving your app locally, go to localhost/your page . For example, if you used the about page, go to http://localhost:3000/about.

Tip: For the most up-to-date content on using custom components in Builder and instructions for other frameworks, check out the official documentation on Integrating Custom Components.

Builder has a lot of other inputs you can configure in your custom component. Check out Input Types in Builder for a comprehensive list.


Hand written text that says "A drag and drop headless CMS?"

Introducing Visual Copilot:

A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

Like our content?

Join Our Newsletter

Continue Reading
Web Development12 MIN
Figma to Angular: Convert Designs to Clean Angular Code in a Click
WRITTEN BYVishwas Gopinath
May 16, 2024
Web Development18 MIN
Getting Started with Mitosis: Creating a Cross-Framework Design System
WRITTEN BYVishwas Gopinath
May 7, 2024
Company News3 MIN closes $20 million in funding led by M12, Microsoft’s Venture Fund
WRITTEN BYSteve Sewell
April 24, 2024