> ## Documentation Index
> Fetch the complete documentation index at: https://react-docx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Templates and testing

A template is a React component plus the data it renders. This guide covers declaring templates with validated data, sharing parts between documents, and testing them.

***

## Declare a template

```tsx theme={"dark"}
import { defineTemplate, renderTemplate } from "@cordel/react-docx";
import { z } from "zod";

const EvidenceData = z.object({
  company: z.object({ name: z.string(), logo: z.string() }),
  code: z.string(),
  groups: z.array(
    z.object({
      title: z.string(),
      description: z.string(),
      photos: z.array(z.object({ src: z.string(), caption: z.string() })),
    }),
  ),
});

export const evidenceReport = defineTemplate({
  name: "evidence-report",
  version: "1.2.0",
  schema: EvidenceData,
  render: (data) => <EvidenceReport {...data} />,
});
```

`name` and `version` are recorded in every document the template renders, with a hash of the data; see [Audit and provenance](/react-docx/guides/audit-and-provenance). Bump `version` when the output changes.

`schema` accepts any [Standard Schema](https://standardschema.dev) validator: zod, valibot, arktype and others. React DOCX does not depend on any of them. With a schema, the data type is inferred from it.

Render with data from a request, a queue or a file:

```tsx theme={"dark"}
const bytes = await renderTemplate(evidenceReport, await request.json(), {
  loadFile: (key) => storage.read(key),
});
```

Invalid data rejects with a `TemplateDataError` listing every problem, before anything is rendered. With zod 4:

```
Invalid data for template "evidence-report": groups.0.photos.0.src: Invalid input: expected string, received undefined
```

The messages come from your validator; `error.issues` holds them with their paths.

Templates without a schema are typed by their `render` parameter, and `renderTemplate` checks the data type at compile time.

***

## Share parts between documents

Shared parts are components. A header used by every document of a company:

```tsx theme={"dark"}
export const ExecutiveHeader = ({ company, info }: HeaderProps) => (
  <Locked title="Executive header">
    <Table className="border border-gray-400" width="full">
      <Thead>...</Thead>
    </Table>
  </Locked>
);
```

Use it in any template, pass it different data, and change it in one place. Conditional or repeated parts are ordinary JSX: `{items.map(...)}` and `{condition && ...}`.

***

## Data: props or variables

Prefer passing data as props, as above: TypeScript checks every use. `<Variable>` and `useVariables()` read values passed in the render options; they suit a few document-wide values. A `<Variable>` that was not provided rejects the render, so a missing value never produces a silently incomplete document.

***

## Test templates

`@cordel/react-docx/testing` reads a rendered document back:

```tsx theme={"dark"}
import { renderToDocx } from "@cordel/react-docx/testing";

it("numbers every photo", async () => {
  const docx = await renderToDocx(evidenceReport.render(fixture));

  const captions = docx.paragraphs.filter((text) => text.startsWith("Photo "));
  expect(captions).toHaveLength(152);
  expect(captions[0]).toBe("Photo 1: Boiler inlet");
});
```

| Member       | Description                                      |
| ------------ | ------------------------------------------------ |
| `paragraphs` | Text of each paragraph, table cells included     |
| `document`   | `word/document.xml`                              |
| `xml(part)`  | Any XML part, e.g. `"docProps/core.xml"`         |
| `file(path)` | Any file's bytes, e.g. `"word/media/image1.jpg"` |
| `files`      | Every path in the package                        |

Rendering is deterministic: the same template and data produce byte-identical files. Snapshot `docx.document` to catch any change in a template's output:

```tsx theme={"dark"}
expect(docx.document).toMatchSnapshot();
```

***

## Preview while editing

Re-render on every save and convert to PDF with LibreOffice to review the layout:

```bash theme={"dark"}
npx tsx watch render.tsx
soffice --headless --convert-to pdf out/report.docx --outdir out
```

LibreOffice is close to Word but not identical; check final layouts in Word.
