AddOn Development

Introduction

Welcome to the Beefree SDK AddOn development documentation!

This document is for anyone that wants to start creating AddOns for Beefree SDK users.

Before you get started, you may want to review these frequently asked questions.

Happy coding!

Creating a development app

First, create a development application so that you are not testing your new AddOn with a production application.

Development applications inherit the plan of the production application to which they are connected. You can only build AddOns when you are on the Superpowers plan or above. If you are not on one of those plans:

  • Create a development application

  • Request an upgrade

Once you have a development application on the Superpowers plan or above, proceed to the next step.

Getting started

The process all starts in the Beefree SDK Console:

  1. Log into the Console at developers.beefree.io

  2. If you have not done so yet, create a development app as indicated above

  3. Click the Details link next to any application on the Superpowers plan or above

  4. Click the AddOns link, under Application configuration

  5. Click the Create a custom AddOn button

When you create a new Custom AddOn, you will be prompted to enter some information through the AddOn setup form. Depending on the method that you choose, the number of fields in the form will change.

  • Name: The name of the AddOn is saved in the dashboard. This is not the name used for the tile in the Beefree editor’s Content tab but rather the internal name visible to other developers. In other words, it will not be shown to end-users of the editor.

  • Enable toggle: A toggle element to enable and disable the AddOn. When toggled OFF, the AddOn is not visible to end-users of the editor in your application (the host application).

  • Type: The type of content that the AddOn will create. Currently, the following content types are available:

    • Image

    • HTML

    • Mixed

    • Row

    • Paragraph

    • Button

    • Title

    • List

    • Menu

    • Icon

  • Handle: A unique identifier for this AddOn. This value will be the future reference between the AddOn and its content dialog. Additionally, it will be used to override settings per user or build a content dialog for the AddOn.

  • Method: This is an important selection and is covered in detail in the section below.

    • External iframe: Choose this option if the AddOn is hosted outside the host application. If this is an AddOn that will be made available to other Beefree SDK applications via the Partner AddOn directory, then it is by definition hosted outside of the host application, and therefore you must select this method.

    • Content Dialog: Choose this option for AddOn that lives inside the host application (e.g., internal AddOn). You cannot choose this method if you plan to make your AddOn available to others by listing it in the Partner AddOn directory.

  • Icon: Upload an image from your local computer that will become the icon associated with the tile shown in the editor’s Content tab. We recommend a 64 x 64 pixel SVG file.

  • Labels:

    • Content Title: This is the name that will be used for the tile in the BEE editor’s Content tab (e.g., “Countdown timer,” “Product Recommendation,” etc.).

    • Content button: The label for the call-to-action button (e.g., “Select a Countdown Timer”), which opens the content dialog or iframe.

    • Placeholder message: This is the message shown in the editor’s stage when the tile is first dropped.

If you are using the iFrame method, some additional fields are shown on the form.

  • Iframe URL – Required The URL that will be loaded inside the Iframe.

  • API Key – Optional The API Key optionally authorizes the application to load the URL provided above. If authorization is not needed, this field can be left empty.

  • Authentication URL – Optional The endpoint that handles the optional authorization request. If authorization is not needed, this field can be left empty.

  • Healthcheck URL – Optional, but required for Partner AddOns This URL will be contacted when the editor is started to verify the status of the AddOn. In the case of AddOn downtime, the editor hides it in the UI. Please note: if you are building a Partner AddOn, this is a required field as it will allow applications that have installed your AddOn to protect the quality of their end-users experience if your service is unavailable.

Once you have entered all the details, click Save, and you should immediately find your AddOn visible within the Beefree SDK platform. However, your AddOn is not completed until the configuration steps described below are done.

Concepts

Content dialog vs. iframe methods

One of the important choices you will make is in regard to how your content creation AddOn loads.

The general rule of thumb is that if your AddOn lives within the host application, you can use a content dialog.

If your AddOn lives on another website outside of the host application or will be listed in the Partner AddOn directory, then you must select the external iframe method.

Overview of iframe method

The AddOn can be built using any technology stack. There are no specific rules about how your AddOn functions internally.

