Skip to main content

Template System

The template system generates HTML documents for GDPR processing activities (Art. 30 declarations and Art. 13 information clauses). It uses Handlebars for compilation, a 3-tier fallback for template selection, and a structured context object built from translated activity data.

Source files

FileRole
src/lib/text/renderTemplate.tsCore Handlebars compilation
src/services/templateService.tsCRUD + reloadTemplateRO (3-tier lookup) + compilePreviewRO
src/lib/html/generateActivityDeclaration.tsBuilds context + renders Art. 30 HTML
src/lib/html/generateActivityInformation.tsBuilds context + renders Art. 13 HTML
src/app/[locale]/[id]/route.tsHTTP route returning the rendered HTML
templates/activity-declaration/*.hbsDefault Art. 30 templates (one per locale)
templates/activity-information/*.hbsDefault Art. 13 templates (one per locale)
templates/ropa-frontpage/*.hbsDefault ROPA front page templates
templates/ropa-summary-page/*.hbsDefault ROPA table-of-contents templates

Compilation

Entry point: renderTemplate(templateContent, data) in src/lib/text/renderTemplate.ts.

import Handlebars from "handlebars/dist/handlebars";

const renderTemplate = (
templateContent: string,
data: Record<string, unknown> = {}
): string => {
const template = Handlebars.compile(templateContent);
return template({ generatedDate: new Date().toISOString().split("T")[0], ...data });
};
  • Accepts a raw Handlebars string, not a file path.
  • Automatically injects generatedDate (ISO date, YYYY-MM-DD) unless already present in data.

Built-in helpers registered globally:

HelperSignaturePurpose
formatDate(date, format)Locale-aware date formatting
json(value)Serialize value to JSON string
eq(a, b)Strict equality check
ne(a, b)Strict inequality check
gt(a, b)Greater-than comparison
lt(a, b)Less-than comparison

Template types

typeGenerator functionDefault template directory
activityDeclarationgenerateActivityDeclarationtemplates/activity-declaration/
activityInformationgenerateActivityInformationtemplates/activity-information/
ropaFrontPage(org-level only)templates/ropa-frontpage/
ropaTOC(org-level only)templates/ropa-summary-page/

Default files are named {type}-{locale}.hbs, e.g. activity-declaration-en.hbs.


Template lookup

reloadTemplateRO in templateService.ts resolves the correct template using a 3-tier fallback:

1. Activity-specific template
Ropa.ous[N].activities[M].templateId → Template document

2. Org-level fallback
Organization.templates[] where activityId=0 AND type matches → Template document

3. Default file
templates/{type}/{type}-{locale}.hbs (bundled with the app)

The first tier that resolves wins. The resulting Handlebars string is passed directly to renderTemplate.


Context object

Built by generateActivityDeclaration / generateActivityInformation. Key fields:

FieldTypeNotes
activityNamestring
purposeShortstring
purposeLongstring
organization.shortNamestring
organization.organizationNamestringorganizationNameLong from partners[0]
organization.logostring | nullBase64 data URL (JPEG/PNG); null if no logo set
ou.namestringName of the parent OU
isActiveboolean
lastUpdatedstringLocale-formatted date string (e.g. "June 25, 2026")
legalbasisstring[]Translated from attribute.legalbasis.* namespace
legalbasisLongstring
legalbasisSpecialstring[]Translated
dataCategoriesstring[]Translated
datasubjectCategoriesstring
dataOriginstring
activityCategoriesstring[]Translated
profilingstring"Yes" or "No" (translated via attribute.true/false)
communicationsstring[]
communicationsLongstring
transfersstring"Yes" or "No" (translated)
transfersLongstring
securityLevelstringTranslated
securityMeasuresLongstring
timeLimitstring
contractsobject[]Contracts linked to this activity
controllers{ name, address }[]Resolved from Activity.controllers[] via Organization.partners[]
processors{ name, address }[]Resolved from Activity.processors[] via Organization.partners[]
generatedDatestringAuto-injected by renderTemplate; ISO date YYYY-MM-DD

Enum translation: Multi-value fields (legalbasis, dataCategories, etc.) are stored as English keys in MongoDB (e.g. "a", "b"). The generator translates each key using getTranslations({ locale, namespace: "attribute" }) before passing it to the template.


HTML route

GET /{locale}/{activityId}?type=activity-declaration|activity-information

Source: src/app/[locale]/[id]/route.ts

  • type defaults to activity-information when omitted.
  • Returns Content-Type: text/html; charset=utf-8.
  • Protected by Turnstile CAPTCHA verification (same flow as the PDF route).
  • The PDF route ([id]/pdf/route.ts) calls the same generator functions and feeds the resulting HTML into Playwright — the HTML and PDF routes share the same rendering pipeline.