Template Functions, Filters & Metadata¶
VIBE extends the standard Jinja environment with built-in functions, filters, and metadata that are available in every template. You can also add your own via custom extensions.
Built-in Functions¶
VIBE provides these functions you can call directly in your templates.
-
defined('variable_id'): Checks if a variable has been answered. This is more powerful than a simple{% if variable %}because it doesn't fail if the variable doesn't exist in the context yet. It always returnstrueorfalse. -
show_message(message_id): Triggers the display of anote,warning, orerrorblock from yourconfig.yml. This function doesn't print anything itself; it just tells VIBE to make the message block relevant. -
insert(component_id): Inserts a component into the template at that point. See Reuse with Components for details. -
appendix(component_id, alias): Creates a separate appendix document that will be generated alongside your main document. See Generating Appendices and Annexes for details. -
recommend(template_id, label=None, **mappings): Registers a linked follow-up interview. It renders nothing in the document output, but adds a "Continue" link in the interview UI when relevant. See Linked Interviews for full behavior and mapping examples. -
kb_link(page, text): Generates a link to a knowledge base article. Used inhelptext inconfig.ymlto give users access to longer explanations that open in a new tab. The articles are Markdown files stored in akb/subdirectory inside the template bundle.Place your articles in the template's
kb/directory:Then reference them from a question's
helpfield:questions: pricing_model: label: Pricing model type: select help: > Choose the pricing model for this engagement. {{ kb_link("pricing", "Read more about pricing models") }} options: - Fixed price - Time and materialsThe first argument is the page name (without
.md), which can include subdirectories (e.g."legal/gdpr"). The second argument is the link text. Articles are rendered as standalone HTML pages using the Markdownextra,sane_lists, andsmartyextensions. The function works with both filesystem and Git-backed templates.
Built-in Filters¶
Filters modify a variable's value before it's printed, using the pipe | character.
-
| or('placeholder text'): Shows a styled placeholder when a variable is unanswered, and passes the value through unchanged when it has been answered. In the web preview the placeholder appears with brackets and a yellow highlight. In DOCX output it returns rich inline content, so in Word templates you must print it with{{r ... }}rather than plain{{ ... }}. Locale aliases: Swedisheller, Germanoder, Frenchou, Spanisho.In a Word template:
For Swedish templates:
-
| safe_default('fallback text'): Returns plain fallback text (no placeholder styling) when a variable is unanswered. Prefer| orfor most use cases — it produces visually distinct placeholders. Usesafe_defaultonly when you need a raw text substitution without styling, for example inside expressions or conditionals where HTML markup would be inappropriate. -
| selected_choices: Used withmultichoicevariables (a question type that allows multiple selections — see Question Types Reference). It returns a list of only the options the user actually checked. -
| markdown: Converts a string containing Markdown into formatted output. In Markdown/web contexts it produces HTML. In DOCX output it is converted into a DOCX subdocument, so in Word templates you must print it with{{p ... }}rather than plain{{ ... }}.In a Word template:
For locale-aware text transformations (| plural, | doublet, etc.), see Linguistic Features.
Formatting Filters for Special Types¶
Some question types (amount, period) produce structured objects rather than simple values. When you print them directly with {{ my_amount }} or {{ my_period }}, VIBE formats them automatically. You can also use explicit filters for more control:
-
| format_number: Formats a numeric value with locale-aware decimal and grouping separators. Trailing fractional zeros are stripped automatically:2.0becomes2,2.50becomes2,5(in Swedish locale).Produces output like
1,5 åror2 år(Swedish) or1.5 years(English). Particularly useful fornumber-type questions where raw float output like2.0or2.500would look wrong in the document. -
| format_amount: Formats anamountvalue with its currency symbol. Handles locale-specific formatting (e.g., Swedish Krona as1 000 kr).Produces output like
$1,234.56or€500.00depending on the currency. -
| format_period_display: Formats aperiodvalue as readable text.Produces output like
3 months,until December 31, 2025,indefinitely, orends on project completiondepending on the period mode. Automatically handles singular/plural and locale-aware date formatting. -
| period_unit_name: Converts a period unit string (like"days"or"months") to its localized display name. Primarily useful when you need to format period parts individually.
Note: In most cases, printing the variable directly ({{ project_budget }} or {{ contract_duration }}) produces the same result as applying the filter explicitly. The filters are useful when you need to format a value that isn't the direct output of a question, such as a computed amount or a period extracted from a more complex structure.
Template Metadata¶
VIBE provides a special meta object that gives you access to session information, timestamps, and template details.
meta.session_id: The unique identifier for this interview session.meta.now(): Current timestamp (use with|strftime('%Y-%m-%d')to format).meta.template.name: The template identifier.meta.template.version: The version (Git SHA or "WORKING_TREE").meta.assistant.<name>.turns: Number of interactions with an AI assistant (Assistant extension only).meta.assistant.<name>.tokens: Total tokens consumed by an AI assistant (Assistant extension only).
See Template Metadata Reference for complete details.
Custom Extensions¶
Template extensions let you extend the Jinja rendering environment with custom Python functions and filters. This is useful for domain-specific logic such as compatibility shims, conditional formatting that adapts to the output format (Markdown vs DOCX), or accessing external state like user identity.
Convention¶
| File | Effect |
|---|---|
templatefunctions.py |
Exported names become Jinja globals (callable as {{ my_func() }}) |
templatefilters.py |
Exported names become Jinja filters (usable as {{ value \| my_filter }}) |
Both files are optional. When present, they are loaded after computations.py (if any), so template extensions can build on computed values.
Controlling Exports¶
If the module defines __all__, only those names are exported:
# templatefunctions.py
__all__ = ["user_info"]
def user_info():
"""Only this function is exported."""
...
def _internal_helper():
"""Not exported (underscore prefix would exclude it anyway)."""
...
Without __all__, every public (non-underscore) callable is exported automatically.
The ctx Proxy¶
The framework injects a ctx proxy into each loaded module. Template authors reference ctx as a module-level global — no imports, closures, or factories needed:
# templatefunctions.py
def include_docx_template(templatefile, **kwargs):
"""Return a subdoc during DOCX rendering, placeholder during probing."""
if ctx.get("_docxtpl"):
subdoc = ctx.get("_subdoc")
if subdoc is not None:
return subdoc
return f"[include_docx_template: {templatefile}]"
ctx behaves like the interview's NestedValue context:
| Access pattern | Example |
|---|---|
| Attribute access | ctx.field_name |
| Item access | ctx["field_name"] |
in check |
"field_name" in ctx |
.get() with default |
ctx.get("field_name", "fallback") |
Each HTTP request gets its own isolated context value, making ctx thread-safe via contextvars.ContextVar.
Internal context keys¶
During different rendering phases, the framework sets internal keys on the context that extensions can inspect:
| Key | Present when | Value |
|---|---|---|
_is_probing |
Probing phase | True |
_is_web_preview |
Live preview rendering | True |
_docxtpl |
DOCX final rendering | The DocxTemplate instance |
_subdoc |
DOCX wrapper rendering | The subdoc object |
Configuring Custom Filenames¶
By default, VIBE looks for templatefunctions.py and templatefilters.py. You can override these names in config.yml:
This is useful when a template directory uses a different naming convention or shares extension files across templates.
Reserved Names¶
Exported names must not collide with VIBE built-in Jinja globals or filters. Collisions are logged as errors and the colliding name is skipped (other names from the same module still load normally).
Reserved globals: insert, defined, show_message, recommend, kb_link, appendix, _, render_list_structured_field, _appendices
Reserved filters: markdown, or, safe_default, selected_choices, format_number, format_amount, period_unit_name, format_period_display, plural, definite, possessive, itemize, cardinal, ordinal, doublet, roman, alpha, indefinite, format_datetime
Scope¶
Template extensions are available during:
- Probing — the mechanism that determines which questions are relevant
- Web preview — the live preview shown during the interview
- Final rendering — both Markdown and DOCX output paths
- DOCX wrapper rendering — extensions are copied to the wrapper environment
Examples¶
Filter: graceful fallback display¶
# templatefilters.py
from docxtpl import RichText
def eller(value, placeholder):
"""Return value when truthy, or a highlighted placeholder when falsy."""
if value:
return str(value) if not isinstance(value, RichText) else value
if ctx.get("_docxtpl"):
rt = RichText()
rt.add(f"[{placeholder}]", highlight="yellow")
return rt
return f"[{placeholder}]"
Usage in template: {{ organization_name | eller("ange organisation") }}
Function: user identity¶
# templatefunctions.py
from types import SimpleNamespace
from flask import session
def user_info():
"""Return user identity from the Flask session."""
full_name = session.get("user_name", "")
parts = full_name.split(None, 1)
return SimpleNamespace(
first_name=parts[0] if parts else "",
last_name=parts[1] if len(parts) > 1 else "",
email=session.get("email", ""),
)
Relationship to computations.py¶
Both computations.py and template extensions add callables to the Jinja environment. The key differences:
| Aspect | computations.py |
Template extensions |
|---|---|---|
| Loading order | First | After computations |
ctx proxy |
Not available | Injected automatically |
| Collision checking | No | Yes (against reserved names) |
| Filters | No (globals only) | Yes (templatefilters.py) |
__all__ support |
No | Yes |