However, you will need to implement the following:

  • JavaScript API

    • Protocol to communicate between iframe and the Beefree application.

  • Server API

    • Conditional health check endpoint

    • Optional authentication endpoint

Don’t worry about the fine details just yet! We’ll revisit each of these methods in more detail in the following sections.

Overview of content dialog method

Superpowers and Enterprise applications have the option to create their own, custom AddOn with content dialogs.

This option is convenient when the AddOn and host applications are hosted under the same domain.

You don’t need to implement a JavaScript API or Server API when using the content dialog.

Content types

An AddOn can only return one type of content. The type you choose will determine which sidebar properties are shown when your AddOn is selected.

Currently, you may choose between the following content types:

  • Image: Will insert an image module on the stage, and show the properties of an image content block in the sidebar. Adjusting the properties allows for customization of the content type.

  • HTML: Will insert an HTML module on the stage, and show the properties of a custom HTML content block in the sidebar, except the HTML text input will be hidden. That’s because the HTML cannot be edited outside of the content dialog or iframe made available by your AddOn.

  • Mixed: Will insert a module on the stage that allows for loading of different content blocks with a single action.

  • Row: Will insert a row module on the stage, and show the row properties in the sidebar. The row is pre-built with a specific use case in mind. For example, a row that serves of the purpose of allowing end users to add products to their builders.

  • Paragraph: Will insert a paragraph module on the stage, and show the properties of a paragraph content block in the sidebar.

  • Button: Will insert a button module on the stage, and show the properties of a button content block in the sidebar. This simplifies offering pre-built buttons to your end users.

  • Title: Will insert a title module on the stage, and show the properties of a title content block in the sidebar.

  • List: Will insert a list module on the stage, and show the properties of a list content block in the sidebar.

  • Menu: Will insert a menu module on the stage, and show the properties of a menu content block in the sidebar. This simplifies offering pre-built menus to your end users.

  • Icon: Will insert an icon module on the stage, and show the properties of an icon content block in the sidebar.

Mixed Content AddOns

Mixed Content AddOns are a new type of content tile that allows the host application to load multiple content modules at once. This grants you greater flexibility with how you want your custom AddOn to interact with your customers and data; for example, you could create a mixed content AddOn to drop a product image in your template, along with a title, description, and more.

To utilize this feature:


var beeConfig = { ... contentDialog: { addOn: { label: 'Custom AddOn Label', handler: (resolve, reject, args) => { ... } } } }
  • The host application should return a valid response as the parameter of the “resolve” callback.

Response Content

A valid response that the hosting application should send to the Beefree app when the contents of a Mixed Content AddOn are selected must have the following structure:


{
  "type": "mixed",
  "value": [
    ... here the list of modules
  ]
}

For example:


{
  "type": "mixed",
  "value": [
    {
      "type": "title",
      "value": {
        "text": "Enjoy $50 off!",
        "title": "h1"
      }
    }, {
      "type": "title",
      "value": {
        "text": "ENTER CODE: 2 YEARS",
        "title": "h2"
      }
    }, {
      "type": "image",
      "value": {
        "alt": "My custom image",
        "href": "http://www.google.com/",
        "src": "http://my-image-url",
      }
    }, {
      "type": "button",
      "value": {
        "label": "SHOP NOW >",
      }
    }
  ]
}

If the response is not valid, an error is raised, and onError is called.

Modules definition

For each module type, here is the list of allowed properties.

Unless otherwise specified, the properties are optional.

Title

The following code calls a function named resolve with an object argument. This object defines a heading element with characteristics such as type, text content, alignment, font size, boldness, text color, and link color. The resolve function handles or returns the constructed heading element.


resolve({
  type: 'heading',
  value: {
    title: 'h3',
    text: 'Title',
    align: 'right',
    size: '48',
    bold: true,
    color: 'pink',
    linkColor: 'green',
  }
})

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

title

String

No

text

String

Yes

align

String

No

size

String

No

bold

Boolean

No

color

String

No

linkColor

String

No

