---
title: "Generate PDFs from a Word template using JavaScript | Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/pdf-generation/from-word-template/"
md_url: "https://www.nutrient.io/guides/web/pdf-generation/from-word-template.md"
last_updated: "2026-06-09T10:38:40.973Z"
description: "Learn to create PDFs from Word templates using Nutrient Web SDK. Populate DOCX files with data and export in PDF or PDF/A formats."
---

# Generate PDFs from a Word template using JavaScript

Nutrient Web SDK generates PDF documents from Word templates by merging DOCX files with data. You can run this flow headlessly in the background or in the Nutrient viewer UI.

[Try for Free](https://www.nutrient.io/sdk/web/getting-started.md)

[Launch Demo](https://www.nutrient.io/demo/generation-from-office-template)

This guide explains how to:

- Prepare DOCX templates with placeholders.

- Populate templates with structured data.

- Export output as PDF or PDF/A.

## General principles

Word templating has three parts:

- A DOCX file that acts as the template.

- A template model with placeholder values.

- Optional delimiter configuration.

## Template model

You can define the template model in an external JSON file or build it programmatically.

The model includes:

- Optional delimiter configuration (`start` and `end`). If omitted, the default delimiters are `{` and `}`.

- At least one placeholder-value pair. Placeholder names must match the placeholders in the DOCX template.

## Classes, methods, and properties

This guide uses one entry point from the SDK:

- [`NutrientViewer.populateDocumentTemplate`](https://www.nutrient.io/api/web/modules/NutrientViewer.html#.populateDocumentTemplate)

## Populate a Word template

Nutrient Web SDK supports placeholder replacement, loops, and dynamic tables. When content size changes, the DOCX template reflows across pages. Placeholders keep their original DOCX formatting.

Start by preparing a DOCX template with placeholders. The example below uses `{{` and `}}` delimiters.

**Placeholder naming rules**

Placeholder names must follow these rules:

- **Supported characters** — Letters (a–z, A–Z), numbers (0–9), and underscores (_)

- **Valid examples** — `{{name}}`, `{{firstName}}`, `{{item_1}}`, `{{TOTAL_AMOUNT}}`

- **Invalid examples** — `{{first-name}}`, `{{item.price}}`, `{{user@email}}`, `{{my placeholder}}`

These rules help the parser process templates correctly and avoid conflicts with special syntax characters.

Prepare data:

```js

const data = {
  config: {
    delimiter: {
      start: "{{",
      end: "}}",
    },
  },
  model: {
    name: "Alex Smith",
    text: "Hello World!",
    amount: "$249.99",
  },
};

```

Load the DOCX template and replace placeholders. This returns an `ArrayBuffer` with the populated DOCX:

```js

const buffer = await NutrientViewer.populateDocumentTemplate(
  {
    // Other configuration options.
    document: "template.docx",
  },
  data,
);

```

The following image shows the populated DOCX output.

Next, convert the DOCX to PDF. The following example converts to PDF/A-1a. Choose the conformance value you need from the [conformance enumeration](https://www.nutrient.io/api/web/modules/NutrientViewer.html#.Conformance).

```js

const pdfBuffer = await NutrientViewer.convertToPDF(
  {
    // Other configuration options.
    document: buffer,
  },
  NutrientViewer.Conformance.PDFA_1A,
);

```

## Image substitution

Along with text, loops, and conditionals, `NutrientViewer.populateDocumentTemplate()` also supports image substitution in standalone mode.

Use these markers in your DOCX template:

- `{{%name}}` — Inserts an inline image

- `{{%%name}}` — Inserts a centered image

Set the model entry to an image object with `_type: "image"`:

```js

const data = {
  config: {
    delimiter: {
      start: "{{",
      end: "}}",
    },
  },
  model: {
    companyName: "Nutrient",
    logo: {
      _type: "image",
      source: "url",
      url: "https://example.com/logo.png",
      sizing: "fit-width",
      width: 96,
      altText: "Nutrient logo",
    },
  },
};

```

### Supported image sources

The `source` field selects how the image payload is supplied:

- `source: "base64"` — Raw Base64 payload in `data`.

- `source: "dataUrl"` — Data URL payload in `data` (alias of `base64`).

- `source: "url"` — HTTP(S) URL in `url` (fetched and normalized at runtime).

For URL sources, the host must enable CORS. If CORS isn’t configured, use Base64 or data URL payloads.

### Sizing and multipage behavior

The `sizing` mode determines which dimension fields are required:

- If you omit `sizing`, preprocessing keeps sizing unchanged, and the engine applies default layout behavior.

- `sizing: "original"` requires no dimensions.

- `sizing: "fixed"` and `sizing: "fit-max"` require both `width` and `height` (positive numbers).

- `sizing: "fit-width"` requires `width` (positive number).

- `sizing: "fit-height"` requires `height` (positive number).

- For multipage images, `pageNumber` is supported and must be a positive one-based integer.

### Validation and limitations

A few constraints to be aware of:

- For raw Base64 payloads, set `format` explicitly (for example `png`, `jpg`, `gif`, `bmp`, `tif`).

- `source: "file"` isn’t supported in browser public APIs.

- SVG payloads and SVG formats aren’t supported in this release.

- If any image entry is invalid or can’t be resolved, the complete templating request fails.

Image entries also support optional properties such as borders, captions, rotation, link targets, and accessibility metadata. For the full list, refer to the [`NutrientViewer.populateDocumentTemplate` API reference](https://www.nutrient.io/api/web/modules/NutrientViewer.html#.populateDocumentTemplate).

## Advanced example: Looping through data sets

Loops repeat content based on arrays in your data model. Use loops for invoice line items, employee lists, or other repeated sections.

If your data contains three items, the loop renders three repeated sections:

```js

lineItems: [
  { product: "Laptop", price: "$999" },
  { product: "Mouse", price: "$25" },
  { product: "Keyboard", price: "$75" },
];

```

### Creating loops

Add an array of objects to the data model. For each object, the loop body is repeated when the engine processes the DOCX and model.

**Step one — Prepare your data**

```js

const data = {
  config: {
    delimiter: {
      start: "{{",
      end: "}}",
    },
  },
  model: {
    loop1: [
      { loopDesc1: "Monday", loopDesc2: "Tuesday" },
      { loopDesc1: "Wednesday", loopDesc2: "Thursday" },
      { loopDesc1: "Friday", loopDesc2: "Saturday" },
    ],
    loop2: [{ loopDesc: "Red" }, { loopDesc: "Orange" }, { loopDesc: "Green" }],
  },
};

```

**Step two — Create your DOCX template with loop placeholders**

The template defines where and how loops repeat content.

For bulleted or numbered lists, place the opening placeholder immediately before the list and the closing placeholder immediately after it.

**Step three — Generate the populated document**

When you combine the data and template, the loops generate the repeated content.

For an example that combines loops with tables, refer to the [dynamic table loop example](#dynamic-table-loop-example) section below.

### Nested loops example

Nested loops iterate through multilevel data structures, such as categories that contain items. This is useful for documents such as catalogs, menus, and hierarchical reports.

**Real-world scenario — Restaurant menu**

This example uses multiple sections (starters, entrees, sides), each with multiple items and descriptions.

**Step one — Prepare nested data structure**

```js

const data = {
  config: {
    delimiter: {
      start: "{{",
      end: "}}",
    },
  },
  model: {
    title: "New Year’s",
    subtitle: "celebration",
    sections: [
      {
        title: "STARTERS",
        items: [
          {
            title: "SALSA TRIO & FRESH CHIPS",
            description: "Black Bean Salsa, Green Chili, Pico de Gallo",
            hasDescription: true,
          },
          {
            title: "SHRIMP & AVOCADO CEVICHE",
            description: "Gulf Shrimp Marinated in Lime with Cilantro",
            hasDescription: true,
          },
        ],
      },
      {
        title: "ENTREES",
        items: [
          {
            title: "GRILLED ACHIOTE CHICKEN",
            description: "Red Chili & Avocado Puree",
            hasDescription: true,
          },
          {
            title: "SHREDDED BEEF ENCHILADAS",
            description: "Green Chili & Cheddar",
            hasDescription: true,
          },
        ],
      },
      {
        title: "SIDES",
        items: [
          {
            title: "CALABACITAS",
            description: "Zucchini, Poblano Corn, Black Beans",
            hasDescription: true,
          },
          {
            title: "CILANTRO LIME RICE",
            description: null,
            hasDescription: false,
          },
        ],
      },
    ],
  },
};

```

**Step two — Create your DOCX template with nested loop placeholders**

Structure placeholders like this:

```

{{title}} {{subtitle}}

{{#sections}}

{{title}}
{{#items}}

• {{title}}
{{#hasDescription}}{{description}}{{/hasDescription}}

{{/items}}

{{/sections}}

```

**Loop structure**

- **Outer loop** — `{{#sections}}...{{/sections}}` iterates through sections.

- **Inner loop** — `{{#items}}...{{/items}}` iterates through items in each section.

- **Conditional** — `{{#hasDescription}}...{{/hasDescription}}` renders only when the Boolean flag is `true`.

**Step three — Generated output**

This structure generates the following output:

```

New Year’s celebration

STARTERS
• SALSA TRIO & FRESH CHIPS
Black Bean Salsa, Green Chili, Pico de Gallo
• SHRIMP & AVOCADO CEVICHE
Gulf Shrimp Marinated in Lime with Cilantro

ENTREES
• GRILLED ACHIOTE CHICKEN
Red Chili & Avocado Puree
• SHREDDED BEEF ENCHILADAS
Green Chili & Cheddar

SIDES
• CALABACITAS
Zucchini, Poblano Corn, Black Beans
• CILANTRO LIME RICE

```

## Adding conditional logic

Conditionals insert and format content based on conditions in the template. You can combine conditionals with loops.

Use this syntax:

- `#condition` — Starts a block rendered when the condition is `true`.

- `^condition` — Starts a block rendered when the condition is `false`.

- `/condition` — Ends the conditional block.

Example using `isBlue`:

### XML

```xml

{{#isBlue}}Blue{{/isBlue}}{{^isBlue}}Red{{/isBlue}}

```

### JAVASCRIPT

```js

{
	config: {
		delimiter: {
			start: "{{",
			end: "}}"
		}
	},
	model: {
		isBlue: true
	}
}

```

The following is a more complex conditional example.

Combine the template above with this data:

```js

{
	config: {
		delimiter: {
			start: "{{",
			end: "}}"
		}
	},
	model: {
		isConsulting: false,
		consultingField: "N/A",
		scopeExhibit: "Exhibit A",
		startDate: "March 1, 2024",
		endDate: "February 28, 2025",
		autoRenew: true
	}
}

```

This produces the following DOCX, which you can then convert to PDF.

## Dynamic table loop example

This example uses loops to insert rows into a table dynamically. Download the [DOCX template](https://www.nutrient.io/downloads/word-template/table.docx) used in this example.

### Using a model loaded from a JSON file

Populate the DOCX template with data from a JSON file. You can [download the JSON file](https://www.nutrient.io/downloads/word-template/table.json) used in this example:

```js

const data = await fetch("table.json").then((response) => response.json());
const docx = await NutrientViewer.populateDocumentTemplate(
  {
    // Other configuration options.
    document: "table.docx",
  },
  data,
);

const pdfBuffer = await NutrientViewer.convertToPDF(
  {
    // Other configuration options.
    document: docx,
  },
  NutrientViewer.Conformance.PDFA_1A,
);

```

## Automatic reflow example

Text reflow automatically adjusts text to fit available layout space. The engine moves text to new lines, pages, or columns as content size changes.

Use the following JSON:

```json

{
  "config": {
    "delimiter": {
      "start": "{{",
      "end": "}}"
    }
  },
  "model": {
    "clauses": [
      {
        "clause": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      },
      {
        "clause": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      },
      {
        "clause": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      },
      {
        "clause": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      },
      {
        "clause": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      }
    ],
    "title": "Purchase and Sale Agreement"
  }
}

```

The example below generates a PDF from a DOCX template where content reflows into another column using a model loaded from JSON. For a programmatic model example, refer to the [dynamic table loop example](#dynamic-table-loop-example) section above.

```js

data = await fetch("data.json").then((response) => response.json());
const docx = await NutrientViewer.populateDocumentTemplate(
  {
    // Other configuration options.
    document: "reflow.docx",
  },
  data,
);

const pdfBuffer = await NutrientViewer.convertToPDF(
  {
    // Other configuration options.
    document: docx,
  },
  NutrientViewer.Conformance.PDFA_1A,
);

```

## Canceling template population

To cancel an in-progress `populateDocumentTemplate` call, pass an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) through the [`signal`](https://www.nutrient.io/api/web/NutrientViewer.Configuration.html#signal) configuration option. This also works with `convertToPDF` in the same pipeline:

When the signal is aborted, the operation promise rejects with a `DOMException` whose name is `AbortError`. HTTP-based steps (such as document fetch) are canceled at the network level. For WebAssembly/worker-based processing, the promise still rejects immediately, but underlying processing may continue in the background briefly.

```js

const controller = new AbortController();

async function generatePdf() {
  const docx = await NutrientViewer.populateDocumentTemplate(
    {
      document: "template.docx",
      signal: controller.signal,
    },
    data,
  );

  const pdfBuffer = await NutrientViewer.convertToPDF(
    {
      document: docx,
      signal: controller.signal,
    },
    NutrientViewer.Conformance.PDFA_1A,
  );

  return pdfBuffer;
}

generatePdf().catch((error) => {
  if (error.name === "AbortError") {
    console.log("Generation cancelled");
  }
});

// Cancel at any point during the pipeline.
controller.abort();

```
---

## Related pages

- [Generate PDFs from images using JavaScript](/guides/web/pdf-generation/from-images.md)
- [Generate a PDF with a PDF form using JavaScript](/guides/web/pdf-generation/from-pdf-form.md)
- [Headless PDF generation](/guides/web/pdf-generation/headless.md)
- [Generate PDFs from a template using JavaScript](/guides/web/pdf-generation/from-pdf-template.md)
- [JavaScript PDF generation library](/guides/web/pdf-generation.md)
- [Generate PDF reports using JavaScript](/guides/web/pdf-generation/pdf-reports.md)
- [Generate PDF thumbnails using JavaScript](/guides/web/pdf-generation/thumbnail-preview.md)

