JavaScript API for Iframe AddOns
Overview
The JavaScript API enables communication between your external iframe AddOn and the Beefree editor using the browser's postMessage
API. This bidirectional messaging system is the foundation of all iframe-based addons—it's how your addon tells Beefree it's ready, receives context about the editor environment, sends content to be inserted, and signals when operations are complete or canceled. Understanding this API is essential for building functional iframe addons.
You do not need this if:
You're using the Content Dialog Method
Communication Protocol
Message Structure
All messages follow a consistent JSON structure with an action
property identifying the message type and an optional data
property containing the payload. This standardization makes the API predictable and easy to implement—every message you send or receive follows this same pattern, reducing cognitive overhead and making debugging straightforward. The action
field is always a string, and the data
field can contain any JSON-serializable data appropriate for that action.
All messages use this format:
{
action: string, // Action type: 'loaded', 'init', 'onSave', etc.
data: any // Optional payload specific to the action
}
Communication Flow
Understanding the message flow sequence is critical for proper addon implementation. Your iframe must send loaded
first to signal readiness, then wait for Beefree to respond with init
containing context. If the user is editing existing content, Beefree also sends load
with the current content. Finally, your addon sends either onSave
(to insert content) or onCancel
(to abort) based on user actions. Missing any step in this flow causes the addon to malfunction.
Your Iframe Beefree Editor
──────────────────────────────────────────────────
1. Send 'loaded' ──────────────────→
←──────────────── 2. Send 'init'
←──────────────── 3. Send 'load' (if editing)
[User interacts with your UI]
4. Send 'onSave' ──────────────────→
OR
Send 'onCancel' ─────────────────→
Messages Your AddOn Sends
1. loaded
— Iframe is Ready
The loaded
message is your addon's first communication with Beefree—it signals that your iframe has finished loading, initialized its UI, and is ready to receive messages. This is a critical handshake; without it, Beefree doesn't know your addon is ready and won't send the init
message or display your interface. Always send this message as early as possible in your application's lifecycle, typically in a DOMContentLoaded or useEffect hook.
When: Immediately after your iframe loads Purpose: Tells Beefree the AddOn is ready to communicate
// Send this as soon as your iframe is ready
window.parent.postMessage({
action: 'loaded',
data: {
width: '800px',
height: '600px',
isRounded: true,
hasTitleBar: true,
showTitle: true
}
}, '*');
With Modal Configuration
The data
object in the loaded
message controls the visual presentation of your addon within Beefree. Setting appropriate dimensions ensures your UI displays optimally. The isRounded
property adds modern rounded corners, hasTitleBar
provides a close button and context, and showTitle
displays your addon's name. These visual options let you match Beefree's design language while maintaining your addon's identity.
Options:
isRounded
Boolean
false
Rounded modal corners
hasTitleBar
Boolean
false
Show title bar with close button
showTitle
Boolean
false
Display AddOn name in title
width
String
'100%'
Modal width ('600px'
, '80%'
, '80vw'
)
height
String
'100%'
Modal height ('400px'
, '90%'
, '90vh'
)
2. onSave
— Send Content to Editor
The onSave
message is how your addon delivers content to be inserted into the email template. The data
object must contain a valid content object matching your addon type's schema—this is where all your addon's work culminates in structured data that Beefree can process. The schema validation is strict: missing required properties, incorrect property names, or wrong data types cause silent failures. Always validate your content object before sending to avoid frustrating debugging sessions.
When: User clicks save/submit button Purpose: Insert content into the editor
// Send this when user confirms they want to insert content
window.parent.postMessage({
action: 'onSave',
data: {
type: 'html', // Must match your addon type from Console
value: { // Type-specific content structure
html: '<div>Your content here</div>'
}
}
}, '*');
Examples by Type
Each addon type has its own schema requirements. These examples show the minimal valid structure for common types—understanding these patterns is essential for successful content insertion. Note the subtle differences: images need src
and alt
, buttons need label
, mixed content needs an array. Learning these schemas thoroughly prevents the frustration of silently failing insertions.
Image:
{
action: 'onSave',
data: {
type: 'image',
value: {
src: 'https://example.com/photo.jpg',
alt: 'Descriptive alt text for accessibility',
href: 'https://example.com', // Optional: makes image clickable
target: '_blank' // Optional: link behavior
}
}
}
Button:
Important: Button properties like border-radius
and all padding values must be numbers, not strings.
{
action: 'onSave',
data: {
type: 'button',
value: {
label: 'Get Started',
href: 'https://example.com/signup',
'background-color': '#0066CC',
'border-radius': 4, // Number, not string
color: '#FFFFFF',
'padding-top': 12, // Number, not string
'padding-right': 24,
'padding-bottom': 12,
'padding-left': 24
}
}
}
Paragraph:
{
action: 'onSave',
data: {
type: 'paragraph',
value: {
html: '<p>This is a paragraph of text.</p>',
color: '#333333',
bold: false
}
}
}
Mixed Content:
Important: Within Mixed Content, titles use type: 'title'
(not type: 'heading'
). This is different from standalone Title AddOns which use type: 'heading'
.
{
action: 'onSave',
data: {
type: 'mixed',
value: [
{
type: 'image',
value: {
src: 'https://example.com/img.jpg',
alt: 'Image'
}
},
{
type: 'title', // Note: 'title' not 'heading' in mixed content
value: {
text: 'Feature Heading',
align: 'center',
size: 28
}
},
{
type: 'paragraph',
value: {
html: '<p>Description text</p>'
}
},
{
type: 'button',
value: {
label: 'Learn More',
href: 'https://example.com',
'background-color': '#0066CC',
'border-radius': 4,
'padding-top': 12,
'padding-right': 24,
'padding-bottom': 12,
'padding-left': 24
}
}
]
}
}
3. onCancel
— User Canceled
The onCancel
message tells Beefree to close your addon's modal without inserting any content. This is essential for the user experience, every interaction path must end with either onSave
or onCancel
. Users might cancel by clicking a cancel button, closing the dialog, or hitting escape.
When: User clicks cancel or closes modal Purpose: Close modal without inserting content
// Send this when user decides not to insert content
window.parent.postMessage({
action: 'onCancel'
}, '*');
Messages Your AddOn Receives
1. init
— Editor Context
After Beefree receives your loaded
message, it responds with init
containing context about the editor environment. This context lets your addon adapt its behavior: the locale
property enables internationalization, showing your UI in the user's language. The hasOpenOnDrop
boolean indicates whether Direct Open triggered the addon (automatic on drop), allowing different workflows for quick inserts versus detailed configuration. The optional data
property can pass through custom information configured in the host application.
When: After Beefree receives your loaded
message
Contains: Editor locale, Direct Open status, and optional custom data
window.addEventListener('message', (event) => {
const { action, data } = event.data;
if (action === 'init') {
console.log('Locale:', data.locale); // e.g., 'en-US', 'es-ES'
console.log('Direct Open:', data.hasOpenOnDrop); // true/false
console.log('Custom Data:', data.data); // Optional pass-through
// Use locale to show translated UI
loadTranslations(data.locale);
// Adapt behavior based on Direct Open
if (data.hasOpenOnDrop) {
// Show quick-select UI for fast insertion
showQuickSelect();
} else {
// Show full configuration UI
showFullEditor();
}
}
});
Structure:
{
action: 'init',
data: {
locale: 'en-US', // Editor language setting
hasOpenOnDrop: false, // Was addon triggered via Direct Open?
data: {} // Optional pass-through data from host app
}
}
2. load
— Edit Existing Content
When users click to edit content they previously inserted with your addon, Beefree sends the load
message containing the existing content object. This enables stateful addons where users can modify their previous choices rather than starting from scratch each time. Your addon should use this data to pre-fill its UI, showing the current configuration. Users see what they created before and can make targeted changes.
When: User is editing previously inserted content Contains: The existing content object that was originally inserted
window.addEventListener('message', (event) => {
const { action, data } = event.data;
if (action === 'load') {
console.log('Editing existing content:', data);
// Pre-fill your UI with existing values
if (data && data.value) {
if (data.type === 'html' && data.value.html) {
document.getElementById('htmlInput').value = data.value.html;
}
if (data.type === 'image') {
document.getElementById('imageUrl').value = data.value.src;
document.getElementById('altText').value = data.value.alt;
}
if (data.type === 'button') {
document.getElementById('btnLabel').value = data.value.label;
document.getElementById('btnUrl').value = data.value.href;
}
// Show any custom metadata you stored
if (data.value.customFields) {
restoreCustomSettings(data.value.customFields);
}
}
}
});
Structure:
{
action: 'load',
data: {
type: 'html', // The addon type
value: {
html: '<div>Existing content</div>',
// Any other properties you included when inserting
customFields: { /* your custom data */ }
}
}
}
Complete Implementation
This complete example demonstrates a production-ready iframe addon with proper message handling, user input validation, edit mode support, and comprehensive comments. It shows the full lifecycle: initialization, message listening, content creation, and cleanup. Use this as a template for your own addons, replacing the HTML/textarea with your actual UI components. The pattern shows immediate loaded
, centralized message listener, validation before sending
<!DOCTYPE html>
<html>
<head>
<title>My AddOn</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
margin: 0;
}
h2 {
margin-top: 0;
}
textarea {
width: 100%;
height: 250px;
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
}
.button-group {
margin-top: 15px;
}
button {
padding: 10px 20px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.save-btn {
background-color: #0066CC;
color: white;
}
.cancel-btn {
background-color: #6c757d;
color: white;
}
</style>
</head>
<body>
<h2>Add Content</h2>
<p>Enter your HTML content below:</p>
<textarea id="content" placeholder="<div> <h1>Hello World!</h1> <p>Your content here</p> </div>"><div style="padding: 20px; background-color: #f0f0f0;">
<h2>Hello World!</h2>
<p>This is sample HTML content.</p>
</div></textarea>
<div class="button-group">
<button class="save-btn" onclick="save()">Insert Content</button>
<button class="cancel-btn" onclick="cancel()">Cancel</button>
</div>
<script>
// Step 1: Send 'loaded' message immediately
window.parent.postMessage({
action: 'loaded',
data: {
width: '700px',
height: '550px',
isRounded: true,
hasTitleBar: true,
showTitle: true
}
}, '*');
// Step 2: Listen for messages from Beefree
window.addEventListener('message', (event) => {
const { action, data } = event.data || {};
// Handle 'init' message - editor is ready
if (action === 'init') {
console.log('Editor initialized');
console.log('Locale:', data.locale);
console.log('Direct Open:', data.hasOpenOnDrop);
// You can adapt your UI based on locale or Direct Open
// e.g., loadTranslations(data.locale);
}
// Handle 'load' message - user is editing existing content
if (action === 'load') {
console.log('Loading existing content for editing');
// Pre-fill the textarea with existing HTML
if (data && data.value && data.value.html) {
document.getElementById('content').value = data.value.html;
}
// If you stored custom metadata, restore it
if (data && data.value && data.value.customFields) {
console.log('Custom fields:', data.value.customFields);
// Restore any custom settings from metadata
}
}
});
// Step 3: Save handler - validates and sends content
function save() {
const html = document.getElementById('content').value;
// Validate user input
if (!html.trim()) {
alert('Please enter some content');
return;
}
// Optional: Add custom metadata for tracking
const contentObject = {
type: 'html',
value: {
html: html,
// Optional: Store custom data that persists with the content
customFields: {
createdAt: new Date().toISOString(),
version: '1.0'
}
}
};
// Send content to Beefree
window.parent.postMessage({
action: 'onSave',
data: contentObject
}, '*');
console.log('Content sent to editor');
}
// Step 4: Cancel handler - closes without inserting
function cancel() {
console.log('User canceled');
// Notify Beefree to close the modal
window.parent.postMessage({
action: 'onCancel'
}, '*');
}
// Optional: Handle escape key to cancel
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
cancel();
}
});
</script>
</body>
</html>
Troubleshooting
Messages not received
Check browser console for errors, verify postMessage syntax is exact, ensure listener is added before messages arrive
Modal not appearing
Ensure loaded
message is sent immediately on page load, check modal config properties are valid, verify health check endpoint is responding
Content not inserting
Validate content object schema matches addon type exactly, check for missing required properties, test with minimal valid object first
Can't edit existing content
Implement load
message listener, pre-fill UI with event.data.value
contents, handle case where value might be undefined
Cross-browser issues
Test in all major browsers (Chrome, Firefox, Safari, Edge), check for browser-specific postMessage quirks, use polyfills if needed
Button not displaying
Verify padding and border-radius values are numbers, not strings
Last updated
Was this helpful?