Made in

Announcing Visual Copilot - Figma to live 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

You can extend and customize Builder by making your own plugin. With custom plugins you can register custom field types for your Builder model custom fields and inputs for your custom components, custom targeting or symbols.

This document covers making a minimal plugin to add a custom type for use in a Data model.

Tip: All plans can create and submit Builder Plugins. When you're ready to contribute your plugin, head over to GitHub and submit a PR. Make sure to add your plugin to the plugins directory.

To get the most out of this document, make sure you are familiar with the following:

This tutorial guides you through creating a plugin with a custom type and custom type editor. By creating custom types in a plugin, you provide rich data types that you can use across the Visual Editor. For example, you can:

  • Create custom component inputs that accept structured data fields
  • Target content based on products in a customer's cart
  • Model fields that store multimedia content
  • Enable Symbol inputs to accept externally hosted documents

Each custom type has a custom type editor, which provides a user interface for selecting values for your custom type fields. For example, editors can fetch external resources and present the user with a browsable list of items or provide menus and fields for data entry.

With custom types and custom type editors, your users can create and update nearly any kind of data structure beyond the basic types provided by Builder, all from within the Visual Editor.

After installing the plugin that registers your custom type editor, the corresponding custom type is available across the Visual Editor, specifically when using the following features:

  • Custom component inputs
  • Targeting content
  • Model fields
  • Symbol inputs

Create a directory for your plugin and open the new director with the following command:

mkdir text-plugin && cd text-plugin

Initialize the project to use npm:

npm init

Press Enter through the prompts and respond yes to the proposed package.json.

Use npm install to install the dependencies for this project. You can use --save-dev because Builder provides these dependencies for you later. The dependencies are:

  • exposes certain APIs to interact with APIs to interact with Builder
  • enables you to use React to create your plugin
  • webpack: module bundler for JavaScript
  • webpack-dev-server: development server with live reloading
  • @babel/preset-react: supports JSX
  • babel-loader: transpiles modern and superset JavaScript, such as JSX using Babel with webpack

Paste the following command, which includes all these dependencies, at the command line:

npm install --save-dev webpack webpack-dev-server webpack-cli @babel/preset-react babel-loader

Install react-quill for the Rich Text editor that this plugin uses:

npm install react-quill

This section guides you through creating the project infrastructure by creating the key files and pasting in the contents for each.

