dragble-integration — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dragble-integration (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.
Dragble is an embeddable drag-and-drop email/web/popup editor. You integrate it into your application using either a framework wrapper (React, Vue, Angular) or the vanilla JavaScript SDK loaded from CDN.
| Package | Install | Purpose |
|---|---|---|
dragble-react-editor | npm install dragble-react-editor | React wrapper component |
dragble-vue-editor | npm install dragble-vue-editor | Vue 3 wrapper component |
dragble-angular-editor | npm install dragble-angular-editor | Angular wrapper component |
dragble-types | npm install dragble-types | Shared TypeScript type definitions |
The SDK itself (`@dragble/editor-sdk`) is CDN-only. It is loaded automatically by the framework wrappers from https://sdk.dragble.com/{version}/dragble-sdk.min.js. There is NO npm package for the SDK. NEVER suggest npm install @dragble/editor-sdk.
ek_* or px_*."email" (default), "web", "popup"."edit" (admin -- shows Row Actions for setting permissions), "live" (end-user -- enforces permissions). Default: "live".npm install dragble-react-editorTypes are included automatically via re-export from dragble-types.
import { useRef, useState } from "react";
import { DragbleEditor, type DragbleEditorRef, type DesignJson } from "dragble-react-editor";
export default function EmailEditor() {
const editorRef = useRef<DragbleEditorRef>(null);
const [lastSaved, setLastSaved] = useState<string | null>(null);
const handleReady = (editor) => {
console.log("Editor ready");
};
const handleSave = async () => {
const editor = editorRef.current?.editor;
if (!editor) return;
// Export HTML
const html = await editor.exportHtml();
console.log("HTML:", html);
// Export JSON (for saving/restoring designs)
const json = await editor.exportJson();
console.log("JSON:", json);
setLastSaved(new Date().toISOString());
};
const handleLoad = () => {
const editor = editorRef.current?.editor;
if (!editor) return;
// Load a previously saved design
const savedDesign: DesignJson = JSON.parse(localStorage.getItem("design")!);
editor.loadDesign(savedDesign);
};
return (
<div>
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
<button onClick={handleSave}>Save</button>
<button onClick={handleLoad}>Load Saved</button>
</div>
<DragbleEditor
ref={editorRef}
editorKey="ek_your_editor_key"
editorMode="email"
height="800px"
onReady={handleReady}
onChange={(data) => console.log("Design changed:", data.type)}
onError={(err) => console.error("Editor error:", err)}
/>
</div>
);
}| Prop | Type | Default | Description | ||
|---|---|---|---|---|---|
editorKey | string | required | Authentication key (ek_* or px_*) | ||
editorMode | `"email" \ | "web" \ | "popup"` | "email" | Editor type |
design | `DesignJson \ | ModuleData \ | null` | undefined | Initial design to load |
designMode | `"edit" \ | "live"` | "live" | Template permission mode | |
contentType | "module" | undefined | Lock to single-row module editing | ||
options | EditorOptions | {} | All editor configuration (appearance, tools, features, AI, storage, etc.) | ||
callbacks | DragbleCallbacks (minus onReady/onLoad/onChange/onError) | undefined | SDK callbacks (linkClick, onModuleSave, onPreview, etc.) | ||
popup | PopupConfig | undefined | Popup configuration (when editorMode is "popup") | ||
collaboration | `boolean \ | CollaborationFeaturesConfig` | false | Enable collaboration/commenting | |
user | UserInfo | undefined | User identity for sessions and collaboration | ||
height | `string \ | number` | undefined | Editor height (CSS value or pixels) | |
minHeight | `string \ | number` | "600px" | Minimum height | |
className | string | undefined | CSS class on the outer wrapper | ||
style | React.CSSProperties | undefined | Inline styles on the outer wrapper | ||
onReady | (editor: DragbleSDK) => void | undefined | Fires once when editor is fully loaded | ||
onLoad | () => void | undefined | Fires when a design is loaded | ||
onChange | (data: { design: DesignJson; type: string }) => void | undefined | Fires on every design change | ||
onError | (error: Error) => void | undefined | Fires on SDK/loading errors | ||
onComment | (action: CommentAction) => void | undefined | Fires on comment create/edit/delete/resolve/reopen | ||
sdkUrl | string | CDN latest | Custom SDK script URL (overrides sdkVersion) | ||
sdkVersion | string | "latest" | SDK version channel: "latest", "stable", or "vX.Y.Z" | ||
editorVersion | string | undefined | Editor version forwarded to SDK init config | ||
editorUrl | string | undefined | Editor URL override (for self-hosted) |
DragbleEditorRef)const editorRef = useRef<DragbleEditorRef>(null);
// Access the SDK instance
const editor = editorRef.current?.editor;
// Check if the editor is ready
const ready = editorRef.current?.isReady();The editor property exposes the full DragbleSDK interface. All methods are available after isReady() returns true.
useDragbleEditorimport { useDragbleEditor } from "dragble-react-editor";
function MyComponent() {
const { ref, editor, isReady } = useDragbleEditor();
return (
<DragbleEditor ref={ref} editorKey="ek_..." />
);
}npm install dragble-vue-editorTypes are included automatically via re-export from dragble-types.
<template>
<div>
<div style="display: flex; gap: 8px; margin-bottom: 8px">
<button @click="handleSave">Save</button>
<button @click="handleLoadBlank">Load Blank</button>
</div>
<DragbleEditor
ref="editorRef"
editor-key="ek_your_editor_key"
editor-mode="email"
height="800px"
@ready="onReady"
@change="onChange"
@error="onError"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { DragbleEditor, type DragbleSDK, type DesignJson } from "dragble-vue-editor";
const editorRef = ref<InstanceType<typeof DragbleEditor>>();
const onReady = (editor: DragbleSDK) => {
console.log("Editor ready");
};
const onChange = (data: { design: DesignJson; type: string }) => {
console.log("Changed:", data.type);
};
const onError = (error: Error) => {
console.error("Editor error:", error);
};
const handleSave = async () => {
const html = await editorRef.value?.exportHtml();
const json = await editorRef.value?.exportJson();
console.log("Exported:", { html, json });
};
const handleLoadBlank = () => {
editorRef.value?.loadBlank();
};
</script>Vue props use kebab-case in templates. All the same configuration options as React apply:
| Prop (kebab-case) | Type | Default | Description | ||
|---|---|---|---|---|---|
editor-key | string | required | Authentication key | ||
editor-mode | `"email" \ | "web" \ | "popup"` | "email" | Editor type |
design | `DesignJson \ | ModuleData \ | null` | undefined | Initial design |
design-mode | `"edit" \ | "live"` | undefined | Template permission mode | |
content-type | "module" | undefined | Single-row module editing | ||
options | EditorOptions | undefined | All editor configuration | ||
callbacks | DragbleCallbacks (minus lifecycle) | undefined | SDK callbacks | ||
popup | PopupConfig | undefined | Popup config | ||
collaboration | `boolean \ | CollaborationFeaturesConfig` | undefined | Collaboration features | |
user | UserInfo | undefined | User identity | ||
height | `string \ | number` | "600px" | Editor height | |
min-height | `string \ | number` | "600px" | Minimum height | |
merge-tags | `(MergeTag \ | MergeTagGroup)[]` | undefined | Personalization tags | |
modules | Module[] | undefined | Custom modules | ||
display-conditions | DisplayConditionsConfig | undefined | Display conditions | ||
appearance | AppearanceConfig | undefined | Visual customization | ||
tools | ToolsConfig | undefined | Tool enable/disable | ||
features | FeaturesConfig | undefined | Feature toggles | ||
fonts | FontsConfig | undefined | Custom fonts | ||
locale | string | undefined | UI language locale | ||
text-direction | `"ltr" \ | "rtl"` | undefined | Text direction | |
body-values | Record<string, unknown> | undefined | Default body/canvas values | ||
header | object | undefined | Locked header row JSON | ||
footer | object | undefined | Locked footer row JSON | ||
ai | AIConfig | undefined | AI features configuration | ||
custom-css | string[] | undefined | Custom CSS URLs/inline | ||
custom-js | string[] | undefined | Custom JS URLs/inline | ||
sdk-url | string | CDN latest | Custom SDK script URL | ||
sdk-version | string | "latest" | SDK version channel | ||
editor-version | string | undefined | Editor version | ||
editor-url | string | undefined | Editor URL override |
Vue also exposes many props as top-level (e.g., merge-tags, appearance, tools, fonts) in addition to the options prop. Top-level props are merged into options internally. Either approach works; do not set the same option in both places.
| Event | Payload | Description |
|---|---|---|
ready | DragbleSDK | Editor is fully loaded |
load | unknown | Design was loaded |
change | { design: DesignJson; type: string } | Design changed |
error | Error | Error occurred |
comment | CommentAction | Comment event |
The Vue component automatically watches for changes to these props and updates the editor at runtime without re-initialization:
design -- calls loadDesign() when changedmerge-tags -- calls setMergeTags() when changedmodules -- calls setModules() when changeddisplay-conditions -- calls setDisplayConditions() when changedAll SDK methods are exposed via expose() on the component ref:
<script setup lang="ts">
const editorRef = ref<InstanceType<typeof DragbleEditor>>();
// After ready:
await editorRef.value?.exportHtml();
await editorRef.value?.exportJson();
await editorRef.value?.exportImage();
await editorRef.value?.exportPdf();
editorRef.value?.loadDesign(design);
editorRef.value?.loadBlank();
editorRef.value?.undo();
editorRef.value?.redo();
editorRef.value?.showPreview("desktop");
editorRef.value?.setMergeTags({ customMergeTags: [...] });
editorRef.value?.setModules([...]);
editorRef.value?.addEventListener("row:selected", (data) => { ... });
// ... all DragbleSDK methods are available
</script>useDragbleEditorFor direct SDK access without the component:
<script setup lang="ts">
import { useDragbleEditor } from "dragble-vue-editor";
const { editor, isReady, containerId } = useDragbleEditor({
editorKey: "ek_your_editor_key",
editorMode: "email",
});
const handleExport = async () => {
if (isReady.value && editor.value) {
const html = await editor.value.exportHtml();
console.log(html);
}
};
</script>
<template>
<div :id="containerId" style="height: 600px" />
<button @click="handleExport" :disabled="!isReady">Export</button>
</template>npm install dragble-angular-editorTypes are included automatically via re-export from dragble-types.
import { Component, ViewChild } from "@angular/core";
import { DragbleEditorComponent } from "dragble-angular-editor";
@Component({
selector: "app-email-editor",
standalone: true,
imports: [DragbleEditorComponent],
template: `
<div>
<div style="display: flex; gap: 8px; margin-bottom: 8px">
<button (click)="handleSave()">Save</button>
<button (click)="handleLoadBlank()">Load Blank</button>
</div>
<dragble-editor
#editor
editorKey="ek_your_editor_key"
editorMode="email"
height="800px"
(ready)="onReady($event)"
(change)="onChange($event)"
(error)="onError($event)"
></dragble-editor>
</div>
`,
})
export class EmailEditorComponent {
@ViewChild("editor") editor!: DragbleEditorComponent;
onReady(sdk: any) {
console.log("Editor ready");
}
onChange(data: { design: any; type: string }) {
console.log("Changed:", data.type);
}
onError(error: Error) {
console.error("Editor error:", error);
}
async handleSave() {
const html = await this.editor.exportHtml();
const json = await this.editor.exportJson();
console.log("Exported:", { html, json });
}
handleLoadBlank() {
this.editor.loadBlank();
}
}For Angular apps not using standalone components:
import { NgModule } from "@angular/core";
import { DragbleEditorModule } from "dragble-angular-editor";
@NgModule({
imports: [DragbleEditorModule],
})
export class AppModule {}Then use <dragble-editor> in your templates the same way.
| Input | Type | Default | Description | ||
|---|---|---|---|---|---|
editorKey | string | required | Authentication key | ||
editorMode | EditorMode | "email" | Editor type | ||
design | `DesignJson \ | ModuleData \ | null` | undefined | Initial design |
designMode | `"edit" \ | "live"` | undefined | Template permission mode | |
contentType | "module" | undefined | Single-row module editing | ||
options | Partial<EditorOptions> | undefined | All editor configuration | ||
callbacks | DragbleCallbacks (minus lifecycle) | undefined | SDK callbacks | ||
popup | PopupConfig | undefined | Popup config | ||
collaboration | `boolean \ | CollaborationFeaturesConfig` | undefined | Collaboration features | |
user | UserInfo | undefined | User identity | ||
height | `string \ | number` | "600px" | Editor height | |
minHeight | `string \ | number` | "600px" | Minimum height | |
appearance | AppearanceConfig | undefined | Visual customization | ||
tools | ToolsConfig | undefined | Tool enable/disable | ||
features | FeaturesConfig | undefined | Feature toggles | ||
fonts | FontsConfig | undefined | Custom fonts | ||
mergeTags | MergeTagsConfig | undefined | Personalization tags | ||
specialLinks | SpecialLinksConfig | undefined | Special link categories | ||
modules | Module[] | undefined | Custom modules | ||
displayConditions | DisplayConditionsConfig | undefined | Display conditions | ||
locale | string | undefined | UI language locale | ||
textDirection | TextDirection | undefined | Text direction | ||
language | Language | undefined | Multi-language config | ||
bodyValues | Record<string, unknown> | undefined | Default body values | ||
header | unknown | undefined | Locked header row JSON | ||
footer | unknown | undefined | Locked footer row JSON | ||
ai | AIConfig | undefined | AI features configuration | ||
customCSS | string[] | undefined | Custom CSS | ||
customJS | string[] | undefined | Custom JS | ||
sdkUrl | string | CDN latest | Custom SDK URL | ||
sdkVersion | string | "latest" | SDK version channel | ||
editorVersion | string | undefined | Editor version | ||
editorUrl | string | undefined | Editor URL override |
| Output | Payload | Description |
|---|---|---|
ready | DragbleSDK | Editor is fully loaded |
load | unknown | Design was loaded |
change | { design: DesignJson; type: string } | Design changed |
error | Error | Error occurred |
commentAction | CommentAction | Comment event |
The Angular component implements OnChanges. When these inputs change after initialization, the editor updates automatically:
design -- calls loadDesign()mergeTags -- calls setMergeTags()modules -- calls setModules()displayConditions -- calls setDisplayConditions()All SDK methods are exposed as public methods on the component:
@ViewChild("editor") editor!: DragbleEditorComponent;
// Design
this.editor.loadDesign(design);
this.editor.loadBlank();
this.editor.saveDesign((design) => { ... });
await this.editor.getDesign();
// Export
await this.editor.exportHtml();
await this.editor.exportJson();
await this.editor.exportPlainText();
await this.editor.exportImage();
await this.editor.exportPdf();
await this.editor.exportZip();
// Configuration
this.editor.setMergeTags(config);
this.editor.setModules(modules);
this.editor.setFonts(config);
this.editor.setBodyValues(values);
this.editor.setAppearance(config);
this.editor.setLocale("fr-FR");
this.editor.setTextDirection("rtl");
// Actions
this.editor.undo();
this.editor.redo();
this.editor.save();
this.editor.showPreview("desktop");
this.editor.hidePreview();
// Raw SDK instance
const sdk = this.editor.getEditor();
// Events
const unsub = this.editor.addEventListener("row:selected", (data) => { ... });
unsub(); // unsubscribeThe SDK is loaded from CDN. There is no npm package.
<!DOCTYPE html>
<html>
<head>
<title>Dragble Editor</title>
<script src="https://sdk.dragble.com/latest/dragble-sdk.min.js"></script>
</head>
<body>
<div id="editor-container" style="height: 800px;"></div>
<script>
// Global singleton
dragble.init({
containerId: "editor-container",
editorKey: "ek_your_editor_key",
editorMode: "email",
});
dragble.addEventListener("editor:ready", () => {
console.log("Editor ready");
});
// Save
document.getElementById("save-btn")?.addEventListener("click", async () => {
const html = await dragble.exportHtml();
const json = await dragble.exportJson();
console.log("Exported:", { html, json });
});
</script>
</body>
</html>createEditor()The global dragble object is a singleton. For multiple editors on the same page, use createEditor():
<div id="editor-1" style="height: 600px;"></div>
<div id="editor-2" style="height: 600px;"></div>
<script src="https://sdk.dragble.com/latest/dragble-sdk.min.js"></script>
<script>
const editor1 = createEditor({
containerId: "editor-1",
editorKey: "ek_key_1",
editorMode: "email",
});
const editor2 = createEditor({
containerId: "editor-2",
editorKey: "ek_key_2",
editorMode: "web",
});
editor1.addEventListener("editor:ready", () => {
console.log("Editor 1 ready");
});
editor2.addEventListener("editor:ready", () => {
console.log("Editor 2 ready");
});
</script>Important: Do NOT use dragble.init() for multiple instances. Each createEditor() call returns an independent SDK instance with its own lifecycle.
dragble.init({
containerId: "editor-container",
editorKey: "ek_your_editor_key",
design: savedDesignJson, // Pass design JSON at init time
});
// Or load after init:
dragble.addEventListener("editor:ready", () => {
dragble.loadDesign(savedDesignJson);
});// Destroy the editor instance and clean up resources
dragble.destroy();Types are automatically available. All wrappers re-export the full type surface from dragble-types:
import type {
DragbleSDK,
DragbleConfig,
DragbleCallbacks,
DesignJson,
EditorOptions,
EditorMode,
MergeTag,
MergeTagGroup,
Module,
PopupConfig,
ExportHtmlOptions,
ExportImageData,
ExportPdfData,
ExportZipData,
CollaborationFeaturesConfig,
UserInfo,
CommentAction,
// ... all types available
} from "dragble-react-editor"; // or "dragble-vue-editor" or "dragble-angular-editor"Install the types package as a dev dependency:
npm install --save-dev dragble-typesThen declare the global SDK:
import type { DragbleSDK } from "dragble-types";
declare global {
const dragble: DragbleSDK;
function createEditor(config: import("dragble-types").DragbleConfig): DragbleSDK;
}
// Now fully typed
dragble.init({
containerId: "editor",
editorKey: "ek_your_key",
});
const html: string = await dragble.exportHtml();https://sdk.dragble.com/{sdkVersion}/dragble-sdk.min.js| Channel | Example | Description |
|---|---|---|
latest | https://sdk.dragble.com/latest/dragble-sdk.min.js | Latest release (default) |
stable | https://sdk.dragble.com/stable/dragble-sdk.min.js | Stable release |
vX.Y.Z | https://sdk.dragble.com/v1.2.3/dragble-sdk.min.js | Pinned version (note: v prefix required) |
All framework wrappers accept four versioning props:
| Prop | Controls | Default | Description |
|---|---|---|---|
sdkVersion | SDK script version | "latest" | Which SDK build to load from CDN |
sdkUrl | SDK script URL | Derived from sdkVersion | Full URL override for the SDK script |
editorVersion | Editor iframe version | undefined | Which editor build the SDK loads |
editorUrl | Editor iframe URL | undefined | Full URL override for the editor |
sdkUrl overrides sdkVersion. If both are set, sdkVersion is ignored and a console warning is emitted.editorUrl overrides editorVersion. Same behavior.// React: Pin SDK to v1.2.3, editor to stable
<DragbleEditor
editorKey="ek_..."
sdkVersion="v1.2.3"
editorVersion="stable"
/>
// React: Use a self-hosted SDK
<DragbleEditor
editorKey="ek_..."
sdkUrl="https://cdn.example.com/dragble-sdk.min.js"
editorUrl="https://editor.example.com"
/><!-- Vue -->
<DragbleEditor
editor-key="ek_..."
sdk-version="v1.2.3"
editor-version="stable"
/><!-- Vanilla: specific version -->
<script src="https://sdk.dragble.com/v1.2.3/dragble-sdk.min.js"></script>Register event listeners with addEventListener. It returns an unsubscribe function.
const unsubscribe = editor.addEventListener("design:updated", (data) => {
console.log("Design updated:", data);
});
// Later: stop listening
unsubscribe();
// Or use removeEventListener with the same callback reference
editor.removeEventListener("design:updated", callback);| Event Name | Data Type | Description |
|---|---|---|
editor:ready | void | Editor fully loaded and ready for interaction |
design:loaded | { design: DesignJson } | A design was loaded into the editor |
design:updated | { design: DesignJson; type: string } | The design changed (any edit) |
design:saved | { design: DesignJson } | Design was saved (via save()) |
row:selected | { row: RowData } | A row was selected |
row:unselected | void | Row selection cleared |
column:selected | { column: ColumnData } | A column was selected |
column:unselected | void | Column selection cleared |
content:selected | { content: ContentData } | A content block was selected |
content:unselected | void | Content selection cleared |
content:modified | { content: ContentData; changes: object } | A content block was edited |
content:added | { content: ContentData } | A content block was added |
content:deleted | { contentId: string } | A content block was deleted |
preview:shown | { device: ViewMode } | Preview mode opened |
preview:hidden | void | Preview mode closed |
image:uploaded | { url: string } | Image upload completed |
image:error | { error: string } | Image upload failed |
export:html | { html: string } | HTML export completed |
export:plainText | { text: string } | Plain text export completed |
export:image | ExportImageData | Image export completed |
export | { type: string; data: unknown } | Generic export event |
save | void | Save triggered |
save:success | { design: DesignJson } | Save completed successfully |
save:error | { error: string } | Save failed |
element:selected | { type: string; id: string } | Any element selected (generic) |
element:deselected | void | Any element deselected (generic) |
template:requested | void | User requested a template |
displayCondition:applied | { contentId: string; condition: object } | Display condition was set |
displayCondition:removed | { contentId: string } | Display condition was removed |
displayCondition:updated | { contentId: string; condition: object } | Display condition was changed |
Callbacks are passed via the callbacks prop (framework wrappers) or the callbacks key in the init config (vanilla JS). They are divided into non-blocking and blocking.
These fire-and-forget. Return value is ignored.
| Callback | Signature | Description |
|---|---|---|
onReady | () => void | Editor is fully loaded. In wrappers, use the dedicated onReady prop / ready emit instead. |
onLoad | (data: DesignData) => void | Design was loaded. In wrappers, use the onLoad prop / load emit. |
onChange | (data: DesignData) => void | Design changed. In wrappers, use the onChange prop / change emit. |
onError | (error: EditorError) => void | Error occurred. In wrappers, use the onError prop / error emit. |
linkClick | (data: LinkClickData) => void | User clicked a link in the editor canvas. |
onHeaderRowClick | (data: { rowId: string }) => void | User clicked the locked header row. |
onFooterRowClick | (data: { rowId: string }) => void | User clicked the locked footer row. |
onLockedRowClick | (data: { rowId: string }) => void | User clicked any other locked row. |
The editor waits for the Promise to resolve before continuing.
| Callback | Signature | Description |
|---|---|---|
onModuleSave | (data: ModuleSaveData) => Promise<ModuleSaveResult> | User saves a row as a module. Return { success: true, moduleId? } or { success: false, error? }. |
onPreview | (html: string) => Promise<string> | Preview opened. Receives raw HTML, must return (possibly transformed) HTML to display. |
onContentDialog | (info: ContentDialogInfo) => Promise<ContentDialogResult> | Custom tool with openOnDrop requests a dialog. Return { values } or { cancelled: true }. |
<DragbleEditor
editorKey="ek_..."
onReady={(editor) => console.log("Ready")}
onChange={(data) => console.log("Changed:", data.type)}
callbacks={{
linkClick: (data) => window.open(data.url, "_blank"),
onModuleSave: async (data) => {
const result = await fetch("/api/modules", {
method: "POST",
body: JSON.stringify(data),
});
const { id } = await result.json();
return { success: true, moduleId: id };
},
onPreview: async (html) => {
// Replace merge tags with sample data before preview
return html.replace(/\{\{first_name\}\}/g, "Jane");
},
}}
/>dragble.init({
containerId: "editor-container",
editorKey: "ek_your_editor_key",
callbacks: {
onReady: () => console.log("Ready"),
onChange: (data) => console.log("Changed"),
linkClick: (data) => window.open(data.url, "_blank"),
onModuleSave: async (data) => {
const res = await fetch("/api/modules", {
method: "POST",
body: JSON.stringify(data),
});
const { id } = await res.json();
return { success: true, moduleId: id };
},
onPreview: async (html) => {
return html.replace(/\{\{first_name\}\}/g, "Jane");
},
},
});The editor renders inside an iframe. If the container has height: 0, display: none, or is inside a collapsed accordion, the editor will appear blank. Always ensure the container has a visible, non-zero height.
// WRONG: no height
<DragbleEditor editorKey="ek_..." />
// RIGHT: explicit height
<DragbleEditor editorKey="ek_..." height="800px" />SDK methods throw or silently fail if called before editor:ready. Always wait for the ready event.
// WRONG
dragble.init({ ... });
dragble.exportHtml(); // Editor not ready yet!
// RIGHT
dragble.addEventListener("editor:ready", async () => {
const html = await dragble.exportHtml();
});HTML export is for sending/rendering. To let users edit their design later, you must also save the design JSON.
// WRONG: only save HTML
const html = await editor.exportHtml();
saveToDB(html);
// RIGHT: save both
const html = await editor.exportHtml();
const json = await editor.exportJson();
saveToDB({ html, json });
// Later: restore
editor.loadDesign(savedJson);dragble.init() for multiple editorsThe global dragble is a singleton. Calling init() twice replaces the first editor. Use createEditor() for multiple instances.
// WRONG
dragble.init({ containerId: "editor-1", ... });
dragble.init({ containerId: "editor-2", ... }); // Replaces editor-1!
// RIGHT
const editor1 = createEditor({ containerId: "editor-1", ... });
const editor2 = createEditor({ containerId: "editor-2", ... });Framework wrappers handle this automatically -- each <DragbleEditor> component creates its own instance via createEditor().
Dragble uses containerId + editorKey. Do NOT use id, projectId, or apiKey -- those are from other editors.
// WRONG (Unlayer-style)
dragble.init({ id: "editor", projectId: 12345 });
// RIGHT
dragble.init({ containerId: "editor", editorKey: "ek_your_key" });npm install @dragble/editor-sdkThe SDK is CDN-only. There is no npm package for the SDK itself. Framework wrappers load it from CDN automatically.
In SPAs, always destroy the editor when the component unmounts. Framework wrappers handle this automatically, but in vanilla JS you must call destroy() manually.
// On route change or cleanup
editor.destroy();init() or the component mounts.height: 800px, not height: 0).editorKey is valid and not expired.editor:ready never firessdk.dragble.com.editorKey format is correct (ek_* or px_*).sdk.dragble.com or the editor domain.sdkUrl or editorUrl, verify the URLs are reachable and serve the correct files.The ref is populated after the component mounts and the SDK loads. Use onReady instead of checking the ref immediately:
// WRONG
useEffect(() => {
editorRef.current?.editor?.exportHtml(); // ref not ready yet
}, []);
// RIGHT
<DragbleEditor
ref={editorRef}
onReady={(editor) => {
// Safe to use editor here
}}
/>DesignJson object (not a string -- parse it first if stored as a string).design as a prop, it loads at init time. If calling loadDesign() manually, wait for editor:ready.sdk.dragble.com.sdkUrl, verify the URL returns a valid JavaScript file.In React, the editor re-initializes when editorKey or containerId changes. Ensure these are stable values:
// WRONG: new key on every render
<DragbleEditor editorKey={`ek_${Date.now()}`} />
// RIGHT: stable key
<DragbleEditor editorKey="ek_your_stable_key" />Callback props (onReady, onChange, etc.) are stored in refs internally and do NOT trigger re-initialization when they change.
Each editor instance must have a unique container element. Framework wrappers auto-generate unique container IDs. In vanilla JS, ensure each containerId is unique and use createEditor() (not dragble.init()).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.