You are currently offline, serving cached version

Angular parser

Introduction

By default, docxtemplater does not support complex expressions inside tags. For example, if your template contains the tag {user.name}, docxtemplater will attempt to retrieve the property "user.name" from the data object — that is, it will run data["user.name"], which typically returns undefined.

By enabling the angular parser, you gain the ability to use real JavaScript-like expressions, such as:

Make sure to use angular-expressions version 1.4.3 or higher (released on 10th December 2024), as earlier versions (1.4.2 and below) contain a security vulnerability. You can check your version in node_modules/angular-expressions/package.json by examining the version field.

Setup

To use the Angular parser, you need to install the angular-expressions library, which provides the underlying expression engine:

npm install --save angular-expressions

Once installed, you can enable the Angular parser by importing and configuring it like this:

const expressionParser = require("docxtemplater/expressions.js");

const parser = expressionParser.configure({
    filters: {}, // optional: define your custom filters here
});

const doc = new Docxtemplater(zip, {
    parser,
});

doc.render(/* your data */);

Nested property access

Once the Angular parser is set up, you can use dot notation to access nested properties within your data. With following template :

{user.name}
const expressionParser = require("docxtemplater/expressions.js");

const parser = expressionParser.configure({});

const doc = new Docxtemplater(zip, {
    parser,
});

doc.render({
    user: {
        name: "John",
    },
});

This will render:

Hello John

Conditions

With the Angular parser enabled, you can use conditional expressions directly in your templates.

