Skip to main content
import { render } from "@cordel/react-docx";

const { outputPath, diagnostics } = await render(<MyDocument />, "./output.docx");
Returns a Promise<RenderResult> with the output path and any diagnostics collected during rendering.

Basic usage

import { render, Document, Section, H1, Paragraph } from "@cordel/react-docx";

const MyDocument = () => (
  <Document>
    <Section>
      <H1>Hello</H1>
      <Paragraph>World</Paragraph>
    </Section>
  </Document>
);

const { outputPath, diagnostics } = await render(<MyDocument />, "./hello.docx");

renderToBuffer

For serverless or HTTP scenarios where you don’t want to write to disk:
import { renderToBuffer } from "@cordel/react-docx";

const { buffer, diagnostics } = await renderToBuffer(<MyDocument />);
Returns a Promise<RenderToBufferResult> with a Uint8Array buffer and diagnostics.

HTTP response

const { buffer } = await renderToBuffer(<Invoice />);

res.setHeader(
  "Content-Type",
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);
res.send(buffer);

Write to file manually

import fs from "node:fs";

const { buffer } = await renderToBuffer(<MyDocument />);
fs.writeFileSync("output.docx", buffer);

Variables

Pass dynamic data to your document:
await render(<Invoice />, "./invoice.docx", {
  variables: {
    invoiceNumber: "INV-001",
    customer: "Acme Corp",
    total: 420.00,
  },
});

Variable component

import { Variable } from "@cordel/react-docx";

const Invoice = () => (
  <Document>
    <Section>
      <H1>
        Invoice #<Variable name="invoiceNumber" />
      </H1>
      <Paragraph>
        Customer: <Variable name="customer" defaultValue="N/A" />
      </Paragraph>
    </Section>
  </Document>
);

useVariables hook

For more control:
import { useVariables } from "@cordel/react-docx";

const Invoice = () => {
  const vars = useVariables<{
    invoiceNumber: string;
    customer: string;
    total: number;
  }>();

  return (
    <Document>
      <Section>
        <H1>Invoice #{vars.invoiceNumber}</H1>
        <Paragraph>Customer: {vars.customer}</Paragraph>
        <Paragraph>
          <Text className="font-bold">Total: ${vars.total.toFixed(2)}</Text>
        </Paragraph>
      </Section>
    </Document>
  );
};

Formatting

<Variable
  name="price"
  format={(value) => `$${Number(value).toFixed(2)}`}
/>

Render options

OptionTypeDescription
variablesRecord<string, VariableValue>Dynamic data passed to Variable components and useVariables
strictbooleanThrow StrictModeError if any error-severity diagnostic is emitted
tailwindParserTailwindParserCustom parser for overriding colors, fonts, or adding class handlers
await render(<MyDocument />, "./output.docx", {
  variables: { name: "John" },
  strict: true,
});

Diagnostics

Both render and renderToBuffer return a diagnostics array. These are structured warnings and errors collected during rendering — images that failed to load, invalid nesting, unsupported Tailwind classes, etc.
const { outputPath, diagnostics } = await render(<MyDocument />, "./output.docx");

for (const d of diagnostics) {
  console.log(`[${d.severity}] ${d.code}: ${d.message}`);
}

Diagnostic interface

interface Diagnostic {
  code: string;                // e.g. "IMAGE_LOAD_FAILED"
  severity: "error" | "warning" | "info";
  message: string;
  element?: string;            // Component name, e.g. "Image"
  suggestion?: string;         // How to fix the issue
  path?: string;               // Tree path, e.g. "Document > Section > Td"
  url?: string;                // Link to documentation
}

Common codes

CodeSeverityDescription
IMAGE_LOAD_FAILEDerrorImage file not found or URL unreachable
INVALID_NESTINGerrorComponent placed in an unsupported parent
UNSUPPORTED_TAILWIND_CLASSwarningTailwind class has no DOCX equivalent
TAILWIND_CONFIG_LOAD_FAILEDwarningFailed to parse tailwind.config file
VARIABLE_NOT_FOUNDinfoVariable not provided and no defaultValue set

Custom handler

Override how diagnostics are reported:
import { setDiagnosticHandler } from "@cordel/react-docx";

setDiagnosticHandler((d) => {
  if (d.severity === "error") {
    console.error(`[${d.code}] ${d.message}`);
  }
});
Pass null to restore the default console-based handler:
setDiagnosticHandler(null);

Strict mode

Throws a StrictModeError if any diagnostic with "error" severity is emitted:
import { render, StrictModeError } from "@cordel/react-docx";

try {
  await render(<MyDocument />, "./output.docx", { strict: true });
} catch (error) {
  if (error instanceof StrictModeError) {
    console.error("Document has errors:", error.message);
  }
}
Works with renderToBuffer too:
const { buffer } = await renderToBuffer(<MyDocument />, { strict: true });

Error handling

Three levels of error handling: Diagnostics — non-fatal issues returned in the result. The document still generates.
const { diagnostics } = await render(<MyDocument />, "./output.docx");
if (diagnostics.length > 0) {
  console.warn(`${diagnostics.length} issue(s) found`);
}
Strict mode — promote error-severity diagnostics to thrown exceptions.
await render(<MyDocument />, "./output.docx", { strict: true });
Try/catch — handle unexpected failures (invalid JSX tree, filesystem errors, etc.).
try {
  await render(<MyDocument />, "./output.docx");
} catch (error) {
  console.error("Failed:", error);
}

Full example

import {
  render,
  Document,
  Section,
  H1,
  Paragraph,
  Text,
  Table,
  Thead,
  Tbody,
  Tr,
  Th,
  Td,
  useVariables,
} from "@cordel/react-docx";

interface InvoiceData {
  number: string;
  date: string;
  customer: { name: string; email: string };
  items: { name: string; qty: number; price: number }[];
}

const Invoice = () => {
  const vars = useVariables<InvoiceData>();
  const total = vars.items.reduce((sum, item) => sum + item.qty * item.price, 0);

  return (
    <Document>
      <Section>
        <H1 className="text-blue-700">Invoice #{vars.number}</H1>

        <Paragraph className="mt-4">
          <Text className="font-bold">Date: </Text>{vars.date}
        </Paragraph>
        <Paragraph>
          <Text className="font-bold">Customer: </Text>{vars.customer.name}
        </Paragraph>

        <Table className="mt-6 border">
          <Thead>
            <Tr>
              <Th className="border p-2 bg-gray-100">Item</Th>
              <Th className="border p-2 bg-gray-100">Qty</Th>
              <Th className="border p-2 bg-gray-100">Price</Th>
            </Tr>
          </Thead>
          <Tbody>
            {vars.items.map((item, i) => (
              <Tr key={i}>
                <Td className="border p-2">{item.name}</Td>
                <Td className="border p-2">{item.qty}</Td>
                <Td className="border p-2">${item.price}</Td>
              </Tr>
            ))}
          </Tbody>
        </Table>

        <Paragraph className="mt-4 text-right">
          <Text className="text-xl font-bold">Total: ${total}</Text>
        </Paragraph>
      </Section>
    </Document>
  );
};

const { outputPath, diagnostics } = await render(<Invoice />, "./invoice.docx", {
  variables: {
    number: "INV-001",
    date: "2024-01-15",
    customer: { name: "Acme Corp", email: "billing@acme.com" },
    items: [
      { name: "Widget", qty: 10, price: 25 },
      { name: "Gadget", qty: 5, price: 50 },
    ],
  },
});

if (diagnostics.length > 0) {
  console.warn("Warnings:", diagnostics.map((d) => d.message));
}