Title Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-title-single',
  type: 'object',
  required: ['text'],
  properties: {
    type: {
      enum: ['title', 'heading'],
    },
    underline: {
      type: 'boolean',
    },
    italic: {
      type: 'boolean',
    },
    bold: {
      type: 'boolean',
    },
    html: {
      type: 'string',
    },
    text: {
      type: 'string',
    },
    align: {
      enum: [
        'left',
        'center',
        'right',
      ],
    },
    title: {
      enum: [
        'h1',
        'h2',
        'h3',
      ],
    },
    size: {
      type: 'integer',
    },
    color: {
      type: 'string',
    },
    linkColor: {
      type: 'string',
    },
    customFields: {
      type: 'object',
    },
  },
}

Image

The following sample code defines an image element with various attributes.


resolve({
  type: 'image',
  value: {
    alt: 'Alternative desc',
    href: 'http://www.example.com/',
    src: 'https://url.to.myimage.com,
    dynamicSrc: '{{any-merge-tag}}',
    target: '_self',
  }
})

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

alt

String

Yes

href

String (URL)

Yes

src

String (URL)

Yes

dynamicSrc

String (URL)

No

target

String

No

Image Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-image-single',
  type: 'object',
  required: ['src', 'alt', 'href'],
  properties: {
    type: {
      const: 'image',
    },
    alt: {
      type: 'string',
    },
    href: {
      type: 'string',
      format: 'urlOrMergeTags',
    },
    src: {
      type: 'string',
      format: 'urlOrMergeTags',
    },
    dynamicSrc: {
      type: 'string',
    },
    target: {
      enum: [
        '_blank',
        '_self',
        '_top',
      ],
    },
    customFields: {
      type: 'object',
    },
  },
}

Button

The following sample code defines a button element with various attributes.


resolve({
  "type" : "mixed",
  "value" : [
    {
      "type" : "button",
      "value" : {
        "label" : "Click here",
        "font-family" : "inherit",
        "font-size" : "14px",
        "background-color" : "#7747FF",
        "border-radius" : "4px",
        "color" : "#81A07B",
        "line-height" : "200%",
        "padding-top" : "5px",
        "padding-right" : "20px",
        "padding-bottom" : "5px",
        "padding-left" : "20px",
        "width" : "80%",
        "max-width" : "100%",
        "direction" : "ltr",
      }
    }
  ],
  "mergeTags" : [
    {
      "name" : "First Name",
      "value" : "@first_name",
      "previewValue" : "First Name Preview"
    },
    {
      "name" : "Address",
      "value" : "@address",
      "previewValue" : "Address Preview"
    }
  ]
});

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

label

String

Yes

href

String (URL)

No

target

String

No

color

String

No

background-color

String

No

Button Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-button-single',
  type: 'object',
  anyOf: [{
    required: ['label'],
  }, {
    required: ['text'],
  }],
  properties: {
    type: {
      const: 'button',
    },
    label: {
      type: 'string',
      format: 'noAnchorTags',
    },
    text: {
      type: 'string',
      format: 'noAnchorTags',
    },
    href: {
      type: 'string',
      format: 'urlOrMergeTags',
    },
    target: {
      enum: [
        '_blank',
        '_self',
        '_top',
      ],
    },
    color: {
      type: 'string',
    },
    'background-color': {
      type: 'string',
    },
    customFields: {
      type: 'object',
    },
  },
}

Paragraph

The following sample code defines a paragraph element with various attributes. The mergeTags property contains a list of merge tags, which can be used for dynamic content insertion.


resolve({
  "type" : "mixed",
  "value" : [
    {
      "type" : 'paragraph',
      "value" : {
        "html" : "<p>Hello @first_name,</p><p>Your address of @address was recently updated...</p>",
        "underline" : true,
        "italic" : true,
        "bold" : true,
        "color" : "pink",
        "linkColor" : "green",
      }
    }
  ],
  "mergeTags" : [
    {
      "name" : "First Name",
      "value" : "@first_name",
      "previewValue" : "First Name Preview"
    },
    {
      "name" : "Address",
      "value" : "@address",
      "previewValue" : "Address Preview"
    }
  ]
});

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

html

String (HTML content)

Yes

underline

Boolean

No

italic

Boolean

No

align

String

No

size

Number

No

bold

Boolean

No

color

String

No

linkColor

String

No

