For developers
Modules
Modules are the reusable content blocks marketers add to campaigns. A strong module is easy to recognize, easy to configure, and flexible enough to cover a real content need without becoming messy.
Each module usually contains:
- HTML and Liquid code
- settings and inputs
- metadata such as thumbnail, hidden status, and AI context
What makes a good module
A good module should have one clear job. For example:
- hero
- article card
- promo banner
- image with text
- product row
If a module is trying to do everything, it usually becomes hard to use in the Campaign Editor.
Creating modules
You can create a module in the Design System Editor by:
- adding a new blank module
- extracting an existing section of code from the template base into a module
The extraction workflow is especially useful when you start from a finished HTML email and want to turn it into a reusable system.
Settings
Each module has its own settings. These settings are what marketers edit when they select the module in the Campaign Editor.
Read more:
Recipient conditions
A module or the template base can show part of its content to some recipients only. Wrap that part in recipient_if:
{% recipient_if segments contains "Vandra Circle members" %}
<p>Your member price is waiting in your account.</p>
{% recipient_elsif recipient.country == "NO" %}
<p>Free delivery anywhere in Norway.</p>
{% recipient_else %}
<p>Join Vandra Circle for member prices.</p>
{% endrecipient_if %}
The condition isn't Liquid. It names recipient fields by key, recipient.<key>, and saved segments by name, segments contains "<name>". Compare with ==, !=, > and <, test for an empty field with == blank or != blank, and combine with and, or and not. Mixing and and or needs parentheses, one level deep.
A design system has no integration, so each Campaign looks the names up in its own. Preview shows the branch for the current preview profile, and export wraps every branch in the ESP's conditional code. A name the Campaign's integration doesn't have blocks export. Recipient conditions work in modules and the template base, not in the subject line or preheader.
A date field can also compare with today:
{% recipient_if recipient.arrival_date == today+14 %}
<p>Two weeks to go. Here's our packing list.</p>
{% recipient_elsif recipient.arrival_date within today+7 %}
<p>See you this week.</p>
{% endrecipient_if %}
today is today's date, today+14 is 14 days from now and today-365 is 365 days ago. within today+7 matches today up to 7 days from now, and within today-30 the last 30 days. These count from the day the ESP builds the message and work only with the ESPs listed under Dates relative to today. With any other ESP, the Date conditions work in this Integration check blocks export.
Metadata
Metadata improves how the module behaves and how easy it is to manage.
Thumbnail
The thumbnail helps marketers recognize the module quickly in the module list. Use a realistic example state, not a blank placeholder.
Hidden status
Hidden modules stay in the design system but do not appear in the marketer-facing module list. This is useful when you want to retire a module without deleting its code immediately.
Conditional module visibility
Use Only show if when a module should be available only in some campaign contexts. The condition is checked against the campaign's current Global values — including values produced by the Global Script — and context.integration.id from the render context.
For example, a module can be available only for one business unit:
global.business_unit == 'retail'
Or only when the campaign uses a specific integration:
context.integration.id == 481
You can also use a Global Script to compute a simpler flag and use that in the module condition:
global.allow_hero_module == true
Module conditions can reference global.* values and context.integration.id (checking context.integration against null also works). The rest of the render context is not available here — and conditions referencing it are rejected when you save: context.email and context.block because visibility is decided before the email renders, context.isPreview because what's visible must agree between preview and export, and context.integration.name/.type because the integration id is the only field editor-side checks can reconstruct. If the condition evaluates to false, the module is hidden from the module list in the Campaign Editor and from Betty's available module list for that campaign.
This only controls whether the module can be added from that point forward. If a module has already been added to a campaign, it stays in the campaign even if the design system later gets a condition that would hide it from the module list.
In the Design System Editor preview, a selected module with a non-matching condition is still rendered with an overlay. This lets design system authors check the module layout while also seeing that marketers would not be able to add it in the current context.
Content-zone restrictions
If the design system has more than one content zone, each module can be restricted to the zones it actually fits in. Restrictions apply everywhere a module can be added or moved — the module sidebar, drag-and-drop, and Betty.
By default, a module is only allowed in the Main zone. Open another zone for a module only when it makes sense visually and structurally.
See Content zones for more details.
AI metadata
AI metadata helps Betty understand how the module should be used, what kind of content it is for, and any important rules it should follow.
Recommended habits
- Keep module names short and specific.
- Give every production module a useful thumbnail.
- Hide modules before deleting them when you are unsure whether they are still needed.
- Use conditional module visibility for business-unit, market, integration, or feature-flag differences that should affect which modules marketers can add.
- Use repeatable settings when one module needs a repeatable content structure.