Build AddOns with External Iframe

Overview

The External Iframe Method allows Custom AddOns to be hosted on different domains from the host application. This method is required for Partner AddOns or any AddOn you want to distribute to other Beefree SDK applications. Unlike the Content Dialog method which runs in your application's context, the Iframe method loads your addon as a separate web application in an iframe, communicating with Beefree through the browser's postMessage API. This architecture enables cross-domain functionality and makes your addon portable across different Beefree SDK implementations.

Required Components

To build an iframe-based addon, you need three main components working together. Your AddOn Application is the web interface users interact with, running in an iframe inside Beefree. The JavaScript API implements postMessage communication to send and receive data between your iframe and the Beefree editor. The Server API provides a health check endpoint that Beefree uses to verify your addon is available, and optionally handles authentication if you want to control which applications can use your addon.

  1. Your AddOn Application — Hosted on your domain (can be any web technology: React, Vue, vanilla HTML, etc.)

  2. JavaScript API — Communication via postMessage (Details)

  3. Server API — Health check endpoint (authentication optional)

Quick Setup

1. Create AddOn in Console

Before implementing your iframe application, you must configure the addon in the Beefree SDK Developer Console. This registration tells Beefree where to load your iframe from, how to verify it's healthy, and optionally how to authenticate requests. These settings are critical—the iframe URL must be publicly accessible, the health check endpoint must respond quickly and reliably, and authentication settings (if used) must be properly configured to avoid blocking legitimate access.

Log into developers.beefree.io:

Field
Value

Method

External iframe

Iframe URL

https://yourdomain.com/addon

Healthcheck URL

https://yourdomain.com/api/health (optional but recommended)

API Key

your-api-key (optional, for authentication)

Authentication URL

https://yourdomain.com/api/auth (optional)

2. Implement JavaScript API

Your iframe must implement a specific postMessage protocol to communicate with Beefree. This protocol defines five critical messages: loaded (sent when your iframe is ready), init (received from Beefree with context), load (received when editing existing content), onSave (sent when inserting content), and onCancel (sent when aborting). Understanding this message flow is essential—missing any of these messages will break the addon's functionality and leave users with non-responsive dialogs.

Your iframe must implement postMessage communication:

Required messages:

  • Send loaded when ready

  • Listen for init from Beefree

  • Listen for load when editing existing content

  • Send onSave with content

  • Send onCancel on exit

3. Implement Health Check

The health check endpoint is a simple HTTP GET endpoint that returns a 200 status code if your addon service is operational. Beefree periodically calls this endpoint to verify your addon is available before offering it to users. This prevents frustrating experiences where users try to use an addon that's down. The health check should be lightweight—it doesn't need to validate complex logic, just confirm your service is responding. A simple "OK" response is sufficient.

Simple endpoint that returns 200:

// Express.js example
app.get('/api/health', (req, res) => {
  // Return 200 OK to indicate the service is healthy
  res.status(200).send('OK');
});

// You can optionally include more detailed health information
app.get('/api/health', (req, res) => {
  res.status(200).json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: '1.0.0'
  });
});

JavaScript API (Quick Reference)

1. Send "loaded"

The loaded message is the first thing your iframe must send—it tells Beefree that your application has finished loading and is ready for interaction. The data object in this message controls the modal's appearance: dimensions (width/height), whether it has rounded corners (isRounded), whether it shows a title bar with close button (hasTitleBar), and whether to display the addon name (showTitle). This configuration determines the visual presentation of your addon within the Beefree editor.

// Send this immediately when your iframe loads
window.parent.postMessage({
  action: 'loaded',
  data: {
    width: '800px',      // Modal width
    height: '600px',     // Modal height
    isRounded: true,     // Rounded corners
    hasTitleBar: true,   // Show title bar with close button
    showTitle: true      // Display AddOn name
  }
}, '*');

2. Listen for "init"

After Beefree receives your loaded message, it responds with an init message containing context about the editor environment. The locale property tells you the user's language setting, allowing you to localize your interface. The hasOpenOnDrop boolean indicates whether the addon was triggered via Direct Open (automatic on drop). The optional data property can contain pass-through information you've configured. This context allows your addon to adapt its behavior based on how and where it's being used.

