Skip to Content
HelpDocuments & TemplatesHow Tag Templates Work

What Are Tag Templates?

Tag Templates are the standard document format in ScopeStack. They use tags — short references enclosed in curly braces — to pull project data into a Word, PowerPoint, or Excel file and produce a finished document for the client.

Tag Templates replaced the older Mail Merge (V1) format. If you are building new templates, use Tag Templates. The legacy format continues to work but is not recommended for new work.

Formatting Options

When you upload a Tag Template, you can enable Formatting for Tag Templates. This option lets you apply date, time, and currency formatting directly in your template using wrapper methods like .to_currency and .to_short_date.

See the Tag Template Formatting article for full details on when to enable this and how it changes the tag syntax.

Viewing Merge Data

Before writing template tags, it helps to see the actual data available for a specific project. ScopeStack includes a Merge Data view for this purpose.

Accessing the view: Open any project, click the gear icon next to the project name, and select View Merge Fields V2. The panel shows each field path and its current value for that project, letting you confirm exact paths before adding them to a template.

The menu also has View Merge Fields, which shows the older V1 data. Use the V2 item for Tag Templates.

The view always shows raw values. It is built without the template formatting option, so fields that support a wrapper such as .to_short_date are listed with their underlying value and the wrapper sub-keys are not shown. Use it to confirm a field’s path, not to discover which fields accept a formatting wrapper.

Use the V2 Merge Data view — not the raw API response — as your reference when building or debugging templates. The render-time data includes computed fields like unique, unique_count, and renamed entities that do not appear in the API.

Debugging Templates: Merge Data View vs. Raw API

The Merge Data view and the raw API look like two ways to access the same information. They are not. Understanding the difference prevents a common class of confusing failures.

The Merge Data view (/projects/{id}/merge_data_visualization?version=2, accessed from the gear menu) shows data after the V2 transformation layer has run. This is what the template engine actually sees at render time. Use this as your reference when building or debugging any Tag Template.

The raw API endpoint (/projects/{id}/merge-data) returns data before that transformation runs. Several fields that exist at render time are absent from the raw API response because they are synthesized during rendering, not stored in the database.

