Skip to main content
Learn

Custom Documentation with Handlebars Templates

โฑ 2 min read๐ŸŸก Intermediate ยท 30-45 minutesEdit on GitHub โ†—

Overviewโ€‹

Extend your documentation with custom Handlebars templates when you need more control than the built-in calm-widgets provide.

Learning objectivesโ€‹

By the end of this tutorial, you will:

  • Know when to use raw Handlebars instead of (or alongside) calm-widgets
  • Use core Handlebars syntax: {{#each}}, {{#if}}, {{#unless}}
  • Use CALM-specific helpers such as eq, kebabToTitleCase, join, json
  • Create a node inventory template that filters by node type
  • Create a relationship details template with conditional formatting
  • Combine widgets and raw Handlebars in the same template

Prerequisitesโ€‹

Complete Custom Documentation with CALM Widgets first.

Step-by-Step Guideโ€‹

1. When to Use Raw Handlebarsโ€‹

In the Custom Documentation with CALM Widgets lesson you learned calm-widgets provide ready-made components. Use raw Handlebars when you need to:

  • Create custom filtering logic (e.g., only show nodes of a certain type)
  • Format data in ways widgets don't support
  • Build complex conditional documentation
  • Generate non-markdown outputs (CSV, HTML, etc.)

2. Handlebars Basicsโ€‹

Handlebars is a simple templating language:

SyntaxDescriptionExample
{{property}}Output a value{{name}}
{{#each array}}...{{/each}}Loop over arrays{{#each nodes}}{{name}}{{/each}}
{{#if condition}}...{{/if}}Conditional{{#if description}}...{{/if}}
{{#unless condition}}...{{/unless}}Negative conditional{{#unless @last}}, {{/unless}}
{{@index}}Current index in loop{{@index}}. {{name}}
{{@last}}Is last item in loopFor comma-separated lists
{{this}}Current item{{#each tags}}{{this}}{{/each}}

3. CALM-Specific Helpersโ€‹

The docify command includes custom helpers:

HelperDescriptionExample
eqEquality check{{#if (eq node-type "service")}}
lookupAccess property by name{{lookup this 'unique-id'}}
jsonOutput as JSON{{json metadata}}
kebabToTitleCaseFormat text{{kebabToTitleCase node-type}}
kebabCaseConvert to kebab-case{{kebabCase name}}
isObjectCheck if object{{#if (isObject data)}}
isArrayCheck if array{{#if (isArray items)}}
joinJoin array elements{{join tags ", "}}

4. Create a Node Inventory Templateโ€‹

Create a template that filters nodes by type โ€” something the widgets don't do directly:

File: templates/node-inventory.md

---
architecture: ../architectures/ecommerce-platform.json
---
# Node Inventory: {{metadata.description}}

| Name | Type | ID | Description |
|------|------|-------|-------------|
{{#each nodes}}
| {{name}} | {{kebabToTitleCase node-type}} | `{{unique-id}}` | {{description}} |
{{/each}}

## Services Only

{{#each nodes}}
{{#if (eq node-type "service")}}
- **{{name}}** (`{{unique-id}}`): {{description}}
{{/if}}
{{/each}}

## Databases Only

{{#each nodes}}
{{#if (eq node-type "database")}}
- **{{name}}** (`{{unique-id}}`): {{description}}
{{/if}}
{{/each}}

## Actors

{{#each nodes}}
{{#if (eq node-type "actor")}}
- **{{name}}**: {{description}}
{{/if}}
{{/each}}

5. Preview in VSCodeโ€‹

Just like in the Custom Documentation with CALM Widgets lesson, you can preview this template in VSCode:

  1. Open templates/node-inventory.md in VSCode
  2. Open the CALM preview pane (Ctrl+Shift+C / Cmd+Shift+C) to see the rendered output

6. Create a Relationship Details Templateโ€‹

File: templates/relationship-details.md

---
architecture: ../architectures/ecommerce-platform.json
---
# Relationship Details: {{metadata.description}}

{{#each relationships}}
### {{unique-id}}

{{#if relationship-type.connects}}
**Type:** Connection

| Property | Value |
|----------|-------|
| Source | `{{relationship-type.connects.source.node}}` |
| Destination | `{{relationship-type.connects.destination.node}}` |
{{#if relationship-type.connects.source.interfaces}}
| Source Interfaces | {{#each relationship-type.connects.source.interfaces}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}} |
{{/if}}
{{/if}}

{{#if relationship-type.interacts}}
**Type:** Interaction

- **Actor:** `{{relationship-type.interacts.actor}}`
- **Interacts with:** {{#each relationship-type.interacts.nodes}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
{{/if}}

{{#if relationship-type.composed-of}}
**Type:** Composition

- **Container:** `{{relationship-type.composed-of.container}}`
- **Contains:** {{#each relationship-type.composed-of.nodes}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
{{/if}}

---
{{/each}}

7. Generate Static Files with CLIโ€‹

For CI/CD or static site generation, use the docify CLI:

calm docify \
--architecture architectures/ecommerce-platform.json \
--template templates/node-inventory.md \
--output docs/generated/node-inventory.md

calm docify \
--architecture architectures/ecommerce-platform.json \
--template templates/relationship-details.md \
--output docs/generated/relationship-details.md

8. Combine Widgets and Handlebarsโ€‹

You can mix calm-widgets with raw Handlebars in the same template:

File: templates/combined-view.md

---
architecture: ../architectures/ecommerce-platform.json
---
# {{metadata.description}} - Combined Documentation

## Architecture Overview (widget)
{{block-architecture this}}

## Services Summary (custom Handlebars)
{{#each nodes}}
{{#if (eq node-type "service")}}
### {{name}}
- **ID:** `{{unique-id}}`
- **Description:** {{description}}
{{/if}}
{{/each}}

## All Nodes (widget)
{{table nodes columns="unique-id,name,node-type"}}

Don't forget to commit your progress with git. Capturing a snapshot here keeps your change history clean and meaningful.

Key conceptsโ€‹

When to Use Each Approachโ€‹

ApproachBest For
calm-widgetsQuick visualisations, standard documentation
Raw HandlebarsCustom filtering, complex logic, non-standard formats
CombinedBest of both worlds

Filtering Nodes by Typeโ€‹

The eq helper combined with {{#if}} is the primary filtering mechanism:

{{#each nodes}}
{{#if (eq node-type "service")}}
... only rendered for service nodes ...
{{/if}}
{{/each}}

Debugging Templatesโ€‹

Add {{json nodes}} to output the raw data structure and discover available property names before using them in your template logic.

Next stepsโ€‹

In the next tutorial, you'll use CALM Chat mode as an expert architecture advisor to identify and fix resilience weaknesses in your e-commerce platform!