window.addEventListener('message', (event) => {
  const { action, data } = event.data;
  
  if (action === 'init') {
    console.log('Locale:', data.locale);           // e.g., 'en-US'
    console.log('Direct Open:', data.hasOpenOnDrop); // true/false
    console.log('Custom data:', data.data);         // Optional pass-through
  }
});

3. Listen for "load" (Editing Mode)

When users edit existing content that was previously inserted by your addon, Beefree sends a load message containing the original content data. Your iframe should use this data to pre-fill your UI, allowing users to modify their previous selections. This is critical for a good editing experience—without it, users would have to reconfigure everything from scratch each time they edit. The data parameter contains the exact content object you previously sent via onSave.

window.addEventListener('message', (event) => {
  const { action, data } = event.data;
  
  if (action === 'load') {
    // Pre-fill your UI with existing content
    // data.value contains the content object you previously sent
    if (data && data.value) {
      document.getElementById('myInput').value = data.value.someProperty || '';
      // Update all relevant UI elements with stored values
    }
  }
});

4. Send "onSave"

When the user completes their interaction with your addon and confirms they want to insert content, you send the onSave message containing the content object. This content object must follow Beefree's schema for your addon type—the structure with type and value properties is critical. Beefree validates this object against the schema, and if it's valid, inserts the content into the email template. If the schema is invalid, the insertion silently fails, so proper validation before sending is essential.

// Call this when user clicks save/insert
window.parent.postMessage({
  action: 'onSave',
  data: {
    type: 'html',  // Must match your addon type
    value: {
      html: '<div>Your content here</div>'
    }
  }
}, '*');

5. Send "onCancel"

If the user decides not to insert content—whether by clicking a cancel button or closing your interface—you must send the onCancel message to properly close the modal. This tells Beefree the operation is complete but no content should be inserted. Failing to send either onSave or onCancel leaves the modal open indefinitely, creating a poor user experience. Every user interaction path in your addon should end with one of these two messages.

// Call this when user cancels or closes
window.parent.postMessage({
  action: 'onCancel'
}, '*');

See JavaScript API documentation for complete details.

Simple Working Example

This complete HTML example demonstrates a fully functional iframe addon. It shows the entire postMessage protocol implementation: sending loaded on page load with proper modal configuration, listening for init and load messages, and sending onSave or onCancel based on user actions. The load message handler is particularly important—it pre-fills your UI when users are editing existing content, allowing them to modify what they previously inserted. This example serves as a starting point you can build upon with richer UI, validation, and features.

<!DOCTYPE html>
<html>
<head>
  <title>My AddOn</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    textarea {
      width: 100%;
      height: 200px;
      padding: 10px;
      margin: 10px 0;
    }
    button {
      padding: 10px 20px;
      margin-right: 10px;
    }
  </style>
</head>
<body>
  <h2>Add Content</h2>
  <textarea id="content" placeholder="Enter your HTML content here..."><div style="padding: 20px;"><p>Hello World!</p></div></textarea>
  <button onclick="save()">Insert Content</button>
  <button onclick="cancel()">Cancel</button>

  <script>
    // Step 1: Notify Beefree that iframe has loaded
    window.parent.postMessage({
      action: 'loaded',
      data: { 
        width: '700px', 
        height: '500px',
        isRounded: true,
        hasTitleBar: true,
        showTitle: true
      }
    }, '*');

    // Step 2: Listen for messages from Beefree
    window.addEventListener('message', (event) => {
      const { action, data } = event.data;
      
      if (action === 'init') {
        // Beefree is ready - editor context received
        console.log('Editor initialized with locale:', data.locale);
      }
      
      if (action === 'load') {
        // User is editing existing content - pre-fill the UI
        if (data && data.value && data.value.html) {
          document.getElementById('content').value = data.value.html;
        }
      }
    });

    // Step 3: Save function sends content to Beefree
    function save() {
      const html = document.getElementById('content').value;
      
      if (!html.trim()) {
        alert('Please enter some content');
        return;
      }
      
      window.parent.postMessage({
        action: 'onSave',
        data: {
          type: 'html',
          value: { html }
        }
      }, '*');
    }

    // Step 4: Cancel function closes without inserting
    function cancel() {
      window.parent.postMessage({
        action: 'onCancel'
      }, '*');
    }
  </script>
