Dragble Editor Configuration
Overview
All editor configuration is passed through the options object in dragble.init(). The top-level DragbleConfig controls boot-time settings (container, auth, mode), while options holds all runtime-configurable settings (appearance, tools, features, localization, merge tags, etc.).
dragble.init({
containerId: 'editor-container',
editorKey: 'ek_xxx',
editorMode: 'email',
options: {
appearance: { theme: 'light', accentColor: 'indigo' },
features: { preview: true, undoRedo: true },
tools: { video: { enabled: false } },
locale: 'en-US',
// ... all other config
},
});
DragbleConfig (Top Level)
| Property | Type | Required | Description |
|---|---|---|---|
containerId |
string |
Yes | DOM element ID where the editor iframe is mounted |
editorKey |
string |
Yes | Project editor key for authentication (ek_...) |
editorMode |
'email' | 'web' | 'popup' |
No | Builder type. Default: 'email' |
designMode |
'edit' | 'live' |
No | 'edit' = admin mode (shows Row Actions), 'live' = end-user mode (enforces row permissions). Default: 'live' |
design |
DesignJson | null |
No | Initial design JSON. null = blank design, undefined = no design loaded |
popup |
PopupConfig |
No | Popup builder config (only relevant when editorMode is 'popup') |
callbacks |
DragbleCallbacks |
No | Event callbacks (onReady, onChange, onLoad, onError, onModuleSave, onPreview, onContentDialog, etc.) |
options |
EditorOptions |
No | All editor configuration (appearance, tools, features, locale, merge tags, etc.) |
editorUrl |
string |
No | Custom editor source URL for enterprise self-hosted editors |
editorVersion |
'stable' | 'latest' | string |
No | Editor version to load. Default: 'latest'. Ignored when editorUrl is set |
environment |
'production' | 'development' | 'staging' |
No | Environment mode |
dragble.init({
containerId: 'editor',
editorKey: 'ek_live_abc123',
editorMode: 'email',
designMode: 'live',
design: savedDesignJson,
callbacks: {
onReady: () => console.log('Editor ready'),
onChange: (data) => console.log('Design changed', data),
},
options: { /* ... */ },
});
Appearance
Theme
The editor supports 4 themes. Branded themes ignore accentColor.
| Theme | accentColor |
Description |
|---|---|---|
'light' |
Customizable | Generic light theme, accent tints the UI |
'dark' |
Customizable | Generic dark theme, accent tints the UI |
'dragble-light' |
IGNORED (fixed indigo) | Dragble branded light theme |
'dragble-dark' |
IGNORED (fixed white-on-black) | Dragble branded dark theme |
CORRECT:
options: {
appearance: {
theme: 'light',
accentColor: 'teal', // Works — generic theme respects accentColor
}
}
WRONG:
options: {
appearance: {
theme: 'dragble-light',
accentColor: 'teal', // IGNORED — branded themes use fixed colors
}
}
Accent Colors
24 Radix UI color scale names:
gray, gold, bronze, brown, yellow, amber, orange, tomato, red, ruby, crimson, pink, plum, purple, violet, iris, indigo, blue, cyan, teal, jade, green, grass, mint, sky
Side Panel
options: {
appearance: {
sidePanel: {
tabs: {
content: { visible: true }, // CONTENT tab (tools)
modules: { visible: true }, // MODULES tab (rows + custom tabs)
styles: { visible: true }, // STYLES tab (body settings)
},
modulesTab: {
rows: { visible: true, defaultExpanded: true },
modulesLibrary: {
visible: true,
defaultExpanded: false,
title: 'My Modules',
categories: ['Banner', 'footer', 'header'], // Filter/order displayed categories (optional)
defaultCategory: 'Banner', // Pre-selected category (optional)
},
customTabs: [
{ id: 'snippets', label: 'Snippets', content: '...', icon: 'star', order: 1, visible: true }
],
},
stylesTab: {
general: { visible: true, defaultExpanded: true },
fonts: { visible: true, defaultExpanded: false },
contentAlignment: { visible: true },
backgroundImage: { visible: true },
linkStyles: { visible: true },
preheader: { visible: true },
},
dock: 'right', // 'left' | 'right' (default: 'right')
width: 380, // Panel width in pixels (default: 380)
collapsible: false, // Whether panel can be collapsed (default: false)
accordionsCollapsed: false, // Start all accordions collapsed (default: false)
},
}
}
Action Bar
options: {
appearance: {
actionBar: {
placement: 'top_left', // 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
compact: false, // Smaller buttons when true
},
}
}
Shortcut Bar
options: {
appearance: {
shortcutBar: {
placement: 'top_left', // 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
},
}
}
Custom Loader
Priority: svg > url > html + css > default spinner.
options: {
appearance: {
loader: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="none" stroke="#6366f1" stroke-width="4"><animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s" repeatCount="indefinite"/></circle></svg>',
// OR
url: 'https://example.com/loader.gif',
// OR
html: '<div class="my-loader">Loading...</div>',
css: '.my-loader { color: #6366f1; font-size: 18px; }',
},
}
}
Branding Colors
Set brand colors for all color pickers in the editor at runtime:
// Simple flat array
dragble.setBrandingColors({
colors: ['#FF0000', '#00FF00', '#0000FF', '#FFAA00'],
defaultColors: true, // show default palette below brand colors (default: true)
});
// Named palettes
dragble.setBrandingColors({
colors: [
{ id: 'primary', label: 'Primary', colors: ['#6366f1', '#4f46e5', '#4338ca'], default: true },
{ id: 'secondary', label: 'Secondary', colors: ['#f59e0b', '#d97706', '#b45309'] },
],
defaultColors: false, // hide default palette
});
Runtime
dragble.setAppearance({ theme: 'dark', accentColor: 'violet' });
Feature Flags
Feature flags live in options.features. All are optional.
| Feature | Type | Default | Description |
|---|---|---|---|
preview |
boolean |
true |
Show preview button |
undoRedo |
boolean |
true |
Show undo/redo buttons |
responsiveDesign |
boolean |
true |
Show responsive design view toggles (desktop/mobile) |
imageEditor |
boolean |
undefined |
Enable inline image editor. false = force disable even if plan allows |
audit |
boolean |
false |
Enable template audit trail |
stockImages |
boolean | StockImagesConfig |
true |
Stock images in file manager. false = hide even if plan allows |
userUploads |
boolean |
true |
Enable user image uploads |
fileManager |
boolean |
true |
Show File Manager modal. false hides "Choose Image" button but keeps direct upload |
smartMergeTags |
boolean |
false |
Enable smart merge tag suggestions |
specialLinks |
boolean |
true |
Show special links in text toolbar |
languageSelector |
boolean |
true |
Show language dropdown in side panel. false = language controlled via SDK only |
preheaderText |
boolean |
true |
Show preheader text input in Styles panel (email mode only) |
amp |
boolean |
false |
Enable AMP for Email (email mode only). Unlocks interactive tools |
modules |
boolean | ModuleFeaturesConfig |
false |
Enable Modules Library (save row as module, synced modules). Not available in contentType: 'module' |
header |
boolean |
false |
Enable locked header row from SDK. Requires plan permission |
footer |
boolean |
false |
Enable locked footer row from SDK. Requires plan permission |
dynamicImage |
boolean |
undefined |
Enable dynamic image merge tag on image tool (Business+ plans) |
collaboration |
boolean | CollaborationFeaturesConfig |
false |
Team collaboration (commenting, reviewer role). See Collaboration |
export |
ExportFeaturesConfig |
all true |
Granular export toggles: { html, json, image, pdf, zip } |
options: {
features: {
preview: true,
undoRedo: true,
responsiveDesign: true,
imageEditor: true,
audit: true,
stockImages: { enabled: true, safeSearch: true, defaultSearchTerm: 'business' },
userUploads: true,
fileManager: true,
smartMergeTags: false,
specialLinks: true,
languageSelector: true,
preheaderText: true,
amp: false,
modules: { saveRowAsModule: true },
header: false,
footer: false,
dynamicImage: true,
collaboration: true,
export: { html: true, json: true, image: true, pdf: true, zip: true },
}
}
Merge Tags
Merge tags are dynamic content placeholders (e.g., {{first_name}}) that users can insert into text.
MergeTagsConfig
interface MergeTagsConfig {
excludeDefaults?: boolean; // Hide built-in tags, show only custom (default: false)
sort?: boolean; // Sort alphabetically
customMergeTags?: (MergeTag | MergeTagGroup)[];
}
interface MergeTag {
label: string; // Display label in dropdown
value: string; // Value inserted (e.g., '{{first_name}}')
category?: string; // Optional grouping category
sample?: string; // Preview sample value
}
interface MergeTagGroup {
name: string; // Group name
mergeTags: (MergeTag | MergeTagGroup)[]; // Nested tags
}
Flat Example
options: {
mergeTags: {
customMergeTags: [
{ label: 'First Name', value: '{{first_name}}', sample: 'Jane' },
{ label: 'Last Name', value: '{{last_name}}', sample: 'Doe' },
{ label: 'Email', value: '{{email}}', sample: 'jane@example.com' },
],
}
}
Grouped Example
options: {
mergeTags: {
excludeDefaults: true,
customMergeTags: [
{
name: 'Contact Info',
mergeTags: [
{ label: 'First Name', value: '{{contact.first_name}}', sample: 'Jane' },
{ label: 'Last Name', value: '{{contact.last_name}}', sample: 'Doe' },
],
},
{
name: 'Company',
mergeTags: [
{ label: 'Company Name', value: '{{company.name}}', sample: 'Acme Inc' },
{ label: 'Website', value: '{{company.website}}', sample: 'https://acme.com' },
],
},
],
}
}
Runtime
dragble.setMergeTags({
customMergeTags: [
{ label: 'Coupon Code', value: '{{coupon_code}}', sample: 'SAVE20' },
],
});
const tags = await dragble.getMergeTags();
Special Links
Special links add custom link categories to the link picker in the text editor toolbar.
SpecialLinksConfig
interface SpecialLinksConfig {
excludeDefaults?: boolean;
customSpecialLinks?: (SpecialLink | SpecialLinkGroup)[];
}
interface SpecialLink {
name: string; // Display name
href: string; // Link href value
target?: '_blank' | '_self' | '_parent' | '_top';
}
interface SpecialLinkGroup {
name: string; // Group name
specialLinks: SpecialLink[];
}
Flat Example
options: {
specialLinks: {
customSpecialLinks: [
{ name: 'Unsubscribe', href: '{{unsubscribe_url}}', target: '_blank' },
{ name: 'View in Browser', href: '{{webview_url}}', target: '_blank' },
{ name: 'Manage Preferences', href: '{{preferences_url}}', target: '_blank' },
],
}
}
Grouped Example
options: {
specialLinks: {
excludeDefaults: true,
customSpecialLinks: [
{
name: 'Email Actions',
specialLinks: [
{ name: 'Unsubscribe', href: '{{unsubscribe_url}}' },
{ name: 'View in Browser', href: '{{webview_url}}' },
],
},
{
name: 'Social',
specialLinks: [
{ name: 'Share on Twitter', href: '{{share_twitter_url}}' },
{ name: 'Share on Facebook', href: '{{share_facebook_url}}' },
],
},
],
}
}
Runtime
dragble.setSpecialLinks({
customSpecialLinks: [
{ name: 'Survey', href: 'https://example.com/survey' },
],
});
const links = await dragble.getSpecialLinks();
Fonts
FontsConfig
interface FontsConfig {
excludeDefaults?: boolean; // Hide built-in fonts, show only custom (default: false)
customFonts?: FontDefinition[];
}
interface FontDefinition {
label: string; // Display label in dropdown
value: string; // CSS font-family value
url?: string; // URL to load font (Google Fonts, etc.)
weights?: number[]; // Available font weights (e.g., [400, 700])
defaultFont?: boolean; // Whether this is a default system font
}
Example
options: {
fonts: {
excludeDefaults: false,
customFonts: [
{
label: 'Inter',
value: "'Inter', sans-serif",
url: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap',
weights: [400, 500, 600, 700],
},
{
label: 'Playfair Display',
value: "'Playfair Display', serif",
url: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&display=swap',
weights: [400, 700],
},
],
}
}
Runtime
dragble.setFonts({
customFonts: [
{ label: 'Roboto', value: "'Roboto', sans-serif", url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap' },
],
});
const fonts = await dragble.getFonts();
Display Conditions
Display conditions enable conditional rendering of rows based on user data or context. Conditions wrap row HTML with before/after syntax (e.g., Liquid, Jinja, Handlebars) that the sending platform evaluates at render time.
DisplayConditionsConfig
interface DisplayConditionsConfig {
enabled?: boolean; // Enable feature (default: false)
conditions?: DisplayConditionDefinition[]; // Pre-defined conditions for picker
permissions?: DisplayConditionsPermissions;
}
interface DisplayConditionDefinition {
type: string; // Category (e.g., 'Customer Segment')
label: string; // Display label in picker
description?: string;
before: string; // Syntax before the row (e.g., '{% if customer.vip %}')
after: string; // Syntax after the row (e.g., '{% endif %}')
}
interface DisplayConditionsPermissions {
canSelect?: boolean; // Select from pre-defined conditions (default: true)
canAdd?: boolean; // Add custom conditions with own syntax (default: true)
canEdit?: boolean; // Edit existing conditions (default: true)
canRemove?: boolean; // Remove conditions from rows (default: true)
}
Template Engine Syntax
The before and after fields wrap the row's HTML at export time:
<!-- before -->
{% if customer.tier == "VIP" %}
<tr><!-- row HTML --></tr>
{% endif %}
<!-- after -->
Any template engine syntax works (Liquid, Jinja2, Handlebars, Mustache, etc.) since the editor passes these strings through verbatim.
Example
options: {
displayConditions: {
enabled: true,
conditions: [
{
type: 'Customer Segment',
label: 'VIP Members',
description: 'Only show to VIP tier customers',
before: '{% if customer.tier == "VIP" %}',
after: '{% endif %}',
},
{
type: 'Cart Status',
label: 'Has Items in Cart',
before: '{% if cart.items_count > 0 %}',
after: '{% endif %}',
},
{
type: 'Location',
label: 'US Customers Only',
before: '{{#if (eq country "US")}}',
after: '{{/if}}',
},
],
permissions: {
canSelect: true,
canAdd: false, // Users can only pick from pre-defined, not create custom
canEdit: false,
canRemove: true,
},
}
}
Runtime
dragble.setDisplayConditions({
enabled: true,
conditions: [
{ type: 'Segment', label: 'New Users', before: '{% if user.new %}', after: '{% endif %}' },
],
permissions: { canSelect: true, canAdd: true, canRemove: true },
});
// Disable display conditions
dragble.setDisplayConditions({ enabled: false });
Localization
Supported Locales
The editor ships with full translations for 29 locales (BCP 47 format):
| Code | Language |
|---|---|
en-US |
English (US) |
en-CA |
English (Canada) |
ar-AE |
Arabic (UAE) — RTL |
zh-CN |
Chinese (Simplified) |
zh-TW |
Chinese (Traditional) |
cs-CZ |
Czech |
da-DK |
Danish |
nl-NL |
Dutch |
et-EE |
Estonian |
fa-IR |
Farsi / Persian — RTL |
fi-FI |
Finnish |
fr-FR |
French (France) |
fr-CA |
French (Canada) |
de-DE |
German |
hu-HU |
Hungarian |
id-ID |
Indonesian |
it-IT |
Italian |
ja-JP |
Japanese |
ko-KR |
Korean |
no-NO |
Norwegian |
pl-PL |
Polish |
pt-BR |
Portuguese (Brazil) |
pt-PT |
Portuguese (Portugal) |
ru-RU |
Russian |
es-ES |
Spanish |
sv-SE |
Swedish |
tr-TR |
Turkish |
uk-UA |
Ukrainian |
vi-VN |
Vietnamese |
Base codes (e.g., 'en', 'fr', 'de') are also accepted and auto-mapped to their default regional variant.
Configuration
options: {
locale: 'fr-FR',
// Override or extend built-in translations
translations: {
'fr-FR': {
'content_tools.paragraph': 'Mon Paragraphe',
'buttons.save': 'Sauvegarder',
},
// Add a completely new language
'th': {
'content_tools.paragraph': 'ย่อหน้า',
'buttons.save': 'บันทึก',
// Missing keys fall back to English
},
},
textDirection: 'ltr', // 'ltr' | 'rtl'. Auto-set when language has rtl flag.
// Template language (distinct from UI locale)
language: {
label: 'العربية',
value: 'ar-SA',
rtl: true, // Auto-detects RTL from language code (ar, he, fa, ur) when omitted
default: false,
},
}
Priority: translations[locale] > built-in locale > English US defaults.
Runtime
// Switch UI locale
dragble.setLocale('fr-FR');
// Switch with custom overrides
dragble.setLocale('fr-CA', { 'buttons.save': 'Sauvegarder' });
// Set template language (auto-switches text direction for RTL)
dragble.setLanguage({ label: 'العربية', value: 'ar-SA', rtl: true });
// Get current template language
const lang = await dragble.getLanguage();
// Manual text direction control
dragble.setTextDirection('rtl');
const dir = await dragble.getTextDirection();
Collaboration
Team collaboration enables commenting, reviewer roles, and @mentions on the editor canvas.
CollaborationFeaturesConfig
interface CollaborationFeaturesConfig {
enabled?: boolean; // Enable collaboration (default: false)
role?: 'editor' | 'reviewer'; // 'editor' = full edit + comment, 'reviewer' = read-only + comment
commenting?: boolean | CollaborationCommentingConfig;
onComment?: (action: CommentAction) => void;
}
interface CollaborationCommentingConfig {
enabled?: boolean; // Enable commenting (default: true when collaboration enabled)
mentions?: boolean; // Enable @mentions (default: true)
getMentions?: (search: string) => Promise<MentionUser[]>;
}
interface MentionUser {
userHandle: string; // e.g., '@jane'
username: string; // 'Jane Smith'
userColor?: string; // '#3498DB'
}
CommentAction Types
| Type | Description |
|---|---|
NEW_COMMENT |
A new comment or reply was created |
COMMENT_EDITED |
An existing comment's content was modified |
COMMENT_DELETED |
A comment was removed |
COMMENT_THREAD_RESOLVED |
A root comment thread was marked as resolved |
COMMENT_THREAD_REOPENED |
A previously resolved thread was reopened |
Requirements
Collaboration requires user.id to be set in options so comments can be attributed:
options: {
user: { id: 'user-123', name: 'Jane Doe', email: 'jane@example.com', color: '#3b82f6' },
}
Simple Example
options: {
user: { id: 'user-123', name: 'Jane Doe' },
features: {
collaboration: true, // Enable commenting for current user as 'editor' role
},
}
Reviewer Role
options: {
user: { id: 'user-456', name: 'Bob Reviewer' },
features: {
collaboration: {
enabled: true,
role: 'reviewer', // Read-only editing + commenting
commenting: true,
},
},
}
Advanced with @Mentions and Notifications
options: {
user: { id: 'user-123', name: 'Jane Doe', color: '#E74C3C' },
features: {
collaboration: {
enabled: true,
role: 'editor',
commenting: {
enabled: true,
mentions: true,
getMentions: async (search) => {
const res = await fetch(`/api/team?q=${encodeURIComponent(search)}`);
return await res.json();
// Returns: [{ userHandle: '@bob', username: 'Bob Smith', userColor: '#3498DB' }]
},
},
onComment: (action) => {
console.log(`${action.type}: ${action.comment.content}`);
if (action.type === 'NEW_COMMENT' && action.mentions.length > 0) {
notifyMentionedUsers(action.mentions);
}
// Sync all comments to backend
saveCommentsToBackend(action.comments);
},
},
},
}
Editor Behavior
EditorBehaviorConfig
interface EditorBehaviorConfig {
minRows?: number; // Minimum rows in body (default: 0)
maxRows?: number | null; // Maximum rows (default: unlimited)
contentType?: 'module'; // 'module' = single-row module editor (locked to 1 row)
autoSelectOnDrop?: boolean; // Auto-select element when dropped (default: false)
confirmOnDelete?: boolean; // Show confirm dialog before deleting (default: false)
rows?: boolean; // Show Rows accordion in Modules tab (default: true)
title?: string; // Editor title (shown in header)
}
Example
options: {
editor: {
minRows: 1,
maxRows: 10,
autoSelectOnDrop: true,
confirmOnDelete: true,
rows: true,
title: 'Newsletter Editor',
},
}
Module Editor (Single-Row)
options: {
editor: {
contentType: 'module', // Locks editor to 1 row for reusable content editing
},
}
Runtime
dragble.setEditorConfig({
maxRows: 5,
autoSelectOnDrop: true,
confirmOnDelete: false,
});
const config = await dragble.getEditorConfig();
Body/Canvas Values
Control the email/page body defaults (background, content width, fonts, links, preheader).
setBodyValues / getBodyValues
interface SetBodyValuesOptions {
backgroundColor?: string;
contentWidth?: string; // e.g., '600px'
contentAlign?: 'left' | 'center';
backgroundImage?: Partial<BackgroundImage>;
fontFamily?: FontFamily; // { label: string, value: string, url?: string }
textColor?: string;
preheaderText?: string; // Email only
linkStyle?: Partial<LinkStyle>;
}
Example
// Set at init
options: {
bodyValues: {
backgroundColor: '#f5f5f5',
contentWidth: '600px',
contentAlign: 'center',
textColor: '#333333',
fontFamily: { label: 'Inter', value: "'Inter', sans-serif" },
preheaderText: 'Check out our latest deals...',
linkStyle: {
linkColor: '#6366f1',
linkHoverColor: '#4f46e5',
linkUnderline: true,
linkHoverUnderline: true,
},
},
}
// Set at runtime
dragble.setBodyValues({
backgroundColor: '#ffffff',
contentWidth: '700px',
});
// Get current values
const bodyValues = await dragble.getBodyValues();
Tools Configuration
Built-in Tools
14 built-in content tools:
| Tool Key | Description |
|---|---|
text |
Text / paragraph (alias: paragraph) |
heading |
Heading |
button |
Button / CTA |
image |
Image |
divider |
Horizontal divider |
menu |
Navigation menu |
html |
Raw HTML |
social |
Social media icons |
video |
Video embed |
table |
Table |
timer |
Countdown timer |
form |
Form (AMP only in email mode) |
spacer |
Vertical spacer |
paragraph |
Alias for text |
ToolConfig
interface ToolConfig {
enabled?: boolean;
position?: number; // Sort order in tools panel
properties?: Record<string, ToolPropertyConfig>;
}
interface ToolPropertyConfig {
value?: unknown; // Default value override
editable?: boolean; // Whether user can change this property
}
Example
options: {
tools: {
video: { enabled: false },
form: { enabled: false },
heading: {
position: 1,
properties: {
text: { value: 'Default Heading Text' },
fontSize: { value: '28px' },
color: { value: '#111827' },
},
},
button: {
properties: {
backgroundColor: { value: '#6366f1' },
borderRadius: { value: '8px', editable: true },
},
},
image: {
properties: {
alt: { value: '', editable: true },
},
},
}
}
Runtime
dragble.setToolsConfig({
video: { enabled: true },
social: { enabled: false },
});
Popup Config
Popup configuration is only relevant when editorMode is 'popup'.
PopupConfig
interface PopupConfig {
defaultWidth?: string; // Default popup width
defaultHeight?: string; // Default popup height
exportMode?: 'full' | 'partial'; // 'full' = complete HTML document, 'partial' = embeddable fragment
popupId?: string; // Custom popup ID for HTML element IDs
}
Example
dragble.init({
containerId: 'editor',
editorKey: 'ek_xxx',
editorMode: 'popup',
popup: {
defaultWidth: '500px',
defaultHeight: 'auto',
exportMode: 'full',
popupId: 'my-popup',
},
});
getPopupValues
Retrieve the current popup settings from the design:
const popupValues = await dragble.getPopupValues();
// Returns: {
// campaignType: 'lightbox',
// position: { horizontal: 'center', vertical: 'center' },
// width: '500px',
// height: 'auto',
// overlay: { enabled: true, color: 'rgba(0,0,0,0.5)', closeOnClick: true },
// closeButton: { enabled: true, position: 'top-right', size: '24px', color: '#fff', ... },
// animation: 'fade',
// animationDuration: '300ms',
// borderRadius: '12px',
// boxShadow: '0 25px 50px rgba(0,0,0,0.25)',
// padding: '20px',
// border: { width: '0', style: 'none', color: '#000' },
// displayDelay: 0,
// contentAlign: 'center',
// contentVerticalAlign: 'middle',
// }
Note: backgroundColor and backgroundImage are NOT part of popup values. They are managed through bodyValues, same as email/web mode.
Common Mistakes & Troubleshooting
1. Using accentColor with branded themes
// WRONG: accentColor is ignored with dragble-light / dragble-dark
appearance: { theme: 'dragble-light', accentColor: 'teal' }
// CORRECT: Use generic themes for custom accent
appearance: { theme: 'light', accentColor: 'teal' }
2. Missing user.id for collaboration
// WRONG: Collaboration won't attribute comments
features: { collaboration: true }
// CORRECT: Always set user.id
options: {
user: { id: 'user-123', name: 'Jane' },
features: { collaboration: true },
}
3. Calling methods before editor is ready
// WRONG: Editor may not be ready yet
dragble.init({ ... });
dragble.setMergeTags({ ... }); // May throw
// CORRECT: Use onReady callback
dragble.init({
containerId: 'editor',
editorKey: 'ek_xxx',
callbacks: {
onReady: () => {
dragble.setMergeTags({ customMergeTags: [...] });
},
},
});
4. Confusing locale (UI) with language (template content)
localecontrols the editor interface language (button labels, panel text, tooltips)languagecontrols the template content direction and language metadata
options: {
locale: 'en-US', // Editor UI in English
language: { label: 'العربية', value: 'ar-SA', rtl: true }, // Template content in Arabic (RTL)
}
5. Using panels instead of sidePanel
panels is deprecated. Use sidePanel for all panel configuration:
// WRONG (deprecated)
appearance: { panels: { tools: { dock: 'left' } } }
// CORRECT
appearance: { sidePanel: { dock: 'left', width: 380 } }
6. Expecting backgroundColor in popup values
Popup background color and image are managed through bodyValues, not popupValues:
// Access popup background via design, not popup values
const design = await dragble.getDesign();
const bgColor = design.body.values.backgroundColor;
7. Feature flags vs plan permissions
Feature flags can only disable features, not grant them. If a feature requires a plan upgrade (e.g., image editor, dynamic image), setting features.imageEditor: true won't work unless the plan allows it. Setting features.imageEditor: false always disables it regardless of plan.