Skip to main content

Create a New Page Builder Element

What you'll learn
  • how to create a new Page Builder element
  • how to add custom plugins to Webiny apps

Overview#

Although the Page Builder app comes with a lot of ready-made page elements, at one point in time, you might need to create your own to satisfy your specific requirements. To achieve that, we can utilize a couple of simple plugins, which is what we'll cover in this short tutorial. So, without further ado let's get started.

What We'll Build#

We’ll create a new element that will allow content creators to embed content using an iframe. Here's what the end result will look like:

Iframe page element
note

We're creating a sample plugin here, but the same principle applies no matter what type of plugin the developer wants to create.

Prerequisites#

  1. A Webiny Project

This tutorial assumes you have already created a new Webiny project to work on. We recommend reading our install Webiny tutorial which will show you how to do it.

  1. Organization of Files

Although you can place and organize the plugin files as you see fit for your project, we recommend following some kind of structure which is easy to understand and maintain later on.

In this particular case, we'll need to share some logic (a plugin) across multiple apps i.e. apps/admin and apps/website.

For this we're going to create a package named iFrameElement inside packages folder, and simply import and reuse the code (plugin) across apps.

note

This is a very common scenario for Page Builder plugins because the Admin app use the plugin to render element inside editor and the Website app need the same plugin to render element in the actual page.

To create a new page element, we need to register two sets of plugins:

  1. For the admin app:
    • a plugin that will defines how it's rendered in the editor
    • a plugin that will defines all of the available settings and options for it
  2. For the website app:
    • a plugin that will defines how the page element is rendered on the actual page

To better organize files we'll write all the plugins needed for the Admin app in iFrameElement/src/admin and plugins needed for the Website app in iFrameElement/src/render.

In the end the folder structure of our project will look similar to this:

// Config files are omitted for sake of brevity.
β”œβ”€β”€ api
β”‚Β Β  β”œβ”€β”€ code
β”‚Β Β  |__ pulumi
β”œβ”€β”€ apps
β”‚Β Β  β”œβ”€β”€ admin
β”‚Β Β  β”œβ”€β”€ theme
β”‚Β Β  └── website
β”œβ”€β”€ package.json
β”œβ”€β”€ packages
| | // This is our new page element plugin
β”‚Β Β  └── iFrameElement
β”‚Β Β  β”œβ”€β”€ src
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ admin
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── iFrameEmbed.tsx
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── index.tsx
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ assets
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── iframe-icon.svg
β”‚Β Β  β”‚Β Β  └── render
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── iFrame.tsx
β”‚Β Β  β”‚Β Β  └── index.tsx
β”‚Β Β  └── tsconfig.json
└── yarn.lock

Creating the Plugins#

All of the available page elements can be accessed via the elements menu, which can be opened by clicking on the "plus" icon, located on the left side of the editor:

Iframe page element

As mentioned, this list of page elements can be expanded and custom page elements can be created via plugins. Now that we have covered the brief overview of what we need to do and how to organize files. Let's get started with creating plugins.

Add Plugins in the Admin App#

Editor Plugin#

Let's add the new page element in the editor. First, we will add the necessary plugins in the packages/iFrameElement/src/admin/index.tsx file.

We will start with the pb-editor-page-element plugin type.

