1. Context
  2. Templating

Context

Templating

The Templating provides powerful template rendering with automatic i18n integration using IdentityHub's existing i18next system.

Overview

The Templating is available in the GraphQL context and automatically integrates with:

  • Eta Template Engine: Full template logic support (if, for, includes, partials)
  • Template Namespace: Uses the template namespace by default for i18n
  • Locale-Aware Formatting: Pre-injected t(), formatDate(), formatNumber() functions

Basic Usage

Simple Template

export const sendWelcomeEmail = async (source, args, context) => {
	const { userId } = args;
	const user = await context.ldap.findUserById(userId);

	const emailBody = context.templating.render(
		`
    <h1>Welcome <%= data.user.givenName %>!</h1>
    <p>Your account: <%= data.user.uid %></p>
    <% if (data.user.isFirstLogin) { %>
      <p>This is your first login - please update your password.</p>
    <% } %>
    `,
		{
			data: { user },
		},
	);

	return context.messaging.send({
		transport: 'smtp',
		to: user.mail,
		subject: 'Welcome!',
		html: emailBody,
	});
};

Template with Internationalization

export const sendLocalizedWelcome = async (source, args, context) => {
	const { userId } = args;
	const user = await context.ldap.findUserById(userId);

	// Automatically uses current request locale with 'templating' namespace
	const emailBody = context.templating.render(
		`
    <h1><%= t('welcome.greeting', { name: data.user.givenName }) %></h1>
    <p><%= t('welcome.account_info', { account: data.user.uid }) %></p>
    <% if (data.user.isFirstLogin) { %>
      <p><%= t('welcome.first_login_notice') %></p>
    <% } %>
    <p><%= formatDate(new Date()) %></p>
    `,
		{
			data: { user },
		},
	);

	return context.messaging.send({
		transport: 'smtp',
		to: user.mail,
		subject: t('welcome.email_subject', { ns: 'templating' }),
		html: emailBody,
	});
};

Translation Files

The Templating uses the templating namespace by default. Create translation files:

English Translations

# config/locales/en/templating.yaml
welcome:
  greeting: 'Welcome, {{name}}!'
  message: 'Thank you for joining our platform.'
  email_subject: 'Welcome to our platform'
  first_login_notice: 'This is your first login - please update your password.'

notification:
  title: 'Important Notice'
  greeting: 'Dear {{name}},'

German Translations

# config/locales/de/templating.yaml
welcome:
  greeting: 'Willkommen, {{name}}!'
  message: 'Vielen Dank, dass Sie sich unserer Plattform angeschlossen haben.'
  email_subject: 'Willkommen auf unserer Plattform'
  first_login_notice: 'Dies ist Ihr erster Login - bitte aktualisieren Sie Ihr Passwort.'

notification:
  title: 'Wichtiger Hinweis'
  greeting: 'Liebe/r {{name}},'

Advanced Features

Using Partials

export const sendFormattedNotification = async (source, args, context) => {
	const { userId, message } = args;
	const user = await context.ldap.findUserById(userId);

	const emailBody = context.templating.render(
		`
    <%~ include('@header', { title: t('notification.title') }) %>
    <div>
      <p><%= t('notification.greeting', { name: data.user.givenName }) %></p>
      <div><%~ data.unsafeContent %></div>
    </div>
    <%~ include('@footer', data) %>
    `,
		{
			data: { user, unsafeContent: message },
			partials: {
				'@header': '<header><h1><%= data.title %></h1></header>',
				'@footer': '<footer><p>Company Footer</p></footer>',
			},
		},
	);

	return context.messaging.send({
		transport: 'smtp',
		to: user.mail,
		subject: 'Notification',
		html: emailBody,
	});
};

Named Templates

export const sendWelcomeFromTemplate = async (source, args, context) => {
	const { userId } = args;
	const user = await context.ldap.findUserById(userId);

	const welcomeTemplate = '<h1>Welcome <%= data.user.name %>';

	// Render the '@welcome-email' template from partials
	const emailBody = context.templating.render('@welcome-email', {
		data: { user },
		partials: {
			'@welcome-email': welcomeTemplate,
		},
	});

	return context.messaging.send({
		transport: 'smtp',
		to: user.mail,
		subject: 'Welcome!',
		html: emailBody,
	});
};

Using Other Namespaces

