# 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.

- Skill: `dragble/dragble-export` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dragble/dragble-export`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dragble/dragble-export/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: Dragble (https://skillmd.com/u/dragble)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dragble/dragble-export

---


# 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.

```ts
const html: string = await editor.exportHtml({
  title: "My Campaign",  // optional: sets <title> in the HTML
  minify: true            // optional: minifies the output HTML
});
```

**Signature:**

```ts
exportHtml(options?: { title?: string; minify?: boolean }): Promise<string>
```

---

### exportJson

Exports the current design as a JSON object representing the full design tree.

```ts
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:**

```ts
exportJson(): Promise<DesignJson>
```

---

### exportPlainText

Exports the visible text content of the design, stripped of all HTML tags and formatting.

```ts
const text: string = await editor.exportPlainText();
console.log(text);
// "Hello World\nClick here to visit our site\n..."
```

**Signature:**

```ts
exportPlainText(): Promise<string>
```

---

### exportImage

Renders the design as an image via server-side Chromium. **Requires a paid plan. Does NOT work in popup mode.**

```ts
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:**

```ts
exportImage(options?: {
  format?: "png" | "jpeg" | "webp";
  width?: number;
  height?: number;
  quality?: number;
  deviceScaleFactor?: number;
  saveToStorage?: boolean;
  mergeTags?: Record<string, string>;
}): Promise<ExportImageData>
```

**Return type:**

```ts
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.**

```ts
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:**

```ts
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:**

```ts
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.**

```ts
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:**

```ts
exportZip(options?: {
  imageFolder?: string;
}): Promise<ExportZipData>
```

**Return type:**

```ts
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):

```ts
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:

```ts
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.

```ts
editor.loadDesign(designJson);
```

**Signature:**

```ts
loadDesign(design: DesignJson): void
```

### loadDesignAsync

Replaces the current design and resolves once the editor has finished rendering. Validates the design structure before loading.

```ts
try {
  await editor.loadDesignAsync(designJson);
  console.log("Design loaded and rendered");
} catch (err) {
  console.error("Invalid design JSON:", err);
}
```

**Signature:**

```ts
loadDesignAsync(design: DesignJson): Promise<void>
```

### loadBlank

Resets the editor to an empty design.

```ts
editor.loadBlank();
```

**Signature:**

```ts
loadBlank(): void
```

### getDesign

Returns the current design JSON synchronously from the editor's in-memory state.

```ts
const currentDesign: DesignJson = editor.getDesign();
```

**Signature:**

```ts
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.

```ts
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

```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.

```ts
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:**

```ts
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.

```ts
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:**

```ts
setToolValidator(
  toolName: string,
  validator: (values: Record<string, unknown>) => { valid: boolean; errors: string[] }
): void
```

### clearValidators

Removes all registered validators (both global and tool-specific).

```ts
editor.clearValidators();
```

**Signature:**

```ts
clearValidators(): void
```

### audit

Runs all registered validators against the current design and returns the results.

```ts
const result: AuditResult = await editor.audit();

if (result.status === "fail") {
  console.error("Audit failed:", result.errors);
  // result.errors: Array<{ tool?: string; message: string }>
}
```

**Signature:**

```ts
audit(options?: { silent?: boolean }): Promise<AuditResult>
```

**Return type:**

```ts
interface AuditResult {
  status: "pass" | "fail";
  errors: Array<{ tool?: string; message: string }>;
}
```

### validateTemplate

Validates a design JSON structure without loading it into the editor.

```ts
const result = await editor.validateTemplate(designJson);
if (!result.valid) {
  console.error("Template validation errors:", result.errors);
}
```

**Signature:**

```ts
validateTemplate(
  design: DesignJson
): Promise<{ valid: boolean; errors: string[] }>
```

### validateTool

Validates values for a specific content type without loading them.

```ts
const result = await editor.validateTool("button", {
  text: "Click Me",
  href: ""
});
// result.valid === false, result.errors === ["Button must have a link URL"]
```

**Signature:**

```ts
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.

```ts
editor.selectElement("u_content_heading_1");
```

**Signature:**

```ts
selectElement(elementId: string): void
```

### highlightElement

Highlights an element in the editor with a visual indicator. Pass `null` to clear the highlight.

```ts
// Highlight an element
editor.highlightElement("u_content_image_3");

// Clear highlight
editor.highlightElement(null);
```

**Signature:**

```ts
highlightElement(elementId: string | null): void
```

### scrollToElement

Scrolls the editor viewport to bring the specified element into view.

```ts
editor.scrollToElement("u_row_5");
```

**Signature:**

```ts
scrollToElement(elementId: string): void
```

### execCommand

Executes an editor command on the currently selected element.

```ts
editor.execCommand("bold");
editor.execCommand("fontSize", "18px");
editor.execCommand("foreColor", "#ff0000");
```

**Signature:**

```ts
execCommand(command: string, value?: string): void
```

---

## Common Mistakes & Troubleshooting

### Saving only HTML, not design JSON

**Wrong:**
```ts
const html = await editor.exportHtml();
await saveToBackend({ html });
// Design cannot be reloaded for editing!
```

**Correct:**
```ts
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:

```ts
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:**
```ts
const editor = new DragbleEditor("#container", config);
editor.loadDesign(designJson); // Editor not ready yet!
```

**Correct:**
```ts
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:

```ts
try {
  const html = await editor.exportHtml({ minify: true });
} catch (err) {
  console.error("Export failed:", err);
}
```

