Skip to main content

Custom Block

What is a custom block? Custom block is composed of one or more basic blocks. Since version 1.25.0, Easy Email Pro supports direct MJML syntax and HTML code writing.

Features​

  1. Different UI rendering in edit mode and production mode
  2. Support for dynamic data (mergetags) and dynamic rendering
  3. Direct MJML and HTML code writing
  4. In-place text editing via SlateNodePlaceholder

Implementation Guide​

1. Define Block Types​

First, define your custom block type:

custom-types.ts
import { BasicElement } from "easy-email-pro-core";
import { CustomBlockType } from "./custom";

type CustomType = typeof CustomBlockType;

export interface DemoLogoBlockNode extends BasicElement {
type: CustomType["LOGO"];
data: {
buttonText: string;
};
attributes: {
src: string;
"button-color"?: string;
};
}

declare module "easy-email-pro-core" {
export interface CustomTypes {
Element: DemoLogoBlockNode;
}
}

2. Create Custom Block​

Define your block component:

custom/index.tsx
import { createCustomBlock, t, mergeBlock } from "easy-email-pro-core";
import React from "react";
import { ElementCategory } from "easy-email-pro-core";
import { DemoLogoBlockNode } from "src/custom-types";

export const CustomBlockType = {
LOGO: "custom_logo" as const,
};

const defaultData = {
attributes: {
src: "",
},
data: {
buttonText: "Save",
},
};

export const CustomLogo = createCustomBlock<DemoLogoBlockNode>({
get name() {
return t("Custom Logo");
},
defaultData: defaultData,
type: CustomBlockType.LOGO,
void: true,
create: (payload) => {
const data: DemoLogoBlockNode = {
type: CustomBlockType.LOGO,
data: {
...defaultData.data,
},
attributes: {
...defaultData.attributes,
},
children: [],
};
return mergeBlock(data, payload);
},
category: ElementCategory.SECTION,
render(params) {
const { node } = params;
const { data, attributes } = node;

return (
<mj-section {...node.attributes}>
<mj-column>
{data.showLogo ? (
<mj-image
padding="0px 0px 0px 0px"
width="100px"
src={attributes.src}
/>
) : null}
{data.showBtn ? (
<mj-button background-color={attributes["button-color"]} href="#">
{data.buttonText}
</mj-button>
) : null}
</mj-column>
</mj-section>
);
},
});

3. Create Configuration Panel​

Create a panel for block configuration:

custom/Panel.tsx
import { Collapse } from "@arco-design/web-react";
import { t } from "easy-email-pro-core";
import { useSelectedNode } from "easy-email-pro-editor";
import React from "react";
import { Path } from "slate";
import {
AttributesPanelWrapper,
CollapseWrapper,
AttributeField,
} from "easy-email-pro-theme";

export const CustomBlockPanel = ({ nodePath }: { nodePath: Path }) => {
const { selectedNode } = useSelectedNode();

if (!selectedNode) return null;

return (
<AttributesPanelWrapper>
<CollapseWrapper defaultActiveKey={["0", "1"]}>
<Collapse.Item name="0" header={t("Image")}>
<AttributeField.ImageUrl
label={t("Image URL")}
name="src"
path={nodePath}
/>
</Collapse.Item>
<Collapse.Item name="1" header={t("Button")}>
<AttributeField.TextField
label={t("Button Text")}
name="data.buttonText"
path={nodePath}
/>
<AttributeField.ColorPickerField
label={t("Button color")}
name="button-color"
path={nodePath}
/>
</Collapse.Item>
</CollapseWrapper>
</AttributesPanelWrapper>
);
};

4. Register and Use​

Register your custom block and add it to the editor:

import { BlockManager, ElementType, t } from "easy-email-pro-core";
import React from "react";
import { EmailEditorProvider } from "easy-email-pro-editor";
import { Retro } from "easy-email-pro-theme";

// Register blocks
BlockManager.registerBlocks([CustomLogo]);
ConfigPanelsMap[CustomLogo.type] = CustomBlockPanel;

const defaultCategories: ThemeConfigProps["categories"] = [
{
get label() {
return t("Content");
},
active: true,
displayType: "grid",
blocks: [
// ... other blocks
{
type: CustomBlockType.LOGO,
icon: (
<IconFont
className={"block-list-grid-item-icon"}
iconName="icon-bag"
/>
),
},
],
},
// ... other categories
];

export default function MyEditor() {
const config = Retro.createConfig({
categories: defaultCategories,
// ... other config
});

return (
<EmailEditorProvider {...config}>
<Retro.Layout />
</EmailEditorProvider>
);
}

Dynamic Content​

Logic Components​

Easy Email Pro provides built-in logic components:

// ForEach example
<ForEach dataSource={dataSourceKey} itemName="item">
<mj-text>{{item.title}}</mj-text>
</ForEach>

// Show example
<Show
showTruthyInTesting
expression={"order.number == 'Shopify#1001'"}
fallback={<mj-text>false</mj-text>}
>
<mj-text>True</mj-text>
</Show>

HTML Elements​

You can use HTML elements directly within mj-text or mj-button:

// Simple HTML
<mj-text>
<p>Hello</p>
</mj-text>

// Iframe example
<mj-text>
<iframe
width={"100%"}
src="https://www.easyemail.pro"
frameBorder="0"
></iframe>
</mj-text>

Important Notes​

MJML vs React Components​

When creating custom blocks, you have two options for rendering:

  1. MJML Components (mj-xxx): Components like <mj-section>, <mj-column>, <mj-image>, <mj-button>, etc.

    • These are used to output MJML strings directly
    • Do NOT support responsive design (no mobileAttributes)
    • Will be converted to MJML markup during rendering
    • Example:
    return (
    <mj-section>
    <mj-column>
    <mj-image src={attributes.src} />
    </mj-column>
    </mj-section>
    );
  2. React Components (components.Text, components.Section, components.Wrapper, etc.):

    • These support responsive design through mobileAttributes
    • Use when you need desktop/mobile variations
    • Example:
    const { Text, Section } = components;
    return (
    <Section {...attributes}>
    <Text>{content}</Text>
    </Section>
    );

Note: For custom blocks, we recommend using mj-xxx components for simplicity. Responsive design is typically handled at the page level or through standard blocks.

The void Property​

The void property is crucial for Slate.js rendering:

  • Use void: false for blocks with Element children (e.g., section, column)
  • Use void: true for other cases (recommended)