dragble-config — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dragble-config (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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
},
});| 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: { /* ... */ },
});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
}
}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
options: {
appearance: {
sidePanel: {
tabs: {
content: { visible: true }, // CONTENT tab (tools + saved modules)
modules: { visible: true }, // MODULES tab (rows + custom tabs)
styles: { visible: true }, // STYLES tab (body settings)
},
modulesTab: {
rows: { visible: true, defaultExpanded: true },
savedModules: { visible: true, defaultExpanded: false, title: 'My Modules' },
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)
},
}
}options: {
appearance: {
actionBar: {
placement: 'top_left', // 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
compact: false, // Smaller buttons when true
},
}
}options: {
appearance: {
shortcutBar: {
placement: 'top_left', // 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
},
}
}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; }',
},
}
}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
});dragble.setAppearance({ theme: 'dark', accentColor: 'violet' });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 (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 are dynamic content placeholders (e.g., {{first_name}}) that users can insert into text.
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
}options: {
mergeTags: {
customMergeTags: [
{ label: 'First Name', value: '{{first_name}}', sample: 'Jane' },
{ label: 'Last Name', value: '{{last_name}}', sample: 'Doe' },
{ label: 'Email', value: '{{email}}', sample: '[email protected]' },
],
}
}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' },
],
},
],
}
}dragble.setMergeTags({
customMergeTags: [
{ label: 'Coupon Code', value: '{{coupon_code}}', sample: 'SAVE20' },
],
});
const tags = await dragble.getMergeTags();Special links add custom link categories to the link picker in the text editor toolbar.
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[];
}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' },
],
}
}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}}' },
],
},
],
}
}dragble.setSpecialLinks({
customSpecialLinks: [
{ name: 'Survey', href: 'https://example.com/survey' },
],
});
const links = await dragble.getSpecialLinks();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
}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],
},
],
}
}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 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.
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)
}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.
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,
},
}
}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 });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.
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.
// 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();Team collaboration enables commenting, reviewer roles, and @mentions on the editor canvas.
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'
}| 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 |
Collaboration requires user.id to be set in options so comments can be attributed:
options: {
user: { id: 'user-123', name: 'Jane Doe', email: '[email protected]', color: '#3b82f6' },
}options: {
user: { id: 'user-123', name: 'Jane Doe' },
features: {
collaboration: true, // Enable commenting for current user as 'editor' role
},
}options: {
user: { id: 'user-456', name: 'Bob Reviewer' },
features: {
collaboration: {
enabled: true,
role: 'reviewer', // Read-only editing + commenting
commenting: true,
},
},
}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);
},
},
},
}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)
}options: {
editor: {
minRows: 1,
maxRows: 10,
autoSelectOnDrop: true,
confirmOnDelete: true,
rows: true,
title: 'Newsletter Editor',
},
}options: {
editor: {
contentType: 'module', // Locks editor to 1 row for reusable content editing
},
}dragble.setEditorConfig({
maxRows: 5,
autoSelectOnDrop: true,
confirmOnDelete: false,
});
const config = await dragble.getEditorConfig();Control the email/page body defaults (background, content width, fonts, links, preheader).
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>;
}// 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();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 |
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
}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 },
},
},
}
}dragble.setToolsConfig({
video: { enabled: true },
social: { enabled: false },
});Popup configuration is only relevant when editorMode is 'popup'.
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
}dragble.init({
containerId: 'editor',
editorKey: 'ek_xxx',
editorMode: 'popup',
popup: {
defaultWidth: '500px',
defaultHeight: 'auto',
exportMode: 'full',
popupId: 'my-popup',
},
});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.
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' }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 },
}// 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: [...] });
},
},
});locale (UI) with language (template content)locale controls the editor interface language (button labels, panel text, tooltips)language controls the template content direction and language metadataoptions: {
locale: 'en-US', // Editor UI in English
language: { label: 'العربية', value: 'ar-SA', rtl: true }, // Template content in Arabic (RTL)
}panels instead of sidePanelpanels is deprecated. Use sidePanel for all panel configuration:
// WRONG (deprecated)
appearance: { panels: { tools: { dock: 'left' } } }
// CORRECT
appearance: { sidePanel: { dock: 'left', width: 380 } }backgroundColor in popup valuesPopup 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;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.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.