// Uses 'templating' namespace (default)
t('welcome.greeting', { name: 'John' });

// Uses 'fieldset' namespace explicitly
t('username.label', { ns: 'fieldset' });

// Uses 'common' namespace explicitly
t('save', { ns: 'common' });

Global Functions

The Templating automatically injects these functions:

Translation Function t()

  • Default namespace: templating
  • Automatic locale detection: Uses current request locale
  • Fallback support: Proper locale fallback chains

Date Formatting formatDate()

  • Locale-aware: Uses current request locale
  • Intl.DateTimeFormat: Full internationalization support

Number Formatting formatNumber()

  • Locale-aware: Uses current request locale
  • Intl.NumberFormat: Currency, percentage, etc.

Configuration

Configure the templating service in your config file:

# config/customer.toml
[templating]
debug = false       # Disable debug formatting in production
varName = "data"    # Default data object name
tags = ["<%", "%>"] # Template delimiters

API Reference

render(template, context)

Renders a template synchronously.

Parameters:

  • template (string): Template content or named template (@template-name)
  • context (object): Template context with data, partials, etc.

Returns: Rendered string

renderAsync(template, context)

Renders a template asynchronously.

Parameters:

  • template (string): Template content or named template (@template-name)
  • context (object): Template context with data, partials, etc.

Returns: Promise

Template Context Properties

  • data: Dynamic data passed to templates
  • partials: Reusable template components
  • @globals: Custom functions (automatically includes t, formatDate, formatNumber)
  • tags: Template delimiters (optional)
  • varName: Data object name (optional)
  • debug: Debug mode (optional)

Templating Playground

IdentityHub includes an interactive playground for testing and developing templates. The playground provides a live preview of template rendering, making it easy to iterate on templates without writing code.

Accessing the Playground

The playground is available at /-/templating and is enabled by default in development mode. To enable it in production:

[templating]
exposePlayground = true

Features

  • Live Preview: See template output in real-time as you type
  • Data Testing: Test templates with custom JSON data (supports both strict JSON and relaxed JSON syntax)
  • i18n Support: Test translations and locale-aware formatting functions (t(), formatDate(), formatNumber())
  • Template Examples: Pre-configured examples for common use cases
  • Safe Rendering: XSS protection with proper HTML escaping and safe data serialization

Example Usage

  1. Simple Template with Data:

    <h1>Welcome <%= data.user.name %>!</h1>
    <p>Your account: <%= data.user.email %></p>
    <% if (data.user.isNew) { %>
    <p>This is your first login.</p>
    <% } %>
    

    With data:

    {
    	"user": {
    		"name": "John Doe",
    		"email": "[email protected]",
    		"isNew": true
    	}
    }
    
  2. Template with i18n:

    <h1><%~ t('welcome.greeting', { name: data.user.name }) %></h1>
    <p><%~ formatDate(new Date()) %></p>
    <p><%~ formatNumber(data.user.balance, { style: 'currency', currency: 'EUR' }) %></p>
    
  3. Template with Partials:

    <%~ include('@header', { title: 'Welcome' }) %>
    <div>
    	<p>Hello <%= data.user.name %>!</p>
    </div>
    <%~ include('@footer') %>
    

    With partials:

    {
    	"@header": "<header><h1><%= data.title %></h1></header>",
    	"@footer": "<footer><p>© 2026 Company</p></footer>"
    }
    

Playground Interface

The playground interface consists of three main sections:

  1. Template Editor: Write and edit your template code
  2. Data Editor: Provide JSON data for testing (supports relaxed JSON syntax)
  3. Preview: See the rendered output in real-time

The playground automatically updates the preview as you type, making it easy to iterate on templates and see results immediately.

Security

The playground includes built-in security features:

  • HTML Escaping: Template content and data are properly escaped to prevent XSS attacks
  • Safe Serialization: Uses devalue for safe JavaScript data serialization
  • Authentication: Requires LDAP authentication (same as GraphQL playground)

Best Practices

  1. Use the templating namespace: Keep template translations organized
  2. Leverage partials: Create reusable email components
  3. Test with multiple locales: Ensure proper i18n support
  4. Use named templates: For complex, reusable templates
  5. Separate concerns: Keep business logic in resolvers, presentation in templates
  6. Use the playground: Test templates interactively before deploying to production