Skip to main content
The JSON renderer produces structured JSON output from your templates. Enable it in document frontmatter:
---
colin:
  output:
    format: json
---
With this setting, Colin writes document.json instead of document.md.

Writing JSON Directly

The simplest approach is writing JSON directly. If your entire document is valid JSON, Colin passes it through:
---
colin:
  output:
    format: json
---

{"name": "Alice", "role": "admin"}
For dynamic values, use the tojson filter to ensure proper escaping:
{"users": {{ users | tojson }}, "count": {{ users | length }}}

JSON Fences

The recommended approach is wrapping your JSON in a code fence. Colin validates the syntax and provides clear error messages if something is malformed:
---
colin:
  output:
    format: json
---

```json
{
  "name": "Alice",
  "role": "admin",
  "active": true
}
```
This is especially useful when generating JSON dynamically, since Colin will catch syntax errors like trailing commas or mismatched braces.

Markdown Structure

Writing JSON in templates gets painful fast. Consider generating a list of users:
[
{% for user in users %}
  {"name": "{{ user.name }}", "role": "{{ user.role }}"}{% if not loop.last %},{% endif %}
{% endfor %}
]
Trailing comma logic, brace matching, quote escaping, bracket balancing - it’s error-prone and difficult to read. The template syntax fights against JSON syntax. Colin solves this by parsing markdown structure into JSON. You write readable markdown; Colin outputs structured JSON. Headers become keys, lists become arrays, and nesting follows header depth:
---
colin:
  output:
    format: json
---

## name
Alice

## role
admin

## tags
- python
- data
- ml

Headers as Keys

Each header becomes an object key. Text content under the header becomes a string value:
---
colin:
  output:
    format: json
---

## title
Weekly Report

## summary
Everything looks good.

Lists as Arrays

Markdown lists under a header become JSON arrays:
---
colin:
  output:
    format: json
---

## languages
- Python
- Rust
- Go

Literal Values

Text under headers is parsed as strings. For numbers, booleans, or complex objects, use a JSON fence. You can use JSON fences anywhere within a markdown-structured document to write literal JSON as a value:
---
colin:
  output:
    format: json
---

## name
Alice

## age
```json
30
```

## active
```json
true
```

## metadata
```json
{"department": "Engineering", "level": 3}
```

Nested Objects

Deeper header levels create nested objects. An ## header containing ### headers becomes a nested structure:
---
colin:
  output:
    format: json
---

## user
### name
Alice

### contact
#### email
[email protected]

#### phone
555-1234
When a header has both text content and child headers, the text goes into a _content key:
## user
Some introductory text about the user.

### name
Alice

Array Elements

Colin provides a custom {% item %}...{% enditem %} block for creating array elements. Combined with loops, this generates arrays of objects:
---
colin:
  output:
    format: json
---

{% for user in users %}
{% item %}
## name
{{ user.name }}

## role
{{ user.role }}
{% enditem %}
{% endfor %}
Each {% item %} block becomes one element in the output array. Items can contain full markdown structure including nested headers and lists.

Validation

The renderer enforces several rules:
RuleBehavior
Duplicate keys at same levelError
Content before first headerError
{% item %} before headers at rootError
Invalid JSON in fenceError with location
No structure detectedWarning, returns string