V1 to V2 Migration Guide
This guide covers the differences between V1 (Mail Merge) and V2 (Tag Templates) in ScopeStack, and how to convert your existing templates.
Syntax Change
V1 templates use Mail Merge syntax with double angle brackets:
<<project_name>>
<<tasks:each(task)>>
<<task.name>>
<<tasks:endEach>>V2 templates use tag syntax with curly braces:
{project.project_name}
{#services}
{name}
{/services}Top-Level Structure
V1 merge data has approximately 90 flat keys at the root level. V2 organizes these into 8 structured sections:
| V2 Section | Contains |
|---|---|
project | Project details, client, contacts, sales exec, account info |
locations | Service hierarchy (locations, phases, services) |
currency | Currency settings |
language_fields | Language and sentence blocks |
project_pricing | Financial rollups and breakdowns |
project_payments | Payment schedule |
survey_responses | Survey data |
meta | Data format identifier (data_format: "v2") |
Entity Renames
These names changed between V1 and V2:
| V1 Term | V2 Term | Where it appears |
|---|---|---|
tasks | services | Array and loop names |
features | subservices | Array and loop names |
resource | resource_name | Field inside service and pricing loops |
task_type | service_category | Category name on services |
Service Hierarchy
V1: Services are nested as locations > lines_of_business > tasks
V2: Services are nested as locations > phases > services
Both versions support the template path locations > phases > services because the render engine synthesizes phase nesting automatically. If your V1 template already uses locations > phases > tasks, changing tasks to services may be all you need.
Value Format Changes
V2 returns raw numbers instead of pre-formatted strings:
| Field | V1 Value | V2 Value |
|---|---|---|
total_revenue | "$1,128.15" | 1128.15 |
service_revenue | "$981.00" | 981.0 |
total_cost | "$388.13" | 388.13 |
total_hours | "4.5" (string) | 4.5 (number) |
In V2, use the toFixed filter to format numbers:
Revenue: {total_revenue | toFixed:2}If a field returns a string that needs numeric formatting, multiply by 1 first:
{project_pricing.total_contract_value * 1 | toFixed:2}Deduplication Changes
V1: Each task has a unique? boolean flag. The phases_with_tasks section provides a unique_phase_tasks array for per-phase dedup.
V2: Each service has a unique boolean flag. Do not use unique? in V2 templates — the ? character crashes the template parser. Use unique (without the question mark) instead.
V2 also adds unique_count, which gives you the total number of services with the same name:
{#services}
{#unique}
({unique_count}) {name}
{/unique}
{/services}V2 eliminates phases_with_tasks and unique_phase_tasks. Deduplication is handled by the unique flag on individual services instead.
Important: Deduplication is global, not per-location or per-phase. A service name appearing in multiple locations only gets unique: true on the first occurrence across the entire project.
Language Fields
V1 has approximately 30 inline language fields on each service (sow_language, deliverables, out, assumptions, formatted_deliverables, etc.).
V2 consolidates all of these into a nested language_fields array on each service. To access a specific language field in V2:
{#language_fields}
{#name=="Deliverables"}
{~~formatted_sentences}
{/name=="Deliverables"}
{/language_fields}Rich Text Fields
V2 uses the ~~ prefix for fields that contain HTML or markdown:
{~~project.formatted_executive_summary}
{~~formatted_service_description}Without ~~, HTML tags appear as raw text in the output.
Migration Checklist
- Rename all
tasksreferences toservices - Rename all
featuresreferences tosubservices - Rename
resourcetoresource_namein pricing/service loops - Replace
unique?withuniquein dedup filters - Replace pre-formatted currency values with
toFixed:2filter calls - Replace inline language fields with
language_fieldsloop and name equality checks - Add
~~prefix to all formatted/rich text fields - Test with the merge data visualization page (set version to V2) to verify field paths