Skip to main content

Rendering API

Five render functions:

FunctionReturnsUse Case
renderToHtmlstring (combined HTML)Quick render — saving to disk, ad-hoc HTML
renderToHtmlParts{ head, body }Use this when sending email. Splits <style> block from body
renderToPlainTextstring (plain text)The text/plain MIME part for multipart emails (deliverability)
renderToJsonDesignJSONBootstrap the visual Builder with a code-authored design
renderRowToJsonDesignRowExport a single row for the Block Editor

All are pure synchronous functions: same tree in, same output out, no side effects.


renderToHtml(element, config?)

Takes an Elements tree, returns a clean HTML string. No React hydration markers; ready to ship.

import {
renderToHtml,
Email,
Row,
Column,
ColumnLayouts,
Paragraph,
Button,
} from '@unlayer/react-elements';

const html = renderToHtml(
<Email backgroundColor="#f4f4f4">
<Row layout={ColumnLayouts.OneColumn}>
<Column>
<Paragraph fontSize="14px">Hello World</Paragraph>
<Button backgroundColor="#3b82f6" color="#ffffff">
Click me
</Button>
</Column>
</Row>
</Email>,
);

Signature

function renderToHtml(
element: React.ReactElement,
config?: Partial<UnlayerConfig>,
): string;

The optional config overrides per-render config (cdnBaseUrl, mode, etc.). Throws with a descriptive error if rendering fails.


Returns head and body separately. The <style> block (hover effects, responsive breakpoints, font declarations) goes in <head>, where Gmail and most webmail clients actually keep style blocks. Dumping the whole document into <body> works for some clients and silently breaks others.

import {
renderToHtmlParts,
Email,
Row,
Column,
Button,
} from '@unlayer/react-elements';

const { head, body } = renderToHtmlParts(
<Email>
<Row>
<Column>
<Button>Click</Button>
</Column>
</Row>
</Email>,
);

const fullHtml = `<!DOCTYPE html>
<html>
<head>${head}</head>
${body}
</html>`;

Signature

function renderToHtmlParts(
element: React.ReactElement,
config?: Partial<UnlayerConfig>,
): HtmlParts;

interface HtmlParts {
head: string; // <style> blocks + optional <script>/<meta>
body: string; // same as renderToHtml output
}

renderToPlainText(element, config?)

Renders to plain text by first rendering to HTML, then stripping markup. Use it for the text/plain MIME part of a multipart email. Including a plain-text alternative meaningfully improves deliverability.

import {
renderToPlainText,
Email,
Row,
Column,
Paragraph,
} from '@unlayer/react-elements';

const text = renderToPlainText(
<Email>
<Row>
<Column>
<Paragraph>Hello World</Paragraph>
</Column>
</Row>
</Email>,
);
// "Hello World"

Signature

function renderToPlainText(
element: React.ReactElement,
config?: Partial<UnlayerConfig>,
): string;

Sending a multipart email

const { head, body } = renderToHtmlParts(<MyEmail />);
const text = renderToPlainText(<MyEmail />);

await mailer.send({
to: 'jane@example.com',
subject: 'Welcome',
html: `<!DOCTYPE html><html><head>${head}</head>${body}</html>`,
text,
});

renderToJson(element)

Renders the component tree to Unlayer-compatible design JSON — the same shape the visual Builders' saveDesign callback produces. The root must be <Body>, <Email>, <Page>, or <Document>.

import {
renderToJson,
Email,
Row,
Column,
ColumnLayouts,
Heading,
} from '@unlayer/react-elements';

const design = renderToJson(
<Email>
<Row layout={ColumnLayouts.OneColumn}>
<Column>
<Heading headingType="h1" fontSize="24px">
Generated from code
</Heading>
</Column>
</Row>
</Email>,
);

// Hand off to the visual builder
emailEditorRef.current.editor.loadDesign(design);

Signature

function renderToJson(element: React.ReactElement): DesignJSON;

Throws if the root element isn't a recognized container component.

When to Use Which

GoalUse
Send an email (multipart)renderToHtmlParts + renderToPlainText
One-off HTML preview, save to diskrenderToHtml
Bootstrap the visual editor with a default designrenderToJson
Generate a starter template for a userrenderToJson (then let them edit)

You can call multiple render functions on the same tree to keep an HTML version for sending and a JSON version for editing.


renderRowToJson(element)

Convert a single <Row> to row JSON. Useful when targeting Unlayer's Block Editor, which works with individual rows rather than full designs.

import {
renderRowToJson,
Row,
Column,
ColumnLayouts,
Paragraph,
} from '@unlayer/react-elements';

const row = renderRowToJson(
<Row layout={ColumnLayouts.OneColumn}>
<Column>
<Paragraph>Block content</Paragraph>
</Column>
</Row>,
);

Signature

function renderRowToJson(element: React.ReactElement): DesignRow;

Throws if the element isn't a <Row>. For full designs, use renderToJson instead.


Server-Side Use

All render functions are pure and run on the server. They work with:

  • Plain Node scripts (node, tsx, bun)
  • Next.js Route Handlers and Server Actions
  • Next.js App Router Server Components
  • Remix loaders / actions
  • AWS Lambda, Cloudflare Workers (Node-compat), and any other Node-compatible runtime
// app/api/welcome/route.ts (Next.js)
import { NextResponse } from 'next/server';
import { renderToHtmlParts } from '@unlayer/react-elements';
import { WelcomeEmail } from '@/emails/welcome';

export async function GET() {
const { head, body } = renderToHtmlParts(<WelcomeEmail />);
const html = `<!DOCTYPE html><html><head>${head}</head>${body}</html>`;
return new NextResponse(html, { headers: { 'content-type': 'text/html' } });
}

For Server-Component rendering, pass config as a second argument or use <Body config={...}> directly. <UnlayerProvider> requires React Context and isn't available on the server. See Configuration.


See Also

  • Output Formats — what each wrapper produces in HTML
  • Wrappers — choose the right wrapper before rendering