---
title: "Add templates to a project"
description: "Install the template library into a Node project, write a template for a PDF, preview it while you edit it, and render it from your own code. Experimental."
---

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

# Add templates to a project

Define each PDF or XLSX file your Node project produces in a file of its own,
see it while you edit it, and render it from your own server. Do this when your
project sends quotes, letters or price lists.

A file defined in code this way — the params it takes, sample params, and how
it draws itself — is called a _template_. The library works on its own: it
needs no Commentware server and no extension. It is experimental: its
functions, config options and commands may change or be removed in any release, with no notice first, and its preview's page carries an **Experimental** label.

## Prerequisites

- Node 24 or later, and a project with a `package.json` whose code is
  TypeScript or JavaScript with React's JSX. A TypeScript project sets
  `"jsx": "react-jsx"` in its `tsconfig.json`.
- A package manager. npm comes with Node.

## Install the library

1. Point the `@commentware-private` scope at the package registry, `https://registry.commentware.com`,
   so that your package manager fetches the library from it rather than from
   npm. Run the command for your package manager, or for yarn and bun write
   the file it shows:

   With pnpm:

```sh
pnpm config set @commentware-private:registry=https://registry.commentware.com/
```

npm: `npm config set @commentware-private:registry=https://registry.commentware.com/`

pnpm and npm — It writes ~/.npmrc, the file the install below reads. No flag: that is where this command goes by default.

With yarn, in `~/.yarnrc.yml`:

```yaml
npmScopes:
  commentware-private:
    npmRegistryServer: "https://registry.commentware.com/"
```

yarn — Yarn 2 and later ignores .npmrc. `yarn config set … --home` writes this file, but only from yarn 2 and later: yarn 1 takes the same command, says it worked, and writes somewhere yarn 2 never reads.

With bun, in `~/.npmrc`:

```ini
@commentware-private:registry=https://registry.commentware.com/
```

bun — bun reads ~/.npmrc and has no command that writes it. npm's command above produces exactly this line.

2. In your project, add the library and the four packages your templates are
   built with:

   With pnpm:

```sh
pnpm add @commentware-private/documents react react-dom zod exceljs
```

npm: `npm install @commentware-private/documents react react-dom zod exceljs` · yarn: `yarn add @commentware-private/documents react react-dom zod exceljs` · bun: `bun add @commentware-private/documents react react-dom zod exceljs`

3. A PDF is printed by Chrome. If you want the library to use a Chrome or
   Chromium you already have, name it in
   [`browser.executablePath`](/docs/documents/config/#browserexecutablepath) and
   skip this step. Otherwise, download the Chrome for Testing build it uses:

```sh
   npx commentware-documents install-browser
```

   It prints each download, about 650 MB in all, and ends with
   `Chrome for Testing is installed.` On a machine that already has the build,
   it prints that line alone.

## Write a template

1. At the root of your project, create the config file that names the
   directory your templates are in:

```ts title="documents.config.ts"
   import { defineConfig } from "@commentware-private/documents";

   export default defineConfig({
 templates: ["src/templates"],
   });
```

2. Create a template in that directory. Its id is its path inside the
   directory without the extension, here `thank-you`:

```tsx title="src/templates/thank-you.tsx"
   import { z } from "zod";
   import { defineTemplate } from "@commentware-private/documents";

   export default defineTemplate({
 schema: z.object({ name: z.string() }),
 supportedOutputs: ["pdf"],
 mockData: { name: "Mira" },
 config: async ({ params }) => ({
   metadata: {
     fileName: `thank-you-${params.name.toLowerCase()}`,
     pdf: { name: "Thank you", width: "148mm", height: "210mm" },
   },
   renderers: {
     pdf: {
       component: async () => (
         <div className="p-[15mm] text-2xl">Thank you, {params.name}.</div>
       ),
     },
   },
 }),
   });
```

   Tailwind classes work in a template. Every field is in
   [`defineTemplate`](/docs/documents/library/#definetemplate).

## Preview the template

1. From the root of your project, start the preview:

```sh
   npx commentware-documents preview
```

   It prints where it serves and keeps running until you press Ctrl-C:

```text
   Previewing 1 template at http://127.0.0.1:7043/
   Experimental: the template library and these commands may change or go in
   any release, with no notice first.
```

   If something else holds `7043`, start it with `--port` and a free port.

2. Open `http://127.0.0.1:7043/` and select `thank-you`. Edit the params on the
   left, or the template's file, and the page redraws. **Download PDF** gives
   you the file.

   Optional: to see the page as printed pages, select **Pages** in the
   toolbar. Each printed page is the size the template's `pdf` names, with its
   margins, and the text stays text, so a Commentware comment pins to it. The
   preview remembers the choice in this browser.

## Render it from your code

1. Write the index your code renders through. Run it again whenever you add a
   template or a Tailwind class:

```sh
   npx commentware-documents build
```

```text
   Wrote .documents/templates.ts: 1 template, 4 KB of CSS.
```

   `.documents/` is generated. Add it to your `.gitignore`.

2. Render the template where your code needs the file. The params are checked
   against the template's schema, and a mismatch is refused with each problem
   named:

```ts title="render.ts"
   import { writeFile } from "node:fs/promises";
   import { createRenderer } from "@commentware-private/documents";
   import { stylesheet, templates } from "./.documents/templates";

   const renderer = createRenderer({ templates, stylesheet });

   const file = await renderer.render("thank-you", { name: "Ada" }, "pdf");
   await writeFile(file.fileName, file.bytes);
   console.log(`Wrote ${file.fileName}`);

   await renderer.close();
```

   The renderer starts Chrome on the first PDF and keeps it until `close()`,
   so a server creates one renderer and renders through it.

## Check that it worked

Run `render.ts` with the TypeScript runner your project uses, `tsx` in this
example:

```sh
npx tsx render.ts
```

```text
Wrote thank-you-ada.pdf
```

Open `thank-you-ada.pdf`. It is one A5 page reading "Thank you, Ada."

## Next steps

- [documents.config.ts](/docs/documents/config/): add a stylesheet, more
  template directories, or the Vite plugins your templates import with.
- [@commentware-private/documents](/docs/documents/library/): write a template
  that builds a spreadsheet, and render it to XLSX.
- [commentware-documents](/docs/documents/commands/): every option of
  `preview` and `build`.