Replace the contents of package.json with the following:

  "name": "rich-text-plugin",
  "version": "1.0.0",
  "description": "",
  "entry": "plugin",
  "output": "plugin.system.js",
  "main": "dist/plugin.system.js",
  "files": [
  "scripts": {
    "build": "webpack --mode production",
    "start": "webpack-dev-server --mode development"
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/preset-react": "^7.18.6",
    "": "^1.0.0",
    "": "^2.0.13",
    "@emotion/core": "^11.0.0",
    "babel-loader": "^8.2.5",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.10.0"
  "dependencies": {
    "react-quill": "^2.0.0"

At the root of your project, create a file called .babelrc and paste in the following:

// contents of .babelrc
// use preset-react to support JSX 
    "presets": ["@babel/preset-react"],
    "env": {
      "development": {
        "presets": [["@babel/preset-react", { "development": true }]]

Again at the root of your project, create a file called webpack.config.js and paste in the following:

// contents of webpack.config.js
const path = require('path');
const pkg = require('./package.json');

module.exports = {
  entry: `./src/${pkg.entry}.jsx`,
  externals: {
    '': '',
    '': '',
    "@emotion/core": "@emotion/core",
    "react": "react",
    "react-dom": "react-dom"
  output: {
    filename: pkg.output,
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'system',
  resolve: {
    extensions: ['.js', '.jsx'],
  module: {
    rules: [
            test: /\.(jsx)$/,
            exclude: /node_modules/,
            use: [
                loader: 'babel-loader',
  devServer: {
    port: 1268,
    static: {
       directory: path.join(__dirname, './dist'),
    headers: {
      'Access-Control-Allow-Private-Network': 'true',
      'Access-Control-Allow-Origin': '*',

This file establishes these key configurations:

  • The location of the entry point, or where webpack starts when bundling. Here, it's plugin.jsx.
  • The externals are dependencies that you don't need to bundle. You don't need to bundle them because when your plugin is ready, Builder provides these dependencies for you.
  • resolve.extensions specifies that you're using JavaScript and JSX files. If you're using other file extensions, add them here.
  • The module settings specify that you're using JSX files, not bundling node_modules, and using babel-loader to work with JSX.
  • With devServer, configures your plugin to run locally on localhost:1268, serve from the ./dist directory, and provides headers for local development with Builder.

Tip: You can also pass data into your plugin when registering it as an input type. This is useful if you want to control things like API keys or settings in your own codebase, but want to be able to pass into the plugin whenever it is used. To do this, use the options property when registering an input for a component. For more detail, see this example on GitHub from an async dropdown plugin.

Create a folder called src with a file called plugin.jsx and paste in the following:

// contents of plugin.jsx
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Builder } from '';
import ReactQuill from 'react-quill';

function RichTextEditor(props) {
  return (

    name: 'MyText',
    component: RichTextEditor

The key points in the example above are:

  • The editor component wraps ReactQuill, which in turn takes onChange and value as props. The value you set can be any type serializable to JSON, such as string, number, null, array, or object, and be as deeply nested as you need. Refer to the react-quill documentation for more information.
  • props.onChange and props.value are passed to the editor component, which controls the editor's state. value is the current value of the field. onChange is a callback function that accepts one parameter, which is the field's new value after the user changes that value within the editor. For more information, see Controlled Components in the React documentation.
  • When the user updates the text area provided by ReactQuill, ReactQuill calls onChange, which updates the editor's state.
  • Updating the editor's state updates props.value, which is passed down to ReactQuill to update that component's internal state.
  • Registering the editor component with Builder.registerEditor() creates the MyText custom type.

Builder.registerEditor() accepts one parameter: an options object with three properties:

  • name: a string, typically camel-cased, that represents the custom type's name
  • component: the editor component
  • options (optional): an object

Tip: When developing locally, you are mostly likely developing on a non-ssl http:// url within Builder, which is an https:// site. Browsers don't allow https:// sites to make insecure http:// requests unless you explicitly allow it. To allow access to your local http URL in Chrome, click the shield icon on the right side of the address bar, and choose load unsafe scripts. The page will reload and you might have to enter your local URL a second time for Chrome to allow its content to load.

For more information on input types, see Input Types for Custom Components.

  1. Go to Account Settings.
  2. Click the pencil icon for Plugins to add the plugin.
  3. Enter the local address for this example plugin: http://localhost:1268/plugin.system.js.
  4. Click the Save button.

The following video shows these steps:

  1. Go to Models.
  2. Select a model to edit or create a new one.
  3. In the model, click the + New Field button.
  4. To confirm that your plugin is working, click the Type dropdown and scroll down to select the type MyText. Notice that when you select MyText, the Default value input changes to a Rich Text input that includes text formatting options.

MyText comes from the name you provided in plugin.jsx. to Builder.registerEditor(). The following video shows these steps:

This tutorial covered how to create a minimal plugin and use it locally. When you've created your plugin, you can do one of two things:

  • Get your unique plugin creations added to Builder, by check us out on GitHub and submitting a PR with your plugin on the repo.
  • If you're on an Enterprise plan, you can instead host your plugin yourself as a private plugin. For more information, see Creating a Private Plugin.
Was this article helpful?


Visual CMS

Theme Studio for Shopify

Sign up


Featured Integrations





Get In Touch

Chat With Us




© 2020, Inc.


Privacy Policy

Terms of Service


Get the latest from

By submitting, you agree to our Privacy Policy