Paragraph Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-paragraph-single',
  type: 'object',
  required: ['html'],
  properties: {
    type: {
      const: 'paragraph',
    },
    underline: {
      type: 'boolean',
    },
    italic: {
      type: 'boolean',
    },
    bold: {
      type: 'boolean',
    },
    html: {
      type: 'string',
    },
    text: {
      type: 'string',
    },
    align: {
      enum: [
        'left',
        'center',
        'right',
      ],
    },
    size: {
      type: 'integer',
    },
    color: {
      type: 'string',
    },
    linkColor: {
      type: 'string',
    },
    customFields: {
      type: 'object',
    },
  },
}

HTML

The following sample code defines an HTML element with various attributes.

resolve({
    type: 'html',
    value: {
        html: `<div><h4>Html Block</h4>
    </div>`,
    })

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

html

String (HTML content)

Yes

HTML Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-html-single',
  type: 'object',
  required: ['html'],
  properties: {
    type: {
      const: 'html',
    },
    html: {
      type: 'string',
    },
    customFields: {
      type: 'object',
    },
  },
}

The code defines a menu structure with a list of items. Each item has text content (e.g., “Menu item”) and a link associated with it. The link includes a title, URL (href), and a target attribute, demonstrating that it generates a menu with clickable items that open the specified links in the same browser window or tab when clicked.


resolve({
  type: 'menu',
  value: {
    items: [
      {
        text: 'Menu item',
        link: {
          title: 'Link',
          href: 'https://beefree.io',
          target: '_self'
        }
      },
      ...
    ],
  }
})

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

items

Array of objects

Yes

text

String

No

link

Object

No

title

String

No

href

String (URL)

No

target

String

No

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-menu-single',
  type: 'object',
  required: ['items'],
  properties: {
    type: {
      const: 'menu',
    },
    items: {
      type: 'array',
      items: {
        type: 'object',
        additionalProperties: false,
        properties: {
          type: {
            const: 'menu-item',
          },
          text: {
            type: 'string',
          },
          link: {
            type: 'object',
            additionalProperties: false,
            properties: {
              title: {
                type: 'string',
              },
              href: {
                type: 'string',
                format: 'urlOrMergeTags',
              },
              target: {
                type: 'string',
                enum: [
                  '_blank',
                  '_self',
                  '_top',
                ],
              },
            },
          },
        },
      },
    },
    customFields: {
      type: 'object',
    },
  },
}

List

The following code snippet calls the resolve function to generate a list element defined by the type property set to 'list'. The value object specifies the list's characteristics, including the list tag ('ol'), HTML content, alignment, text formatting options like underline, italic, bold, and colors for text and links. This configuration allows you to customize list elements when creating custom addons for your host application using Beefree SDK.


resolve({
  type: 'list',
  value: {
    tag: 'ol',
    html: `<ul><li>1 item</li></ul>`,
    align: 'right',
    size: '48',
    underline: true,
    italic: true,
    bold: true,
    color: 'pink',
    linkColor: 'green',
  }
})

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

html

String (HTML content)

Yes

tag

String ('ol' or 'ul')

No

underline

Boolean

No

italic

Boolean

No

align

String ('left', 'center' or 'right')

No

size

Number

No

bold

Boolean

No

color

String

No

linkColor

String

No

List Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-list-single',
  type: 'object',
  required: ['html'],
  properties: {
    type: {
      const: 'list',
    },
    underline: {
      type: 'boolean',
    },
    italic: {
      type: 'boolean',
    },
    bold: {
      type: 'boolean',
    },
    html: {
      type: 'string',
    },
    text: {
      type: 'string',
    },
    align: {
      enum: [
        'left',
        'center',
        'right',
      ],
    },
    tag: {
      enum: [
        'ol',
        'ul',
      ],
    },
    size: {
      type: 'integer',
    },
    color: {
      type: 'string',
    },
    linkColor: {
      type: 'string',
    },
    customFields: {
      type: 'object',
    },
  },
}

Icons

The following code snippet defines an icons component for your application using Beefree SDK. It specifies an array of icon objects, including properties such as image URL, text, dimensions, and positioning. You can customize each icon with optional attributes like alt, title, and hyperlink details.


