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)

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