Set Word Merge Field Switches and Conditional Fields with JavaScript in React

Merge fields have two ways of changing the output without changing the replacement step, and both are easy to overlook.

One is the field switch: codes such as \b, \f and \* Upper that follow the field name inside the field code, and make the same value land in the document in a different shape — with a prefix, a suffix, or converted to upper case. A switch is part of the template, so editing the template edits the behaviour.

The other is the conditional field: the document hard-codes no sentence at all, but carries an IF field that lets Word decide, when the document is saved, which sentence the current data calls for. It is assembled by code on the spot.

Spire.Doc for JavaScript builds both entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage the template document — no backend server required.

This article covers two core features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Use Field Switches in a Merge Field

Prepare the Template

To lay the output of every switch out side by side for comparison, the template makes the same field, XX_Name, appear nine times, each occurrence carrying a different switch:

Contact

Default: «XX_Name»
\b "Mr ": «XX_Name»
\b Mr: «XX_Name»
\f " (Author)": «XX_Name»
\* Upper: «XX_Name»
\* Lower: «XX_Name»
\* FirstCap: «XX_Name»
\* Caps: «XX_Name»
\* MERGEFORMAT: «XX_Name»

With the field codes shown, each line in Word looks roughly like this ({ } are Word's field braces, which you type with Ctrl + F9):

{ MERGEFIELD  XX_Name  \b "Mr "  \* MERGEFORMAT }
{ MERGEFIELD  XX_Name  \b Mr  \* MERGEFORMAT }
{ MERGEFIELD  XX_Name  \f " (Author)"  \* MERGEFORMAT }
{ MERGEFIELD  XX_Name  \* Upper  \* MERGEFORMAT }
…

Spaces separate the field name from the switch and the switches from one another. If the text argument of a switch itself contains a space, wrap it in double quotes (\b "Mr ") — in that case the space inside the quotes is part of the argument.

The Code

function App() {
  const MailMergeSwitches = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
    // Load the sample template into the virtual file system (VFS)
    let inputFileName = "MailMergeSwitches.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);

    // Create a document and load the template
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Provide a value for a single field; all nine occurrences use it
    let fieldNames = ["XX_Name"];
    let fieldValues = ["jack tang"];

    // Execute the mail merge
    doc.MailMerge.Execute(fieldNames, fieldValues);

    // Define the output file name
    const outputFileName = "MailMergeSwitches-result.docx";

    // Save the document
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    doc.Dispose()

    // Read the saved file and convert it to a Blob
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], {type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"});
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Merge data into a Word template that carries field switches</h1>
      <button onClick={MailMergeSwitches}>Generate</button>
    </div>
  );
}
export default App;

There is no switch-related API on the code side — every switch lives in the template's field codes, and Execute only has to hand XX_Name a value. That is exactly what a field switch is for: the data stays the same and the template decides the shape.

The value here is deliberately written in lower case, as "jack tang". Passing "Jack Tang" would make the output of the case-related switches look exactly like the default line, so the difference would not be visible.

The Result

What the template says Output
«XX_Name» jack tang
«XX_Name \b "Mr "» Mr jack tang
«XX_Name \b Mr» Mrjack tang
«XX_Name \f " (Author)"» jack tang (Author)
«XX_Name \* Upper» JACK TANG
«XX_Name \* Lower» jack tang
«XX_Name \* FirstCap» Jack tang
«XX_Name \* Caps» Jack Tang
«XX_Name \* MERGEFORMAT» jack tang

A few points worth noting:

  • The difference between \b "Mr " and \b Mr is that pair of quotes. The argument of the first one is Mr with a trailing space, which outputs Mr jack tang; the argument of the second is Mr with no space, so it sits flush against the value and becomes Mrjack tang. To leave a gap between the added text and the working text, either put the space inside the quotes or write the argument as \b "Mr ".
  • \f is a suffix and \b is a prefix, and both can appear in the same field.
  • \* FirstCap capitalises only the first character (Jack tang), while \* Caps capitalises the first letter of every word (Jack Tang). The two switches have very similar names but different effects.
  • \* MERGEFORMAT does not affect the text. It preserves the field's formatting, Word adds it by default when it creates a merge field, and leaving it in does no harm.

Build and Execute a Conditional Field

The Approach

The structure of a conditional field (an IF field) is "one field, holding a nested merge field as the test, followed by two result texts":

{ IF { MERGEFIELD Count } > "1" "Greater than one" "Less than one" }

Taken apart, this stretch contains four kinds of element:

  1. an IfField, whose field type is set to FieldType.FieldIf;
  2. a merge field node (FieldType.FieldMergeField) — the value being compared in the condition;
  3. the operator and the two branch texts, appended as ordinary text fragments;
  4. a FieldMark of type FieldMarkType.FieldEnd, which closes the field off — it also has to be assigned back to IfField.End for the field to be complete.

The Code

This document has no input template at all; both IF fields are built from scratch:

function App() {
  const ExecuteConditionalField = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the ARIALUNI.TTF font into the virtual file system (VFS)
    await window.spire.FetchFileToVFS('ARIALUNI.TTF','/Library/Fonts/',`${process.env.PUBLIC_URL}/static/font/`);

    // Create a document and add one section with two paragraphs
    let doc = new docModule.Document();
    let section = doc.AddSection();

    let paragraph = section.AddParagraph();
    let ifField1 = new docModule.IfField(doc);
    ifField1.Type = docModule.FieldType.FieldIf;
    ifField1.Code = "IF ";
    paragraph.Items.Add(ifField1);
    paragraph.AppendField("Count", docModule.FieldType.FieldMergeField);
    paragraph.AppendText(" > ");
    paragraph.AppendText("\"1\" ");
    paragraph.AppendText("\"Greater than one\" ");
    paragraph.AppendText("\"Less than one\"");
    let end1 = doc.CreateParagraphItem(docModule.ParagraphItemType.FieldMark);
    end1.Type = docModule.FieldMarkType.FieldEnd;
    paragraph.Items.Add(end1);
    ifField1.End = end1;

    paragraph = section.AddParagraph();
    let ifField2 = new docModule.IfField(doc);
    ifField2.Type = docModule.FieldType.FieldIf;
    ifField2.Code = "IF ";
    paragraph.Items.Add(ifField2);
    paragraph.AppendField("Age", docModule.FieldType.FieldMergeField);
    paragraph.AppendText(" > ");
    paragraph.AppendText("\"50\" ");
    paragraph.AppendText("\"The old man\" ");
    paragraph.AppendText("\"The young man\"");
    let end2 = doc.CreateParagraphItem(docModule.ParagraphItemType.FieldMark);
    end2.Type = docModule.FieldMarkType.FieldEnd;
    paragraph.Items.Add(end2);
    ifField2.End = end2;

    // Provide values for the merge fields inside the conditional fields
    let fieldNames = ["Count", "Age"];
    let fieldValues = ["2", "30"];
    doc.MailMerge.Execute(fieldNames, fieldValues);

    // Make the fields evaluate when the document is saved
    doc.IsUpdateFields = true;

    // Define the output file name
    const outputFileName = "ExecuteConditionalField-result.docx";

    // Save the document
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    doc.Dispose()

    // Read the saved file and convert it to a Blob
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], {type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"});
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Create and execute a conditional field</h1>
      <button onClick={ExecuteConditionalField}>Generate</button>
    </div>
  );
}
export default App;

A few details that have to happen in this order:

  1. ifField.Code = "IF " only marks the field type. What actually assembles the body of the field is the AppendField and the AppendText calls that follow — they are laid out into this field one after another.
  2. The operator and the two branch texts are all appended with AppendText, and the quotes among them have to be written into the string itself ("\"1\" "). An IF field requires both result texts to be quoted, and these quote characters are what does it.
  3. The order is fixed: compared value → operator → comparison basis → true branch → false branch. The false branch needs no extra space after its closing quote.
  4. FieldEnd is the field's closing mark. It has to be created with document.CreateParagraphItem(wasmModule.ParagraphItemType.FieldMark), assigned to end.Type, appended to the paragraph, and then given to ifField.End. Skip any one of those steps and the field is incomplete.
  5. document.IsUpdateFields = true decides whether the field is evaluated when the document is saved — see below.

The Result

With Count = "2" and Age = "30", the two conditional fields take the true branch and the false branch respectively:

Greater than one
The young man

Swapping the values flips the result, without touching either the template or the code:

Field values First paragraph (IF Count > "1") Second paragraph (IF Age > "50")
Count = "2", Age = "30" Greater than one The young man
Count = "0", Age = "70" Less than one The old man

FAQ

The conditional field saves as two blank paragraphs

Cause: document.IsUpdateFields = true was not set. A field in the document is only an instruction waiting to be evaluated; without that switch turned on it is not executed when the document is saved, and neither branch's text is output — even the text of the false branch disappears along with it, which is why the result looks empty.

Solution: Between Execute and SaveToFile, add:

document.MailMerge.Execute(fieldNames, fieldValues);
document.IsUpdateFields = true;   // Make the fields evaluate when the document is saved
document.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });

The prefix added by \b is stuck to the name

Cause: the text argument of a switch carries no space of its own. The argument of \b Mr is the three characters Mr, and the spacing around it in the output is decided entirely by the argument itself.

Solution: to leave a gap between the name and the prefix, write the space into the argument and wrap it in quotes:

{ MERGEFIELD  XX_Name  \b "Mr "  \* MERGEFORMAT }

\f works the same way. The reverse also works — leave the space outside the field by having it followed by a space in Word — but that is paragraph text, a different thing from a switch.

What is the difference between \* FirstCap and \* Caps

\* FirstCap converts only the first character to upper case and leaves the rest alone: jack tang → Jack tang.

\* Caps converts the first letter of every word to upper case: jack tang → Jack Tang.

To see the difference between these two switches, the value behind the field name has to be lower case or mixed case; if the value is already Jack Tang, both lines come out exactly like the default line.

Some switches have no effect at all

Not every Word field switch takes part in the text replacement of a mail merge. \* MERGEFORMAT preserves the field's formatting and never changes the text to begin with; switches such as \m, \v and \# 0.00 (a number format) have no visible effect either when used on a merge field with a plain text value like the one here. In practice, it is best to stick to the ones verified in this article: \b, \f, \* Upper, \* Lower, \* FirstCap and \* Caps.

How are the spaces between the field name, switches and arguments counted

  • Between the field name and the first switch: at least one space;
  • Between switches: at least one space;
  • For a switch's text argument: a space is needed between it and the switch letter too (\b and Mr), and once the argument itself contains a space or a special character, wrap it in double quotes.

Extra spaces do not affect parsing, but leaving one out lets the argument be swallowed by the previous token — write \* Upper as \*Upper, for example, and on some parsing paths the switch stops working.

Changing the values of Count and Age does not change the output

Cause: the result text of a conditional field is cached in the document and is only evaluated again while IsUpdateFields is on. At the same time, check that the field names handed to Execute match the names written in AppendField (the comparison is case-sensitive).

Solution: confirm that fieldNames matches the first argument of AppendField exactly, and keep IsUpdateFields = true.


Get a Free License

Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.