resolve({
  type: 'icons',
  value: {
    icons: [
      {
        image: 'https://link.to.my/icon.png',
        text: 'Icon text',
        target: '_self',
        alt: 'Image alt',
        title: 'Image title',
        href: 'https://beefree.io',
        width: '50px',
        height: '50px',
        textPosition: 'top'
      }
    ],
  }
})

The following table displays a list of properties in the resolve function, and each of its respective value types and whether or not they are mandatory.

PropertyValueMandatory

icons

Array of objects

Yes

image

String (URL)

Yes

width

String

Yes

height

String

Yes

textPosition

String ('left', 'right', 'top', or 'bottom')

Yes

text

String

No

title

String

No

alt

String

No

href

String (URL)

No

target

String (_blank, _self, or _top)

No

Icons Simplified Schema

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-icons-single',
  type: 'object',
  required: ['icons'],
  properties: {
    type: {
      const: 'icons',
    },
    icons: {
      type: 'array',
      items: {
        type: 'object',
        additionalProperties: false,
        required: [
          'image',
          'textPosition',
          'width',
          'height',
        ],
        properties: {
          alt: {
            type: 'string',
          },
          text: {
            type: 'string',
          },
          title: {
            type: 'string',
          },
          image: {
            type: 'string',
            format: 'urlOrMergeTags',
          },
          href: {
            type: 'string',
            format: 'urlOrMergeTags',
          },
          height: {
            type: 'string',
          },
          width: {
            type: 'string',
          },
          target: {
            enum: [
              '_blank',
              '_self',
              '_top',
            ],
          },
          textPosition: {
            enum: [
              'left',
              'right',
              'top',
              'bottom',
            ],
          },
        },
      },
    },
    customFields: {
      type: 'object',
    },
  },
}

Custom AddOn - Row

This feature expands the capabilities of Custom AddOns by including:

  • additional integrations (e.g. a Product Block integrated with client’s e-commerce)

  • domain-specific contents (e.g Event Block for an application that offers event engagement)

  • .. and much more

To take advantage of this new feature, you have to:



var beeConfig = { contentDialog: { addOn: { label: 'Custom AddOn Label',  handler: (resolve, reject, args) => { ... }  }  } }

  • The host application should return a valid response as the parameter of the “resolve” callback.

Response Content

A valid response that the hosting application should send to the Beefree app when the contents of a Row AddOn are selected must have the following structure:


{
  "type": "rowAddon", 
  "value": {
    ... here all the props accepted by the simplified JSON schema 
        (the one used for "custom-rows")
    ... like name, columns etc...
  }
}

For example:


{
    "type": "rowAddon",
    "value": {
        "name": "First item", // Identifies the row
        "columns": [{ // The columns inside the row
            "weight": 12, // This weight identifies a single column row
            "modules": [{ // This describes the content modules inside the column
                    "type": "title", // Every module is identified by a type parameter
                    "text": "How am I supposed to fight?"
                },
                {
                    "type": "paragraph",
                    "text": "Look, I can take you as far as Anchorhead. You can get a transport there to Mos Eisley or wherever you're going."
                },
                {
                    // List module, with common additional text parameters 

                    "type": "list",
                    "tag": "ol",
                    "text": "<ol><li>First List item</li><li>Second List item</li></ol>",
                    "align": "right",
                    "size": 50,
                    "bold": true,
                    "color": "green",
                    "linkColor": "yellow",
                    "italic": true,
                    "underline": true
                },
                // Icons module
                {
                    "type": "icons",
                    "icons": [{
                        "image": "http://cdn.onlinewebfonts.com/svg/img_456510.png",
                        "textPosition": "right",
                        "text": "Custom addon icon text",
                        "alt": "Custom addon icon alt",
                        "title": "Custom addon icon title",
                        "href": "https://www.google.com",
                        "target": "_blank",
                        "width": "100px",
                        "height": "100px"
                    }]
                }

            ]
        }]
    "metadata": {
        "myMeta": "myMetaValue"
    }, // an object of custom metadata that will be passed back to the Application when the content selection is triggered again
    }
}