packages/iFrameElement/src/admin/index.tsx
// ...
// Some code is removed for sake of brevity.
// ...
import { PbEditorPageElementPlugin } from "@webiny/app-page-builder/types";
import { ReactComponent as IFrameIcon } from "./iframe-icon.svg";
import IFrameEmbed from "./components/IFrameEmbed";
export default () => {
return [
{
name: "pb-editor-page-element-iframe",
type: "pb-editor-page-element",
elementType: "iframe",
toolbar: {
// We use `pb-editor-element-group-media` to put our plugin into the Media group.
title: "iFrame",
group: "pb-editor-element-group-media",
preview() {
return (
<PreviewBox>
<IFrameIcon />
</PreviewBox>
);
}
},
settings: ["pb-editor-page-element-settings-delete"],
target: ["cell", "block"],
onCreate: "open-settings",
create(options) {
/*
Create function is here to create the initial data
for the page element, which then is utilized in the
IFrameEditor component and in the settings dialog.
*/
return {
type: "iframe",
elements: [],
data: {
iframe: {
// The URL property will be populated when user enters the URL in the settings dialog.
url: "",
height: 370
},
settings: {
horizontalAlign: "center",
margin: {
desktop: { all: 0 },
mobile: { all: 0 }
},
padding: {
desktop: { all: 0 },
mobile: { all: 0 }
}
}
},
...options
};
},
render(props) {
/*
Every render function receives the page element's
data assigned to the "element.data" property in
the received props. In here we will store the
"iframe.url" which will be provided via the page
element's settings dialog.
*/
return <IFrameEmbed {...props} />;
},
renderElementPreview({ width, height }) {
return <img style={{ width, height }} alt={"iFrame"} />;
}
} as PbEditorPageElementPlugin
];
};

The key properties of the plugin are the create and render. They define the initial page element's settings and how it will be rendered in the editor, respectively.

The data property holds the initial state of the page element which can contain any data you might need.

The toolbar property helps us put our plugin into the tool bar.

Finally, it’s up to the render function to define how the page element will be rendered once the user drops it on the page.

note

Notice the props that were passed to the render function. This object contains all of the relevant page element's data and settings.

After registering the plugin in the Admin app it will appear in editor toolbar similar to the pre-defined elements as shown below:

Editor Iframe page element

Now that we've covered the overall structure of plugin. Let's see what's inside IFrameEmbed component. The following code snippet shows the entire IFrameEmbed component:

packages/iFrameElement/src/admin/components/iFrameEmbed.tsx
// ...
// Some code is removed for sake of brevity.
// ...
const IFrameEmbed = props => {
const { element } = props;
if (!element.data.iframe.url) {
return (
<PreviewBox>
<IFrameIcon />
</PreviewBox>
);
}
return (
<ElementRoot
className={"webiny-pb-base-page-element-style webiny-pb-page-element-iframe " + outerWrapper}
element={element}
>
<div className={innerWrapper}>
<div id={element.id} />
<iframe src={element.data.iframe.url} width="100%" height={element.data.iframe.height} />
</div>
</ElementRoot>
);
};
export default IFrameEmbed;

As you can see it's a simple React component which render an iframe HTML element.

note

Every element needs to have ElementRoot component as root because it apply styles, classes and attributes to the element assigned via right sidebar style tab.

info

Notice we've added webiny-pb-base-page-element-style CSS class. Every element needs to have it as it contains all base styles applied by Page Builder app.

tip

Notice we've added webiny-pb-page-element-iframe CSS class, which enables us to add custom CSS styling if needed.

Element Settings#

The next plugin we'll need to define is the pb-editor-page-element-advanced-settings, which we will use to render required UI for settings so the user can provide an iframe URL. The settings UI will be shown automatically in right sidebar when the user drags and drops the page element on the page.

This plugin type will be added in the packages/iFrameElement/src/admin/index.tsx file too.

packages/iFrameElement/src/admin/index.tsx
// ...
// Some code is removed for sake of brevity.
// ...
{
name: "pb-editor-page-element-advanced-settings-iframe",
type: "pb-editor-page-element-advanced-settings",
elementType: "iframe",
render({ Bind, submit }) {
return (
<Accordion title={"IFrame"} defaultValue={true}>
<React.Fragment>
<Bind
name={"iframe.url"}
validators={validation.create("required,url")}
>
<Input label={"URL"} description={"Enter an iFrame URL"} />
</Bind>
<Grid className={classes.simpleGrid}>
<Cell span={12}>
<ButtonContainer>
<SimpleButton onClick={submit}>Save</SimpleButton>
</ButtonContainer>
</Cell>
</Grid>
</React.Fragment>
</Accordion>
);
}
} as PbEditorPageElementAdvancedSettingsPlugin

As mentioned, this code will show the settings UI in sidebar and ask for the URL of the iframe, as shown in the image below:

Iframe page element settings

Render Plugin#

As mentioned, every page element consists of two sets of plugins. We've already covered the editor side of the logic now, let's also see the actual plugin/code that will render it to the page.