Fields that are present at render time but absent from the raw API include:

  • unique — the deduplication boolean on each service. Do not use {#unique?} in templates; the ? suffix crashes the V2 parser.
  • unique_count — the count of how many times a service name appears across the project.
  • services and subservices — V2 renames tasks to services and features to subservices. The raw API still returns the old names. If the API shows tasks but your template uses services, that is correct and expected.
  • The nested locations > phases > services path used in location-first templates. The raw API returns a different data structure. The Merge Data view shows the V2-transformed shape.

If a field appears missing when you browse the API directly but works correctly in a generated document, the V2 transformation is the explanation. Always debug templates against the Merge Data view, not the raw API.


Where Does the Data Come From?

Tag Templates pull from ScopeStack’s structured project data, delivered in JSON format. JSON is the industry standard for document generation — it is text-based, compact, and human-readable.

Here is a simplified example of what JSON data looks like, using the first U.S. Congress as a stand-in:

{ "president": { "first_name": "George", "last_name": "Washington", "state": "Virginia" }, "vice_president": { "first_name": "John", "last_name": "Adams", "state": "Massachusetts" }, "representatives": [ { "first_name": "Fisher", "last_name": "Ames", "state": "Massachusetts" }, { "first_name": "John Baptista", "last_name": "Ashe", "state": "North Carolina" } ] }

Two concepts matter here:

  • Object: A set of named attributes, wrapped in curly braces. president is an object with attributes first_name, last_name, and state.
  • Array: A collection of objects, wrapped in square brackets. representatives is an array of individual people.

These two concepts — objects and arrays — drive how sections and loops behave in Tag Templates.

Data Context

Before getting into the specific tags, it helps to understand data context. At any point in a document, the merge process is focused on a particular part of the data — this is the current context. Special tags let you shift the context to zoom in on a specific object or loop through an array.

Section Tags

The core building block of Tag Templates is the section tag pair:

{#path.to.something} ...content... {/path.to.something}

A section always starts with {#...} and ends with {/...}. The path in both tags must match. What happens inside the section depends on what path.to.something points to.

If the path points to an object, the context shifts to that object. Tags inside the section can reference that object’s attributes directly.

Example:

{#president} The first President of the United States was {first_name} {last_name} of {state}. {/president} {#vice_president} His Vice President was {first_name} {last_name} from the state of {state}. {/vice_president}

Output:

The first President of the United States was George Washington of Virginia. His Vice President was John Adams from the state of Massachusetts.

If the path points to an array, the section loops — everything inside repeats once for each item in the array.

Example:

The House of Representatives was as follows: {#representatives} - {last_name}, {first_name} of {state} {/representatives}

Output:

The House of Representatives was as follows: - Ames, Fisher of Massachusetts - Ashe, John Baptista of North Carolina - ...

One caution with loops: any text inside the section repeats for every item. If you include an introductory line inside the loop by mistake, it will repeat for each item in the array.

Conditional Sections

A section tag can also act as a conditional. If the path points to a simple value rather than an object or array, the section renders only when that value is truthy.

You can also use comparisons inside section tags:

{#representatives} {#state=="Massachusetts"} - {first_name} {last_name} {/state=="Massachusetts"} {/representatives}

This renders only the representatives from Massachusetts. Supported comparisons include equal (==), not equal (!=), greater than (>), and less than (<).

Phases in a Template

There are two ways a tag template can handle phases, and the difference decides whether a newly added phase appears on its own.

Looping over every phase. The template iterates the phases it is given and prints whatever phases the project has, so a phase you add later appears automatically.

A separate block per phase. The template names each phase explicitly, usually so each one can carry its own heading and introductory wording. A phase with no block will not appear in that document whatever its services look like, and adding the phase in ScopeStack does not add it to the template.

Per-phase blocks are matched on the phase’s code, not its name:

{#locations} {#phases} {#slug=="T_M"} {#services.length>0} Time and Materials Services {#services} {name} {#subservices} ({quantity}) {name} {/subservices} {/services} {/services.length>0} {/slug=="T_M"} {/phases} {/locations}

Three things about that block are easy to get wrong:

  • The code is not the phase name, and it is case-sensitive. A phase displayed as Time_Materials may have the code T_M. The reliable place to read it is the Merge Data view — open the project, then the gear icon > View Merge Fields V2, and read slug on the phase. Copy it exactly; t_m will not match T_M.
  • One exception. If your block sits inside a lines-of-business loop, that copy of the slug is lowercased — t_m rather than T_M. Read the slug from the same place in the Merge Data view that your template loops over, and copy what you see there.
  • The heading and the service list are separate. The heading and any introductory wording sit inside the {#services.length>0} guard but outside {#services}; the service and subservice lines sit inside {#services}. Adding only the heading gives you a heading with nothing beneath it. That guard is also what suppresses an empty heading for a phase with no services at a given location.

The simplest way to add a phase is to copy an existing phase’s block and change the code.

A template may also carry per-phase blocks in a narrative section, nested inside a language field. Those follow the same pattern and print the phase’s rolled-up sentences rather than a service list:

{#language_fields} {#slug=="tech_solution"} {#phases} {#slug=="T_M"} {#sentences} {.} {/sentences} {/slug=="T_M"} {/phases} {/slug=="tech_solution"} {/language_fields}

If you add a phase to one and not the other, it will appear in one part of the document and not the other.

HTML Content Tags

Fields that use Markdown — such as service descriptions and language fields — have a formatted_ equivalent in the merge data. Use a double-tilde tag to convert that HTML content into formatted document text:

{~~formatted_service_description}

Without the double tilde, the raw HTML markup will appear in the document instead of the formatted text.

The rendered content takes on the paragraph style of the line where the tag sits — that is how you control its font and how lists are styled. See Styling rich text and lists.

Image Tags

To embed an image, use a tag starting with %:

{%project.account_logo_url}

Math in Tags

Tags support basic math expressions. For example:

{#services} {name} will cost {hourly_rate * extended_hours}. {/services}

This lets you calculate totals and derived values directly in the template without pre-processing the data.


Quick Reference

All examples below require V2 merge data. If tags are not replaced in your output, confirm the template is configured for V2 and that the Merge Data view shows the expected fields for your project.

PurposeSyntax
Insert a text field{field}
Conditional block (show when truthy){#field}{/field}
Conditional block (show when falsy){^field}{/field}
Loop over an array{#items}{/items}
Rich text / HTML rendering{~~formatted_field}
Inline math{hourly_rate * extended_hours}
Image embed{%project.account_logo_url}
String equality check{#field=="value"}{/field=="value"}
Negated equality check{^field=="value"}{/field=="value"}
Array length comparison{#items.length > 1}{/items.length > 1}

For the full set of pipe filters, include-formatting dot accessors, deduplication patterns, and expression recipes, see Template Expression Reference.

New to ScopeStack?

ScopeStack automates scoping, pricing, and SOW generation for IT services teams. See how it fits your process.

Book a demoBrowse the docs
Last updated on