</body>
</html>

The modal's appearance is controlled by the data object in your loaded message. These properties determine whether users see a full-screen takeover or a smaller centered dialog, whether there are rounded corners for a modern look, and whether a title bar provides clear context about what addon they're using. Different modal configurations suit different addon types—full-screen for complex editors, smaller dialogs for simple selectors, title bars for clarity when multiple addons are available.

Customize the modal appearance in the loaded message:

window.parent.postMessage({
  action: 'loaded',
  data: {
    isRounded: true,        // Rounded corners for modern look
    hasTitleBar: true,      // Show title bar with close button
    showTitle: true,        // Display AddOn name in title
    width: '800px',         // Modal width
    height: '600px'         // Modal height
  }
}, '*');

Common configurations:

// Full-screen takeover - maximizes space for complex UIs
data: {}  // No properties = 100% viewport

// Fixed size with branding - professional appearance
data: {
  width: '700px',
  height: '500px',
  isRounded: true,
  hasTitleBar: true,
  showTitle: true
}

// Centered modal without title - clean, focused experience
data: {
  width: '900px',
  height: '700px',
  isRounded: true,
  hasTitleBar: false
}

// Compact selector - for quick choices
data: {
  width: '500px',
  height: '350px',
  isRounded: true,
  hasTitleBar: true
}

Content Object Schemas

Your onSave message must include a valid content object that matches your addon type's schema. These schemas define the exact structure Beefree expects—missing required properties or using incorrect property names causes insertion to fail. Each addon type has different requirements: images need src and alt, HTML needs an html string, buttons need label and often href, and mixed content needs an array of module objects. Understanding these schemas is fundamental to iframe addon development.

Your onSave message must include a valid content object:

Image

{
  type: 'image',
  value: {
    src: 'https://example.com/photo.jpg',
    alt: 'Photo description for accessibility',
    href: 'https://example.com',  // Optional clickable link
    target: '_blank'               // Optional link behavior
  }
}

HTML

{
  type: 'html',
  value: {
    html: '<div style="padding: 20px; background: #f0f0f0;"><h2>Welcome!</h2><p>Custom HTML content block.</p></div>'
  }
}

Button

{
  type: 'button',
  value: {
    label: 'Click Here',
    href: 'https://example.com',
    '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
  }
}

Mixed Content

{
  type: 'mixed',
  value: [
    { 
      type: 'image', 
      value: { 
        src: 'https://example.com/banner.jpg', 
        alt: 'Banner' 
      } 
    },
    { 
      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/info',
        'background-color': '#0066CC',
        'border-radius': 4,
        'padding-top': 12,
        'padding-right': 24,
        'padding-bottom': 12,
        'padding-left': 24
      }
    }
  ]
}

See AddOn Types for all schemas.

Testing Checklist

This checklist ensures your addon implements all required functionality before going live. Test each item systematically to catch issues early—missing even one of these can cause confusing user experiences or complete addon failure.

  • Health check returns 200 status quickly (< 1 second)

  • loaded message is sent immediately on iframe load with proper config

  • loaded message includes isRounded, hasTitleBar, showTitle properties

  • Modal appears with correct size and styling

  • init message is received and processed

  • load message handler pre-fills content correctly when editing

  • onSave sends correct content object matching schema

  • onCancel closes modal without inserting content

  • Content inserts correctly in editor and renders properly

  • Authentication (if used) succeeds for valid requests

  • Authentication (if used) blocks invalid requests

  • Cross-browser testing (Chrome, Firefox, Safari, Edge)

  • Mobile/responsive behavior (if applicable)

Troubleshooting

Issue
Solution

Iframe not loading

Check URL accessibility from external networks, verify CORS settings, test health endpoint manually

Authentication failing

Verify API key in headers matches expected value, check auth endpoint logs, confirm client ID is in allowlist

postMessage not working

Validate message format exactly matches specs, check browser console for errors, verify origin validation

Content not inserting

Verify content object schema matches addon type, check for missing required properties, test with minimal valid object

Modal sizing wrong

Check loaded message data object, ensure width/height are valid CSS values

Edit mode not working

Implement load message listener, pre-fill UI with event.data.value contents

Button not displaying

Verify padding and border-radius values are numbers, not strings

Last updated

Was this helpful?