In order to render the element on the actual page, we will use the pb-render-page-element plugin.

We will add this plugin in the packages/iFrameElement/src/render/index.tsx file.

packages/iFrameElement/src/render/index.tsx
import React from "react";
import { PbRenderElementPlugin } from "@webiny/app-page-builder/types";
import IFrame from "./components/iFrame";
export default () =>
({
name: "pb-render-page-element-iframe",
type: "pb-render-page-element",
elementType: "iframe",
render({ element }) {
return <IFrame element={element} />;
}
} as PbRenderElementPlugin);

The following code snippet shows the IFrame component:

packages/iFrameElement/src/render/components/iFrame.tsx
import { ElementRoot } from "@webiny/app-page-builder/render/components/ElementRoot";
//...
// Some code is removed for the sake of brevity.
//...
const IFrame = ({ element }) => {
const { data } = element;
// If the user didn't enter a URL, let's show a simple message.
if (!data.iframe.url) {
return <div>IFrame URL is missing.</div>;
}
// Otherwise, let's render the iframe.
return (
<ElementRoot
className={
"webiny-pb-base-page-element-style webiny-pb-page-element-embed-iframe " + outerWrapper
}
element={element}
>
<div className={innerWrapper}>
<div id={data.id} />
<iframe src={data.iframe.url} width="100%" height={data.iframe.height} />
</div>
</ElementRoot>
);
};
export default IFrame;

Along with the actual page, this plugin will also responsible for rendering the element in the page preview, as shown in the image below:

Page preview inside editor

Register Plugins in the Admin App#

Now that we've created plugins for iFrame element. We need to register them in apps/admin to work. Let's make the required changes as shown below:

apps/admin/code/src/plugins/pageBuilder/editorPlugins.ts

Page Builder editor plugins

apps/admin/code/src/plugins/pageBuilder/renderPlugins.ts

Page Builder editor plugins

After registering the plugins successfully you will see them in the Admin app as shown below:

Editor Iframe page element
info

Using the same approach you can create as many custom elements as you need for your project.

Add Plugins in the Website App#

Render Plugin#

In order to render the page element on the website app, we will use the pb-render-page-element plugin again.

The render plugin is necessary to load our page element in the page.

packages/iFrameElement/src/render/index.tsx
import { PbRenderElementPlugin } from "@webiny/app-page-builder/types";
import IFrame from "./components/iFrame";
export default () =>
({
name: "pb-render-page-element-iframe",
type: "pb-render-page-element",
elementType: "iframe",
render({ element }) {
return <IFrame element={element} />;
}
} as PbRenderElementPlugin);

Like mentioned before the following code snippet shows the IFrame component:

packages/iFrameElement/src/render/components/iFrame.tsx
import { ElementRoot } from "@webiny/app-page-builder/render/components/ElementRoot";
//...
// Some code is removed for the sake of brevity.
//...
const IFrame = ({ element }) => {
const { data } = element;
// If the user didn't enter a URL, let's show a simple message.
if (!data.iframe.url) {
return <div>IFrame URL is missing.</div>;
}
// Otherwise, let's render the iframe.
return (
<ElementRoot
className={
"webiny-pb-base-page-element-style webiny-pb-page-element-embed-iframe " + outerWrapper
}
element={element}
>
<div className={innerWrapper}>
<div id={data.id} />
<iframe src={data.iframe.url} width="100%" height={data.iframe.height} />
</div>
</ElementRoot>
);
};
export default IFrame;
note

In order for these plugins to actually work. We need to add them to plugins inside apps/website. Please see the example below.

apps/website/code/src/plugins/pageBuilder.ts

Page Builder editor plugins

After registering the plugins successfully the page element now will be available in your website app, shown in the image below:

Page preview inside editor

Conclusion#

Congratulations!! πŸŽ‰πŸ₯³

We've successfully created a custom page element for our Page Builder app. Use similar approach to easily create a whole array of custom elements for Page Builder app.

You can also checkout the full code example in our repo. If you have further questions, feel free to ask for additional help.

Last updated on by Ashutosh