Node.js guide

Fill a Word template with JSON and save the PDF in Node.js.

The document layout lives in a normal DOCX file. Your application sends the template ID and a JSON object of values. The API fills the template and answers with the PDF, which your code writes to disk.

  1. Word templateUploaded once and activated in your TmplVision account.
  2. One POST requestfetch with a FormData field data. No SDK to install.
  3. PDF in the responseHTTP 201 with the PDF bytes, saved as proposal.pdf.

Uses only built-in Node.js APIs (fetch, FormData, node:fs) and needs Node.js 22 or newer.

Saved example output 1 pageOpen PDF
Example proposal PDF for a fictional client with three line items, a €2,600 project total and an optional support section.
A pre-generated PDF from the example template and data. This page does not run the script or call the API.

Before you start

Step by step

From the example files to your first PDF

The commands below are complete. Run them in one terminal window, because the environment variables only exist in that session.

  1. Upload and activate the Word template

    To follow along, use the example proposal DOCX. In TmplVision, upload it as a new template and save it. Click Activate and confirm, then copy its Template ID. New templates are inactive and cannot be used through the API until you activate them.

  2. Download the script and the example data

    Create an empty folder and save generate-proposal.cjs, proposal-data.json and proposal-no-support-data.json in it. Or download them from the terminal:

    macOS or Linux

    mkdir tmplvision-node-example
    cd tmplvision-node-example
    curl -fsSLO https://www.tmplvision.io/examples/proposal/generate-proposal.cjs
    curl -fsSLO https://www.tmplvision.io/examples/proposal/proposal-data.json
    curl -fsSLO https://www.tmplvision.io/examples/proposal/proposal-no-support-data.json

    Windows PowerShell

    mkdir tmplvision-node-example
    cd tmplvision-node-example
    Invoke-WebRequest https://www.tmplvision.io/examples/proposal/generate-proposal.cjs -OutFile generate-proposal.cjs -UseBasicParsing
    Invoke-WebRequest https://www.tmplvision.io/examples/proposal/proposal-data.json -OutFile proposal-data.json -UseBasicParsing
    Invoke-WebRequest https://www.tmplvision.io/examples/proposal/proposal-no-support-data.json -OutFile proposal-no-support-data.json -UseBasicParsing
  3. Set your token, template ID and region, then run the script

    Replace YOUR_API_TOKEN and YOUR_TEMPLATE_ID with your own values. Use us instead of eu for a US account; without TMPLVISION_REGION the script uses eu.

    macOS or Linux

    export TMPLVISION_API_TOKEN='YOUR_API_TOKEN'
    export TMPLVISION_TEMPLATE_ID='YOUR_TEMPLATE_ID'
    export TMPLVISION_REGION='eu'
    node generate-proposal.cjs

    Windows PowerShell

    $env:TMPLVISION_API_TOKEN='YOUR_API_TOKEN'
    $env:TMPLVISION_TEMPLATE_ID='YOUR_TEMPLATE_ID'
    $env:TMPLVISION_REGION='eu'
    node generate-proposal.cjs

    On success the script prints Saved proposal.pdf and writes the file into the current folder. Each successful run uses one document generation from your monthly quota.

    A token typed on the command line can end up in your shell history. In your application, take it from your server's environment or secret store instead.

  4. Open the PDF and compare it

    Open proposal.pdf and compare it with the reference PDF (saved as reference-proposal.pdf, so it doesn't collide with your result). With the example template and data, both show the same client, three line items, a €2,600.00 total and the support offer. The files are not byte-identical because PDF metadata such as the creation date differs.

  5. Run it again without the support section

    The script always writes proposal.pdf and replaces an existing file of that name. Rename the first result, then pass the second data file as an argument. In the same terminal:

    macOS or Linux

    mv proposal.pdf proposal-with-support.pdf
    node generate-proposal.cjs proposal-no-support-data.json

    Windows PowerShell

    Rename-Item proposal.pdf proposal-with-support.pdf
    node generate-proposal.cjs proposal-no-support-data.json

    The new proposal.pdf has the same items and total, but no support paragraph. Compare it with the reference PDF without support.

What the script sends

The request and the response

This is the complete downloadable script. Use the same request in your own application; the full endpoint reference is in the API documentation.

// Node.js 22+. Keep this file and proposal-data.json in the same folder.
const { readFile, writeFile } = require('node:fs/promises');
const path = require('node:path');

async function main() {
    const apiToken = process.env.TMPLVISION_API_TOKEN;
    const templateId = process.env.TMPLVISION_TEMPLATE_ID;
    const region = process.env.TMPLVISION_REGION || 'eu';
    if (!apiToken || !templateId) {
        throw new Error('Set TMPLVISION_API_TOKEN and TMPLVISION_TEMPLATE_ID first.');
    }
    if (!['eu', 'us'].includes(region)) throw new Error('TMPLVISION_REGION must be eu or us.');
    const input = process.argv[2] || path.join(__dirname, 'proposal-data.json');
    const variables = JSON.parse(await readFile(input, 'utf8'));
    const form = new FormData();
    form.append('data', JSON.stringify({ templateId, variables }));
    const response = await fetch(`https://${region}.api.tmplvision.io/v1/generation/pdf`, {
        method: 'POST',
        headers: { 'x-apitoken': apiToken },
        body: form,
        signal: AbortSignal.timeout(120000),
    });
    if (response.status !== 201) {
        throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }
    const pdf = Buffer.from(await response.arrayBuffer());
    if (pdf.subarray(0, 5).toString() !== '%PDF-') throw new Error('The API did not return a PDF.');
    await writeFile('proposal.pdf', pdf);
    console.log('Saved proposal.pdf');
}
main().catch(error => {
    // fetch reports network problems as "fetch failed"; the reason is in error.cause.
    const cause = error.cause?.message || error.cause?.code;
    console.error(cause ? `${error.message}: ${cause}` : error.message);
    process.exitCode = 1;
});

Request

  • POST https://eu.api.tmplvision.io/v1/generation/pdf (or us.api)
  • Header x-apitoken with your API token
  • Multipart form body with one text field data
  • data is JSON.stringify({ templateId, variables })

Don't set a Content-Type header yourself. When the body is a FormData object, fetch sets multipart/form-data with a generated boundary.

Response

On success, the API answers with HTTP 201 and the PDF file itself as the body. It does not return JSON or a download link.

The script reads the body as an ArrayBuffer, checks that it starts with %PDF- and only then writes the file. Any other status is reported with the API's JSON error message, and nothing is saved.

Line items and optional sections

What the Word template does with the data

This is the complete content of proposal-data.json. It is the variables object only; the script adds templateId around it.

{
  "reference": "NP-2026-014",
  "issued": "24 October 2026",
  "validUntil": "23 November 2026",
  "company": "Maple & Moss Studio",
  "contact": "Alex Morgan",
  "project": "Client onboarding automation",
  "summary": "Connect your enquiry form to your CRM and generate a consistent PDF proposal for each qualified enquiry.",
  "items": [
    {
      "description": "Workflow discovery and field mapping",
      "qty": 1,
      "rate": "€480.00",
      "amount": "€480.00"
    },
    {
      "description": "Form and CRM integration",
      "qty": 2,
      "rate": "€720.00",
      "amount": "€1,440.00"
    },
    {
      "description": "Proposal template and handover",
      "qty": 1,
      "rate": "€680.00",
      "amount": "€680.00"
    }
  ],
  "total": "€2,600.00",
  "delivery": "10 working days after access and final copy are supplied.",
  "support": true,
  "supportFee": "€120.00"
}
01

Values

{{company}} or {{total}} in the document are replaced with the matching value.

02

One table row per item

The table row starts with {{#each items}} in its first cell and ends with {{/each}} in its last. Add or remove entries in items and the table gets one row per entry.

03

Optional paragraph

{{#if support}}…{{/if}} wraps the support offer. proposal-no-support-data.json is identical except for "support": false, so the paragraph is left out.

Your application supplies every value, including rates, line amounts and the total. TmplVision inserts them as written and does not calculate amounts, totals or taxes. If you change the line items, calculate and format total in your own code. The example uses preformatted EUR strings such as "€2,600.00".

To change the wording or layout instead of the data, edit the DOCX. With the edited example DOCX uploaded and activated as a second template, the same command and data produce a PDF with a revised introduction, as in this reference PDF. Only TMPLVISION_TEMPLATE_ID changes.

The reference PDFs were generated from these templates and data files with TmplVision's document renderer. The script's request and its error handling are checked automatically against simulated API responses, without a real account. The same request format is used by our n8n guide, whose runs we tested against the EU API. Fonts and pagination of your own templates can differ, so check a few outputs before sending them to clients.

Troubleshooting

If the script fails

The script prints one error line and exits with code 1. Lines starting with HTTP contain the API's JSON error; the important part is its message.

Set TMPLVISION_API_TOKEN and TMPLVISION_TEMPLATE_ID first.
One of the two variables is missing in this terminal session. Set both again (step 3) in the window where you run node. In PowerShell, use $env: as shown; set or export do not set environment variables there.
TMPLVISION_REGION must be eu or us.
Set the region to exactly eu or us, in lowercase.
HTTP 403 with Not Authenticated.
The API doesn't know the token. Check that you copied the whole token and that the region matches your account: a token from an EU account is unknown to the US API, and the other way round.
HTTP 403 with API token has expired. or Forbidden to access given template.
Create a new token, or use a token from the same organisation as the template.
HTTP 404 with Template with id … not found.
The template ID is wrong or belongs to the other region. Copy the ID again from the template in your account.
HTTP 400 with Template with id … is not active.
Open the template in TmplVision, click Activate and confirm.
HTTP 402 with Monthly document generation quota exceeded.
Your plan's monthly generations are used up. See pricing.
The API did not return a PDF.
The response had status 201 but its body was not a PDF, so the script saved nothing. Check that the request URL in the script is unchanged and that no proxy replaces the response.
fetch failed followed by a reason
No connection to the API host, for example a DNS problem (ENOTFOUND), a firewall or a proxy. Check that the machine can reach eu.api.tmplvision.io or us.api.tmplvision.io over HTTPS.
The operation was aborted due to timeout
The script waits up to 120 seconds for the PDF. Run it again; if it keeps happening, check your connection to the API host.
ENOENT: no such file or directory or a JSON syntax error
Run the command in the folder that contains the JSON file, and check the file name. If you edited the data, make sure it is still valid JSON: double quotes, no trailing commas.

Connect your own Word template.

Upload the document you already use, mark the fields that change and send your application's data with the same request. All example files stay free to download without an account.