Dragble export
Export content from the Dragble editor as HTML, JSON, plain text, image, PDF, or ZIP. Save and load designs, auto-save patterns, design JSON structure, and template validation.From its SKILL.md
npx -y skills add Dragble/dragble-skills --skill dragble-exportAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
15.3 KB, ~3.8k tokens by cl100k_base, as published. Nobody here has run it
Dragble Export & Design Management
Export content from the Dragble editor in 6 formats. Save, load, validate, and manipulate designs programmatically.
Overview
The Dragble editor supports 6 export formats split into two tiers:
| Tier | Formats | Rendering | Plan |
|---|---|---|---|
| Client-side | HTML, JSON, Plain Text | Browser-based | Free |
| Server-side | Image, PDF, ZIP | Chromium headless | Paid |
Critical rule: Always save BOTH design JSON and HTML together. Design JSON is the editable source of truth; HTML is the rendered output. If you only save HTML, the design cannot be reloaded for editing.
Export Methods
All methods return Promises. There are no callback overloads.
exportHtml
Exports the current design as rendered HTML.
const html: string = await editor.exportHtml({
title: "My Campaign", // optional: sets <title> in the HTML
minify: true // optional: minifies the output HTML
});
Signature:
exportHtml(options?: { title?: string; minify?: boolean }): Promise<string>
exportJson
Exports the current design as a JSON object representing the full design tree.
const designJson: DesignJson = await editor.exportJson();
// Store the JSON for later reloading
await fetch("/api/designs/123", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(designJson),
});
Signature:
exportJson(): Promise<DesignJson>
exportPlainText
Exports the visible text content of the design, stripped of all HTML tags and formatting.
const text: string = await editor.exportPlainText();
console.log(text);
// "Hello World\nClick here to visit our site\n..."
Signature:
exportPlainText(): Promise<string>
exportImage
Renders the design as an image via server-side Chromium. Requires a paid plan. Does NOT work in popup mode.
const imageData: ExportImageData = await editor.exportImage({
format: "png", // "png" | "jpeg" | "webp"
width: 600, // pixel width of the output
height: 0, // 0 = auto-height based on content
quality: 90, // 1-100, applies to jpeg/webp
deviceScaleFactor: 2, // retina scaling (1 | 2 | 3)
saveToStorage: true, // upload result to project storage
mergeTags: { // resolve merge tags before rendering
first_name: "Jane",
company: "Acme Inc."
}
});
// imageData.url - public URL (if saveToStorage: true)
// imageData.data - base64-encoded image data
Signature:
exportImage(options?: {
format?: "png" | "jpeg" | "webp";
width?: number;
height?: number;
quality?: number;
deviceScaleFactor?: number;
saveToStorage?: boolean;
mergeTags?: Record<string, string>;
}): Promise<ExportImageData>
Return type:
interface ExportImageData {
url?: string; // present when saveToStorage is true
data: string; // base64-encoded image
}
exportPdf
Renders the design as a PDF via server-side Chromium. Requires a paid plan. Does NOT work in popup mode.
const pdfData: ExportPdfData = await editor.exportPdf({
pageSize: "A4", // "A4" | "Letter" | "Legal" | custom
orientation: "portrait", // "portrait" | "landscape"
printBackground: true, // include background colors/images
margin: { // page margins
top: "10mm",
right: "10mm",
bottom: "10mm",
left: "10mm"
},
width: 600, // content width in pixels
saveToStorage: true, // upload result to project storage
mergeTags: {
first_name: "Jane"
}
});
// pdfData.url - public URL (if saveToStorage: true)
// pdfData.data - base64-encoded PDF
Signature:
exportPdf(options?: {
pageSize?: string;
orientation?: "portrait" | "landscape";
printBackground?: boolean;
margin?: { top?: string; right?: string; bottom?: string; left?: string };
width?: number;
saveToStorage?: boolean;
mergeTags?: Record<string, string>;
}): Promise<ExportPdfData>
Return type:
interface ExportPdfData {
url?: string; // present when saveToStorage is true
data: string; // base64-encoded PDF
}
exportZip
Bundles the design as a ZIP archive containing the HTML file and all referenced images. Requires a paid plan. Does NOT work in popup mode.
const zipData: ExportZipData = await editor.exportZip({
imageFolder: "images" // folder name for images inside the ZIP
});
// zipData.url - public URL (if saved to storage)
// zipData.data - base64-encoded ZIP
Signature:
exportZip(options?: {
imageFolder?: string;
}): Promise<ExportZipData>
Return type:
interface ExportZipData {
url?: string;
data: string; // base64-encoded ZIP
}
Save & Load
Save Pattern
Always save both the design JSON (for re-editing) and the rendered HTML (for sending/displaying):
async function saveDesign(editor: DragbleEditor, designId: string) {
// Export both in parallel
const [html, designJson] = await Promise.all([
editor.exportHtml({ minify: true }),
editor.exportJson(),
]);
// Persist to your backend
await fetch(`/api/designs/${designId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
html,
designJson,
updatedAt: new Date().toISOString(),
}),
});
}
Load Pattern
Listen for the editor ready event, then load the design:
editor.addEventListener("editor:ready", () => {
// Fetch the saved design JSON from your backend
fetch(`/api/designs/${designId}`)
.then((res) => res.json())
.then((data) => {
editor.loadDesign(data.designJson);
});
});
loadDesign
Replaces the current design with the provided JSON. Does not validate the input.
editor.loadDesign(designJson);
Signature:
loadDesign(design: DesignJson): void
loadDesignAsync
Replaces the current design and resolves once the editor has finished rendering. Validates the design structure before loading.
try {
await editor.loadDesignAsync(designJson);
console.log("Design loaded and rendered");
} catch (err) {
console.error("Invalid design JSON:", err);
}
Signature:
loadDesignAsync(design: DesignJson): Promise<void>
loadBlank
Resets the editor to an empty design.
editor.loadBlank();
Signature:
loadBlank(): void
getDesign
Returns the current design JSON synchronously from the editor's in-memory state.
const currentDesign: DesignJson = editor.getDesign();
Signature:
getDesign(): DesignJson
Auto-Save
Use a debounced listener on the design:updated event to auto-save without flooding your backend. A 2-second debounce is recommended.
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
editor.addEventListener("design:updated", () => {
// Clear any pending save
if (saveTimeout) {
clearTimeout(saveTimeout);
}
// Debounce: wait 2 seconds of inactivity before saving
saveTimeout = setTimeout(async () => {
try {
const [html, designJson] = await Promise.all([
editor.exportHtml({ minify: true }),
editor.exportJson(),
]);
await fetch(`/api/designs/${designId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ html, designJson }),
});
console.log("Auto-saved successfully");
} catch (err) {
console.error("Auto-save failed:", err);
}
}, 2000);
});
Design JSON Structure
The design JSON follows a strict tree hierarchy:
DesignJson
├── body: BodyValues
│ ├── backgroundColor
│ ├── contentWidth
│ ├── fontFamily
│ ├── preheaderText
│ └── ...global styles
├── rows: RowValues[]
│ ├── columns: ColumnValues[]
│ │ ├── contents: ContentData[]
│ │ │ ├── type: string
│ │ │ └── values: { ...per-type properties }
│ │ └── ...column styles
│ └── ...row styles
└── counters: Record<string, number>
Content Types
| Type | Description |
|---|---|
paragraph | Rich text block |
heading | Heading text (h1-h6) |
button | Call-to-action button |
image | Image with optional link |
divider | Horizontal rule / separator |
menu | Navigation link list |
html | Raw HTML block |
social | Social media icon links |
video | Embedded video player |
table | Data table |
timer | Countdown timer |
form | Input form |
spacer | Vertical spacing block |
Column Layouts
The columns array length and relative weight values define the layout:
| columns | Layout |
|---|---|
[1] | 100% (full width) |
[1, 1] | 50% / 50% |
[1, 2] | 33.3% / 66.7% |
[2, 1] | 66.7% / 33.3% |
[1, 1, 1] | 33.3% / 33.3% / 33.3% |
[1, 2, 1] | 25% / 50% / 25% |
[1, 1, 1, 1] | 25% / 25% / 25% / 25% |
Minimal Valid Design JSON
{
"body": {
"rows": [
{
"columns": [
{
"contents": [
{
"type": "paragraph",
"values": {
"text": "<p>Hello World</p>"
}
}
]
}
],
"values": {}
}
],
"values": {
"backgroundColor": "#ffffff",
"contentWidth": "600px",
"fontFamily": {
"label": "Arial",
"value": "arial,helvetica,sans-serif"
}
}
},
"counters": {}
}
Validation & Audit
setValidator
Registers a global design validator that runs before export operations.
editor.setValidator((design: DesignJson) => {
const errors: string[] = [];
if (!design.body.values.preheaderText) {
errors.push("Preheader text is required");
}
return { valid: errors.length === 0, errors };
});
Signature:
setValidator(
validator: (design: DesignJson) => { valid: boolean; errors: string[] }
): void
setToolValidator
Registers a validator for a specific content type (tool). Runs when that tool's content is edited.
editor.setToolValidator("image", (values) => {
const errors: string[] = [];
if (!values.src || !values.alt) {
errors.push("Images must have alt text");
}
return { valid: errors.length === 0, errors };
});
Signature:
setToolValidator(
toolName: string,
validator: (values: Record<string, unknown>) => { valid: boolean; errors: string[] }
): void
clearValidators
Removes all registered validators (both global and tool-specific).
editor.clearValidators();
Signature:
clearValidators(): void
audit
Runs all registered validators against the current design and returns the results.
const result: AuditResult = await editor.audit();
if (result.status === "fail") {
console.error("Audit failed:", result.errors);
// result.errors: Array<{ tool?: string; message: string }>
}
Signature:
audit(options?: { silent?: boolean }): Promise<AuditResult>
Return type:
interface AuditResult {
status: "pass" | "fail";
errors: Array<{ tool?: string; message: string }>;
}
validateTemplate
Validates a design JSON structure without loading it into the editor.
const result = await editor.validateTemplate(designJson);
if (!result.valid) {
console.error("Template validation errors:", result.errors);
}
Signature:
validateTemplate(
design: DesignJson
): Promise<{ valid: boolean; errors: string[] }>
validateTool
Validates values for a specific content type without loading them.
const result = await editor.validateTool("button", {
text: "Click Me",
href: ""
});
// result.valid === false, result.errors === ["Button must have a link URL"]
Signature:
validateTool(
toolName: string,
values: Record<string, unknown>
): Promise<{ valid: boolean; errors: string[] }>
Element Manipulation
selectElement
Selects an element in the editor by its unique ID, opening its property panel.
editor.selectElement("u_content_heading_1");
Signature:
selectElement(elementId: string): void
highlightElement
Highlights an element in the editor with a visual indicator. Pass null to clear the highlight.
// Highlight an element
editor.highlightElement("u_content_image_3");
// Clear highlight
editor.highlightElement(null);
Signature:
highlightElement(elementId: string | null): void
scrollToElement
Scrolls the editor viewport to bring the specified element into view.
editor.scrollToElement("u_row_5");
Signature:
scrollToElement(elementId: string): void
execCommand
Executes an editor command on the currently selected element.
editor.execCommand("bold");
editor.execCommand("fontSize", "18px");
editor.execCommand("foreColor", "#ff0000");
Signature:
execCommand(command: string, value?: string): void
Common Mistakes & Troubleshooting
Saving only HTML, not design JSON
Wrong:
const html = await editor.exportHtml();
await saveToBackend({ html });
// Design cannot be reloaded for editing!
Correct:
const [html, designJson] = await Promise.all([
editor.exportHtml(),
editor.exportJson(),
]);
await saveToBackend({ html, designJson });
Calling exportImage/exportPdf/exportZip on a free plan
These methods require a paid plan and server-side Chromium rendering. They will reject with an error on free plans:
try {
const image = await editor.exportImage({ format: "png" });
} catch (err) {
// "Export to image requires a paid plan"
}
Calling exportImage/exportPdf/exportZip in popup mode
Server-side export methods are not available when the editor is opened in popup mode. Use them only in the embedded (iframe) editor mode.
Loading a design before the editor is ready
Wrong:
const editor = new DragbleEditor("#container", config);
editor.loadDesign(designJson); // Editor not ready yet!
Correct:
const editor = new DragbleEditor("#container", config);
editor.addEventListener("editor:ready", () => {
editor.loadDesign(designJson);
});
Auto-save firing too frequently
Without debouncing, design:updated fires on every keystroke and property change. Always debounce with at least a 2-second delay (see the Auto-Save section above).
Invalid design JSON structure
The design JSON must follow the exact tree hierarchy: body.rows[].columns[].contents[]. Missing any level will cause loadDesign to fail silently or produce a broken layout. Use loadDesignAsync to catch validation errors, or call validateTemplate before loading.
Forgetting to handle export errors
All export methods return Promises that can reject. Always wrap them in try/catch:
try {
const html = await editor.exportHtml({ minify: true });
} catch (err) {
console.error("Export failed:", err);
}
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.