BuilderJS

Export HTML and Inline CSS from BuilderJS

This is the output side of the BuilderJS contract: how you get finished HTML out of the editor, and how you inline its CSS so the result survives email clients that strip stylesheets. BuilderJS is the embeddable editor only — it produces the markup, and your own app owns sending, hosting, and storage. The export surface is small and real: builder.getHtml() returns the rendered HTML with absolute media URLs, builder.getHtmlWithRelativeLinks() returns the same markup with the themeMediaUrl prefix stripped for portable storage, and the bundled export-inline.php reference handler promotes inlinable style rules onto each element. Everything below uses the verbatim API — no invented methods.

Mount it

// 1) Export from the editor (real API — verbatim).
  //    getHtml()                  → absolute media URLs (same-domain consumers)
  //    getHtmlWithRelativeLinks() → themeMediaUrl prefix stripped (portable storage)
  const portableHtml = builder.getHtmlWithRelativeLinks();
  const designJson   = builder.getData(); // JSON source of truth — load() restores it
  
  // 2) Inline CSS for email via the bundled reference handler.
  //    POST the rendered HTML to /backend/export-inline.php.
  //    For email use the ABSOLUTE flavour so clients fetch images from where they're hosted.
  async function inlineForEmail() {
    const body = new FormData();
    body.append('html', builder.getHtml());
    body.append('format', 'json'); // → { ok, html, inlinedDeclarations, inlinedRules, keptRules, styleBlocks }
  
    const res  = await fetch('/backend/export-inline.php', { method: 'POST', body });
    const data = await res.json();
  
    // data.html is the inlined markup; hand it to YOUR sending path.
    // BuilderJS does not send — you bring the ESP / SMTP.
    await fetch('/your-app/queue-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ html: data.html, source: designJson }),
    });
    return data; // { inlinedRules, keptRules, ... } for a live preview
  }
  
  // 3) Restore later — 4 positional args, in this order:
  // builder.load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl);

getHtml() vs getHtmlWithRelativeLinks() — pick by URL space

Two methods, two storage situations. builder.getHtml() returns the rendered canvas HTML with every media URL in absolute form (the themeMediaUrl prefix you passed as load()'s 4th argument is included). Use it when the consumer of the HTML lives on the same origin as the editor — preview iframes, same-domain renderers, server-side render pipelines. builder.getHtmlWithRelativeLinks() does exactly one substitution: it strips that themeMediaUrl prefix so asset paths come out relative (assets/avatar.png instead of https://app.example.com/.../assets/avatar.png). Use it when the saved HTML will be served from a different domain or moved between deployments. Both are pure reads — no side effects, no engine-state mutation — so you can call them anywhere a code path makes sense.

Why email needs inlined CSS, and where BuilderJS draws the line

A web page rendered in a browser is happy with a head stylesheet, so for page output the plain getHtml() markup is usually all you need. Email is different: several major clients discard head and external CSS before showing a message, leaving any non-inlined rule with nothing to apply. The fix is an inliner — a step that copies matching stylesheet rules onto each element's style attribute. BuilderJS keeps this as an explicit output step rather than baking it into getHtml(), so you control when it runs. For a deeper, concept-first explanation of why inboxes strip styles, see the companion guide on inline CSS for email; this page is the how-to for actually producing the inlined output.

Inline at export with the export-inline.php reference handler

The bundle ships a server-side companion to the editor's Export ▸ Inline CSS action: demo/backend/export-inline.php. POST your rendered HTML to it as form data under the html key, with an optional format of download (default — streams an .html attachment) or json (returns {ok, html, inlinedDeclarations, inlinedRules, keptRules, styleBlocks} for live previews). The heavy lifting lives in _lib/CssInliner.php, a zero-dependency DOMDocument-based inliner; the contract is plain string-in, string-out, so you can swap in pelago/emogrifier or tijsverkoyen/css-to-inline-styles by changing a single require line. Most buyers inline server-side, right before handing the HTML to their mail transport — the one place every send path passes through.

Choosing where to inline: client-side topnav vs server-side handler

BuilderJS gives you two entry points to the same inlining step. The editor's topnav Export ▸ Inline CSS action lets an end-user download an inlined file directly from the chrome — convenient for ad-hoc designs. The export-inline.php handler is the programmatic path for automated send pipelines: your backend reads the stored HTML, POSTs it for inlining, and forwards the result to your ESP or SMTP. Because both reduce to CssInliner::inline(string $html): array, the output is identical regardless of trigger. The handler deliberately lets a genuine inliner failure surface as a 500 rather than streaming a half-transformed file — a corrupt email-ready export is worse than a loud error.

What stays yours after export

Export is the boundary where BuilderJS hands control back to your application. getHtml() / getHtmlWithRelativeLinks() return strings; export-inline.php returns inlined strings; getData() returns the JSON source of truth. None of these send, host, store, or schedule anything — BuilderJS never phones home. You decide where the JSON lives (database, object storage, a file), how the HTML is delivered (your ESP, your SMTP, your CDN), and when to regenerate. Pair save with load: store relative HTML and JSON, then pass the new media base URL as load()'s 4th argument and the engine prepends it again on the next open. The round-trip is lossless and the source you self-host under a one-time license.

Frequently asked questions

Explore