The content dialog configuration, required for this custom AddOn is the same configuration used by the other AddOns. Please see the Content Dialog method paragraph.

The Content dialog method

The purpose of the contentDialog object in the code snippet is to handle different types of content that can be added to a dialog. It has a handler function that resolves with an image or HTML content based on the provided contentDialogId.

To set up the content dialogs you will need to add the contentDialog object to beeConfig. For more details about the content dialog, please review Content Dialog: How it works.

Configure content dialog in beeConfig


contentDialog: {
    addOn: {
        handler: (resolve, reject, args) => {
            if (args.contentDialogId == { addOnID }) {
                resolve(
                    {
                        "type": "image",
                        "value": {
                            "alt": "Alternative desc",
                            "href": "http://www.example.com/",
                            "src": "https://d1oco4z2z1fhwp.cloudfront.net/templates/default/731/HNY2020.gif",
                            "dynamicSrc": "{{any-merge-tag}}"
                        }
                    }
                )
            } else if (args.contentDialogId == { addOnID }) {
                resolve(
                    {
                        "type": "html",
                        "value": {
                            "html": "<div>example</div>",
                        }
                    }
                )
            }
        }, // close handler
    }, // close addOn
}, // close contentDialog

Image


{
  "type": "image",
  "value": {
    "alt": "Alternative desc",
    "href": "http://www.example.com/",
    "src": "https://d1oco4z2z1fhwp.cloudfront.net/templates/default/731/HNY2020.gif"
    "dynamicSrc": "{{any-merge-tag}}"
  }
}

HTML


{
  "type": "html",
  "value": {
    "html": "<div>example</div>",
  }
}

Custom Fields

This is optional. Should you feel the need to add custom fields when resolving, we created a “customFields” key allowing to you place any additional custom fields inside of that object.


resolve (
    {
        "type": "html",
        "value": {
            "html": "<div>example</div>",
            "customFields": {
                "customField_example_one": {
                    "example_key": "...",
                    "...": "..."
                },
                "customField_example_two": [
                    "example_index"
                ]
            },
        }
    }
)

Row Simplified Schema

The updated Beefree SDK simplified row schema offers significant benefits to developers working with Custom Addons. With the simplified row schema properties, you have more flexibility for creating Custom Addons and Rows. This added flexibility allows you to control permissions more granularly, ensuring that critical content, like Button URLs, remains unchanged by end users.

When a user opens the Custom Addon content dialog, the Beefree SDK sends the row's content along with the associated metadata to your application. This continuous synchronization of data allows you to listen to the changes your end users make, and eliminates the need to start over each time the dialog is reopened, enabling your application to monitor and respond to user changes dynamically.

{
  $schema: 'http://json-schema.org/draft-07/schema',
  $id: 'BEE-simplified-row',
  type: 'object',
  required: [
    'name',
    'columns',
  ],
  definitions: {
    padding: {
      type: 'integer',
      minimum: 0,
      maximum: 60,
    },
  },
  properties: {
    name: {
      type: 'string',
    },
    colStackOnMobile: {
      type: 'boolean',
    },
    rowReverseColStackOnMobile: {
      type: 'boolean',
    },
    contentAreaBackgroundColor: {
      type: 'string',
    },
    'background-color': {
      type: 'string',
    },
    'background-image': {
      type: 'string',
    },
    'background-position': {
      type: 'string',
    },
    'background-repeat': {
      type: 'string',
    },
    customFields: {
      type: 'object',
    },
    'display-condition': {
      type: 'object',
      properties: {
        type: {
          type: 'string',
        },
        label: {
          type: 'string',
        },
        description: {
          type: 'string',
        },
        before: {
          type: 'string',
        },
        after: {
          type: 'string',
        },
      },
    },
    metadata: {
      type: 'object',
    },
    columns: {
      type: 'array',
      minItems: 1,
      items: {
        type: 'object',
        required: [
          'weight',
          'modules',
        ],
        additionalProperties: false,
        properties: {
          weight: {
            type: 'integer',
            minimum: 1,
            maximum: 12,
          },
          'background-color': {
            type: 'string',
          },
          'padding-top': {
            $ref: '#/definitions/padding',