{#users.length > 1}
There are multiple users
{/}

{#users[0].name == "John"}
Hello {users[0].name}, welcome back
{/}
  • The first block will render only if there are more than one user in the users array.
  • The second block will render only if the first user's name is "John".

You can use any valid JavaScript-like expression inside the condition, including comparisons, boolean operators, and property access.

Operators

The angular parser handles the following operators :

OperatorExpressionExampleResult
TERNARIESa ? b : ctrue ? "welcome" : "nope""welcome"
EQUALITY/INEQUALITYa == 1, a != 11 == 1, 1 != 1true, false
RELATIONALa > 1, a < 1, a >= 1, a <= 13 > 1, 3< 1true, false
ANDa && btrue && falsefalse
ORa || btrue || falsetrue
OPERATOR PRECEDENCE with parenthesis(a && b) || c(true && false) || truetrue
ASSIGNMENTSa = 1a = 11
ADDITIONa + b1+12
SUBSTRACTIONa - b2-11
MULTIPLICATIONa * b2\*24
MODULOa % b4%31
DIVISIONa / b4/ 22
EXPONENTIAL NUMBERS1e31e31000

For example, it is possible to write the following template:

{#myType === "users"}
There are {users.length} users.
{#cond1 || cond2}
Paragraph 1
{/}
{#cond2 && cond3}
Paragraph 2
{/}
{#cond4 ? users : usersWithAdminRights}
Paragraph 3
{/}
{/}

Filters

Filters allow you to transform data before it's rendered in the template.

For example, to convert a name to uppercase:

{user.name | toUpperCase}

You can define this filter using .configure() like this:

const expressionParser = require("docxtemplater/expressions.js");

const parser = expressionParser.configure({
    filters: {
        toUpperCase(input) {
            // Always check for undefined input to avoid runtime errors
            if (!input) {
                return input;
            }
            return input.toUpperCase();
        },
    },
});

const doc = new Docxtemplater(zip, { parser });

doc.render({
    user: {
        name: "John",
    },
});

This will render:

JOHN

Creating a custom filter: olderThan

Filters can accept arguments. For example, to filter a list of users by age:

The allowed users are:

{#users | olderThan:15}
{name} - {age} years old
{/}

Code:

const expressionParser = require("docxtemplater/expressions.js");
const parser = expressionParser.configure({
    filters: {
        olderThan(users, minAge) {
            // Always check for undefined input to avoid runtime errors
            if (!users) {
                return users;
            }
            return users.filter((user) => user.age >= minAge);
        },
    },
});

const doc = new Docxtemplater(zip, { parser });

doc.render({
    users: [
        { name: "John", age: 15 },
        { name: "Mary", age: 26 },
    ],
});

You can find some powerful examples of what filters can do below, all using the .configure({ filters }) method.

Data filtering

You can build generic filters that evaluate each item using Angular-style expressions.

Template:

{#users | where:'age > 15'}
Hello {name}
{/}

Data:

doc.render({
    users: [
        { name: "John", age: 10 },
        { name: "Mary", age: 20 },
    ],
});

This will output:

Hello Mary

Code:

const expressionParser = require("docxtemplater/expressions.js");

const parser = expressionParser.configure({
    filters: {
        where(input, query) {
            return input.filter(function (item) {
                return expressionParser.compile(query)(item);
            });
        },
    },
});
const doc = new Docxtemplater(zip, {
    parser,
});
doc.render(/* data */);

The where filter accepts any valid expression using &&, ||, comparisons, property access, and more.

Data sorting

To sort an array by one or more properties:

Template:

{#items | sortBy:'price'}
{name} for a price of {price} €
{/}

Code:

const { sortBy } = require("lodash");
const expressionParser = require("docxtemplater/expressions.js");

const parser = expressionParser.configure({
    filters: {
        sortBy(input, ...fields) {
            // Always check for undefined input to avoid runtime errors
            if (!input) {
                return input;
            }
            return sortBy(input, fields);
        },
    },
});
const doc = new Docxtemplater(zip, {
    parser,
});
doc.render(/* data */);

Data:

doc.render({
    items: [
        { name: "Acme Computer", price: 1000 },
        { name: "USB storage", price: 15 },
        { name: "Mouse & Keyboard", price: 150 },
    ],
});

This renders items sorted by price in ascending order.

Multi-level loops

It is possible to handle multilevel (nested) loops using a custom loop filter. For example, if your data looks like this:

doc.render({
    companies: [
        {
            name: "EvilCorp",
            users: [
                { firstName: "John" },
                { firstName: "Mary" },
            ],
        },
        {
            name: "HeavenCorp",
            users: [
                { firstName: "Jack" },
                { firstName: "Bonnie" },
            ],
        },
    ],
});

You can loop directly over all users from all companies using the following tag:

{#companies | loop:'users'}
{firstName}
{/}

To enable this, configure the loop filter as follows:

const expressionParser = require("docxtemplater/expressions.js");

const parser = expressionParser.configure({
    filters: {
        loop(input, ...keys) {
            // Always check for undefined input to avoid runtime errors
            if (!input) {
                return input;
            }
            let result = input;

            for (const key of keys) {
                const next = [];
                for (const item of result) {
                    const children = item[key] || [];
                    for (const subitem of children) {
                        next.push({ ...item, ...subitem });
                    }
                }
                result = next;
            }
            return result;
        },
    },
});
const doc = new Docxtemplater(zip, {
    parser,
});
doc.render(/* data */);

You can also support deeper nesting with additional keys:

{#companies | loop:"users":"tasks"}
{firstName} - {taskName}
{/}

This flattens nested structures across multiple levels while keeping access to parent properties.

Data aggregation

If your data is the following:

{
    "items": [
        {
            "name": "Acme Computer",
            "price": 1000
        },
        {
            "name": "Mouse & Keyboard",
            "price": 150
        }
    ]
}

And you would like to show the total price, you can write in your template:

{#items}
{name} for a price of {price} €
{/}
Total Price of your purchase: {items | sumBy:'price'}€

The sumBy filter can be defined using .configure() like this:

const expressionParser = require("docxtemplater/expressions.js");
const parser = expressionParser.configure({
    filters: {
        sumBy(input, field) {
            // Always check for undefined input to avoid runtime errors
            if (!input) {
                return input;
            }
            return input.reduce(
                (sum, object) => sum + object[field],
                0
            );
        },
    },
});
const doc = new Docxtemplater(zip, {
    parser,
});
doc.render(/* data */);

Data formatting

This example demonstrates how to format numbers with two digits of precision (e.g., 150.00). If your data is the following:

{
    "items": [
        {
            "name": "Acme Computer",
            "price": 1000
        },
        {
            "name": "Mouse & Keyboard",
            "price": 150
        }
    ]
}

And you would like to show the price with two decimal digits, you can write in your template:

{#items}
{name} for a price of {price | toFixed:2} €
{/}

The toFixed filter can be defined using .configure() like this:

const expressionParser = require("docxtemplater/expressions.js");
const parser = expressionParser.configure({
    filters: {
        toFixed(input, precision) {
            // Always check for undefined input to avoid runtime errors
            if (!input) {
                return input;
            }
            return input.toFixed(precision);
        },
    },
});
const doc = new Docxtemplater(zip, {
    parser,
});
doc.render(/* data */);

Assignments

With the angular expression option, it is possible to assign a value to a variable directly from your template.

For example, in your template, write:

xxx
{full_name = first_name + last_name}
yyy

This will show in your output :

xxx

yyy

To drop the new line, use a raw tag instead, starting with a @ :

xxx
{@full_name = first_name + last_name}
yyy

Output :

xxx
yyy

Assignments inside loops

By default, variable assignments occur in the current scope. This behavior affects how your data is modified during template processing.

For example, when you write:

{#users}{lastUserName}{lastUserName = name}{/}

After the first iteration, your data structure becomes:

data = {
    users: [
        {
            name: "John",
            lastUserName: "John",
        },
        {
            name: "Mary",
        },
        {
            name: "Jack",
        },
    ],
};

Notice that only the first item in the array has the lastUserName property set.

Customizing Assignment Scope

You can modify this default behavior by customizing the scope handling:

const expressionParser = require("docxtemplater/expressions.js");
new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
    parser: expressionParser.configure({
        setIdentifier(tag, value, currentScope, scopeList) {
            /*
             * In our context scopeList[0] is the same as `data`
             *
             * - scopeList[1] will be the child scope (if it exists)
             *        => will be { name: "John" } for the first iteration
             * - scopeList[2] will be the child child scope (if it exists)
             *        => in your case there is just one level loop
             * - currentScope will in our case be the same as scopeList[1]
             */
            const rootScope = scopeList[0];
            rootScope[tag] = value;
            // return true to notify to the expressionParser that we handled this assignment
            return true;
        },
    }),
});

When using this custom setIdentifier function, after processing all iterations, your data structure would look like:

data = {
    users: [
        {
            name: "John",
        },
        {
            name: "Mary",
        },
        {
            name: "Jack",
        },
    ],
    lastUserName: "Jack", // Set at the root level with the last user's name
};

Instead of placing the value in the current scope (inside each user object), this approach assigns the value to the root data object, making it available globally.

Retrieving $index as part of an expression

One might need to have a condition on the $index when inside a loop.

For example, if you have two arrays of the same length and you want to loop over both of them at the same time:

doc.render({
    names: ["John", "Mary"],
    ages: [15, 26],
});
{#names}
{#$index == 0}First item !{/}
{names[$index]}
{ages[$index]}
{/names}

Preprocess or postprocess the tag or result

It is possible to preprocess or postprocess :

  • the tag ("name" if your tag is {name})
  • the result ("John" if your data is {name: "John"})

For example, you could have some specific behavior applied for all HTML tags.

Say you wanted to use the following in your code, and still make this work with the HTML module :

doc.render({
    htmlDescription: "Dear Edgar,\nWhat's your plan today ?",
});
{~~htmlDescription}

In this case, the new line ("\n" character) will be ignored since in HTML, linebreaks can only be achieved by using a <br> element, or a HTML block tag such as <p> or <div>.

One way to make this work together with the angularParser would be to postprocess your result.

You could do so like this :

const expressionParser = require("docxtemplater/expressions.js");

const htmlModuleNameBlock =
    "pro-xml-templating/html-module-block";
const htmlModuleNameInline =
    "pro-xml-templating/html-module-inline";
const htmlPptxModuleNameBlock =
    "pro-xml-templating/html-pptx-module-block";
const htmlPptxModuleNameShape =
    "pro-xml-templating/html-pptx-module-shape";
const htmlPptxModuleNameInline =
    "pro-xml-templating/html-pptx-module-inline";
const htmlModuleTags = [
    htmlModuleNameBlock,
    htmlModuleNameInline,
    htmlPptxModuleNameBlock,
    htmlPptxModuleNameShape,
    htmlPptxModuleNameInline,
];

const doc = new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
    parser(tag) {
        const obj = expressionParser(tag);
        return {
            get(scope, context) {
                let result = obj.get(scope, context);
                if (
                    htmlModuleTags.indexOf(
                        context.meta.part.module
                    ) !== -1 &&
                    typeof result === "string"
                ) {
                    // for all html tags, replace "\n" by `<br>`
                    result = result.replace(/\n/g, "<br>");
                }
                return result;
            },
        };
    },
    modules: [new HTMLModule(), new HTMLPptxModule()],
});
doc.render({
    htmlDescription: "Dear Edgar,\nWhat's your plan today ?",
});

Similarly, it is possible to preprocess or postprocess, you could be inspired by one of these samples :

Alternatives

Jexl

You could also use the Jexl library if you prefer their syntax.

We did not create a builtin integration with jexl, but it could work for you.

Use it like this :

const jexl = require("jexl");
const doc = new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
    parser: (tag) => {
        const compiledVersion = jexl.compile(tag);
        return {
            get(scope /* , context */) {
                return compiledVersion.evalSync(scope);
            },
        };
    },
});
doc.render({
    employees: [
        { first: "Sterling", last: "Archer", age: 36 },
        { first: "Malory", last: "Archer", age: 75 },
        { first: "Lana", last: "Kane", age: 33 },
        { first: "Cyril", last: "Figgis", age: 45 },
        { first: "Cheryl", last: "Tunt", age: 28 },
    ],
    retireAge: 62,
});

With the following expression :

{employees[.last == 'Tu' + 'nt'].first}

It will simply render :

Cheryl

With this code above, for loops, you won't be able to access data that is outside of the loop, you will have to implement something similar to what we have done for our integration with angular-expressions.

Filtrex

Similarly, you could also use filtrex :

const {
    compileExpression,
    useDotAccessOperator,
} = require("filtrex");

const doc = new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
    parser: (tag) => {
        const fn = compileExpression(tag, {
            customProp: useDotAccessOperator,
        });

        return {
            get(scope /* , context */) {
                return fn(scope);
            },
        };
    },
});
doc.render({
    foo: { bar: 42 },
});
{foo.bar}

Will result in

42

Jsonata

Or you could use jsonata

const jsonata = require("jsonata");

const doc = new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
    parser: (tag) => {
        const expression = jsonata(tag);
        return {
            get(scope /* , context */) {
                return expression.evaluate(scope); // returns 24
            },
        };
    },
});
// We use renderAsync because jsonata is async
doc.renderAsync({
    example: [{ value: 4 }, { value: 7 }, { value: 13 }],
});

With following template :

{$sum(example.value)}

Gives :

24

Notes

If running in IE 11 or if you get the error Proxy is not defined, you can use the following parser instead :

const expressionParser =
    require("docxtemplater/expressions-ie11.js");
const doc = new Docxtemplater(zip, {
    parser: expressionParser,
});
doc.render(/* data */);

If you use docxtemplater 3.32.2 or a previous version, the require("docxtemplater/expressions.js") call won't work. In this case, you can upgrade docxtemplater or copy the code found here

Talk with sales Contact us