TutorialMarch 15, 2026·4 min read

Docxtemplater Template Plugin for React with Variable Highlighting

A React plugin for editing and previewing docxtemplater DOCX templates. Color-coded variable highlighting, loop and conditional detection, and an interactive annotation panel for your template editor.

Template tags are invisible, and that's the problem

A paralegal opens a 20-page lease agreement in Word. Somewhere in there are {tenant_name}, {lease_start_date}, {monthly_rent}, and maybe 40 other placeholders. They all look like regular text. Nothing distinguishes {landlord_address} from the boilerplate around it.

This is how most docxtemplater workflows look. You author a .docx template, scatter {variables} through it, and hope you didn't miss one or misspell {recipeint_name}. The templating engine doesn't care about typos, it just leaves them in the output.

For a one-page letter, this is fine. For a 30-page commercial lease with nested line items, conditional clauses, and variables in headers and footers, it's a real problem. Paralegals and legal ops teams end up Ctrl+F-ing through documents looking for curly braces, manually cross-referencing variable names against a spreadsheet.

The template plugin solves this by making every tag visible: highlighted inline, color-coded by type, with an annotation panel that shows the full template structure at a glance.

Docxtemplater template preview with variable highlighting and annotation panel

What it does

The plugin adds three things to the editor:

  1. Inline highlighting so template tags get colored backgrounds in the document text, making {tenant_name} visually distinct from the words around it
  2. Highlight overlay that renders colored rectangles over tags on the page, with hover and selection states
  3. Annotation panel on the side listing every tag, anchored to its position in the document

Each tag type gets its own highlight:

TagTypeWhat it does
{name}VariableReplaced with a value
{#items}Section startStarts a loop or conditional
{/items}Section endCloses the loop or conditional
{^active}InvertedRenders when value is falsy

In a lease agreement, you'd see amber highlights on {tenant_name}, {lease_start_date}, {monthly_rent}, and blue highlights wrapping {#additional_provisions}...{/additional_provisions} where optional clauses get looped in.

Setup

npm install @eigenpal/docx-js-editor
import { DocxEditor, templatePlugin } from "@eigenpal/docx-js-editor";
 
function TemplatePreview({ buffer }: { buffer: ArrayBuffer }) {
  return (
    <DocxEditor
      documentBuffer={buffer}
      plugins={[templatePlugin]}
    />
  );
}

Upload a DOCX with {variables} in it, and the plugin picks them up automatically. No configuration, no tag registry.

If you want to control the panel:

import { DocxEditor, createTemplatePlugin } from "@eigenpal/docx-js-editor";
 
const plugin = createTemplatePlugin({
  defaultCollapsed: false,
  panelPosition: "right",
  panelWidth: 280,
});

The panel is resizable (200 to 400px) and collapsible.

How detection works

The plugin scans every text node in the document and picks up all standard docxtemplater syntax:

  • {variable} for simple variables
  • {nested.property} for dot-notation access (useful for things like {tenant.address.city})
  • {#section} / {/section} for loops or conditionals
  • {^inverted} for inverted conditionals (renders when the value is falsy)

The prefix character determines the tag type. No prefix means it's a plain variable.

Section nesting

The parser tracks a section stack. When it hits {#line_items}, it pushes onto the stack. Any variables found inside, like {description}, {quantity}, {unit_price}, get marked as insideSection and added to the parent's nestedVars array:

if (type === 'sectionStart' || type === 'invertedStart') {
  tag.nestedVars = [];
  sectionStack.push(tag);
} else if (type === 'variable' && sectionStack.length > 0) {
  const section = sectionStack[sectionStack.length - 1];
  section.nestedVars?.push(name);
  tag.insideSection = true;
}

This is how the annotation panel knows to show nested variables under a section chip. The {#line_items} chip shows description, quantity, unit_price as sub-items.

The annotation panel

The panel renders chips anchored to each tag's vertical position in the document. Variables get a colored dot. Structural tags get a typed badge like LOOP / IF or IF NOT. Section chips expand to show their nested variables.

Clicking a chip scrolls the editor to that tag and selects it. Hovering a chip highlights the corresponding tag in the document, and vice versa.

For a paralegal reviewing a template, this means they can scan the panel to see every variable in the document without scrolling through 20 pages. If {guarantor_name} shows up and it shouldn't be there, they spot it in the panel and click to jump to it.

Filling templates

The preview plugin shows you where the tags are. To substitute values, use processTemplate:

import { processTemplate } from "@eigenpal/docx-js-editor";
 
const filledBuffer = processTemplate(templateBuffer, {
  tenant_name: "Jane Smith",
  lease_start_date: "April 1, 2026",
  monthly_rent: "2,400",
  landlord_name: "Acme Properties LLC",
});

If you need to know what got filled and what didn't:

import { processTemplateDetailed } from "@eigenpal/docx-js-editor";
 
const result = processTemplateDetailed(templateBuffer, values, {
  nullGetter: "keep",   // "keep" leaves unfilled tags as-is, "empty" removes them
  linebreaks: true,
});
 
console.log(result.replacedVariables);   // ["tenant_name", "lease_start_date", ...]
console.log(result.unreplacedVariables); // ["guarantor_name"]

Validation

You can check for syntax errors before processing, things like unclosed tags or malformed braces:

import { validateTemplate, getMissingVariables } from "@eigenpal/docx-js-editor";
 
const validation = validateTemplate(templateBuffer);
 
if (!validation.valid) {
  console.error(validation.errors);
  // [{ message: "Unclosed tag: provisions", type: "parse" }]
}
 
const missing = getMissingVariables(validation.tags, {
  tenant_name: "Jane Smith",
  // lease_start_date is missing
});
// ["lease_start_date"]

For legal documents especially, catching an unclosed {#provisions} tag before the document goes out beats catching it after.

Scanning headers, footers, and footnotes

The editor plugin highlights tags in the visible body. If you also need to scan headers, footers, footnotes, and text boxes (common in legal templates where the firm name is in the header or a matter ID is in the footer), use detectVariablesDetailed:

import { detectVariablesDetailed } from "@eigenpal/docx-js-editor";
 
const result = detectVariablesDetailed(document);
 
console.log(result.variables);        // ["amount", "client_name", "date", ...]
console.log(result.totalOccurrences); // 15
console.log(result.byLocation);
// {
//   body: ["tenant_name", "lease_start_date", "monthly_rent"],
//   headers: ["firm_name", "matter_id"],
//   footers: ["firm_name"],
//   footnotes: [],
//   endnotes: [],
//   textBoxes: [],
// }

Next steps