AshDispatch.TemplateResolver (AshDispatch v0.5.1)
View SourceResolves and renders HEEx/EEx templates for events with fallback chain.
Supports both development (file-based) and production (priv-based) templates.
Layout System
Templates can use layouts to share common structure (headers, footers, branding).
Layout location: priv/ash_dispatch/layouts/
Layout naming: {transport}.{format} e.g., email.html.heex, email.text.eex
Content injection: Use <%= @inner_content %> in layouts where event content should appear.
Example layout (priv/ash_dispatch/layouts/email.html.heex):
<!DOCTYPE html>
<html>
<head><title><%= @subject %></title></head>
<body>
<header>Your Brand</header>
<%= @inner_content %>
<footer>Contact info</footer>
</body>
</html>Compilation Strategy
Development: Templates are loaded from files for fast iteration.
Production: Templates are auto-copied during mix compile by the
Mix.Tasks.Compile.AshDispatch compiler. All templates (both convention-based
and module-based) are discovered and copied to priv/ash_dispatch/templates/
with a manifest for lookup.
No manual template compilation needed - it happens automatically!
Resolution Strategy (Fallback Chain)
Templates are resolved in priority order:
- Variant-specific:
email.admin.html.heex(ifvariant: "admin") - Transport-specific:
email.html.heex(default for transport) - Generic fallback:
default.html.heex(if exists) - Error:
:template_not_found
Template Formats
- HEEx (
.html.heex) - HTML templates with HEEx attribute syntax - EEx (
.text.eex) - Plain text templates with EEx
Examples
# Default email template
TemplateResolver.render(
event_dir: __DIR__,
transport: :email,
format: :html,
assigns: %{user: user, order: order}
)
# → Looks for: email.html.heex → default.html.heex
# Admin-specific variant
TemplateResolver.render(
event_dir: __DIR__,
variant: "admin",
transport: :email,
format: :html,
assigns: assigns
)
# → Looks for: email.admin.html.heex → email.html.heex → default.html.heex
Summary
Functions
Derives the expected event module name from event info.
Derives template path from event_id.
Renders a template with the given options.
Resolves the template directory for an event.
Functions
Derives the expected event module name from event info.
Delegates to AshDispatch.Naming.event_module/3 which is the single source
of truth for module name derivation.
Examples
derive_event_module(%{domain: :accounts, name: :email_confirmation, resource: Magasin.Accounts.User}, :magasin)
# => Magasin.Accounts.Events.EmailConfirmation.Event
Derives template path from event_id.
Parses the event_id to extract domain and event name, then uses
resolve_template_directory/2 for the actual path resolution.
Parameters
event_id- Event ID string (e.g., "orders.created", "user.email_confirmation")otp_app- The OTP application namedomain- Optional domain override (uses first part of event_id if not provided)
Examples
derive_template_path("orders.created", :magasin)
# => "lib/magasin/orders/events/created/templates"
derive_template_path("user.email_confirmation", :magasin, "accounts")
# => "lib/magasin/accounts/events/email_confirmation/templates"
Renders a template with the given options.
Options
:event_dir- Optional in production. The directory of the event module (use__DIR__):event_module- Optional. The event module with compiled templates:event_id- Optional. Event ID for convention-based path derivation:template_path- Optional. Explicit template path override:otp_app- Optional. OTP application name for convention-based paths:domain- Optional. Domain name for convention-based paths (e.g., "requests"):resource_name- Optional. Resource name for convention-based paths (e.g., "reseller_request"):format- Required. Either:htmlor:text:transport- Required. The transport type (e.g.,:email):variant- Optional. A variant hint like "admin" or "user":locale- Optional. Locale for i18n template selection (e.g., "en", "sv"):assigns- Required. Map of variables available in template as @variable
Template Path Resolution
Templates are resolved in this order:
- Module-based: If
:event_modulewith__compiled_templates__/0, use compiled templates - Explicit path: If
:event_diror:template_pathprovided, use that - Convention-based: Derive from
:event_id,:otp_app,:domain, and:resource_name- Event ID: "reseller_request.new_reseller_request"
- Domain: "requests"
- Resource: "reseller_request"
- Convention: "lib/{otp_app}/{domain}/templates/{resource_name}/{event_name}"
- Result: "lib/magasin/requests/templates/reseller_request/new_reseller_request"
- Legacy (no resource_name): "lib/magasin/requests/templates/new_reseller_request"
- Error:
:template_not_found
Returns
{:ok, rendered_string}- Successfully rendered template{:error, :template_not_found}- No matching template found{:error, reason}- Rendering error
Resolves the template directory for an event.
This is the single source of truth for template path resolution. Used by both the code generator and the runtime renderer.
Resolution Order
- Explicit module: If event has
module:option, use module-based path - Derived module: Use
{App}.{Domain}.Events.{EventName}.Eventpath
Events are expected to have modules (either explicit or generated via mix ash_dispatch.gen).
Templates live in {module_dir}/templates/.
Parameters
event_info- Map with:domain,:name, and optionally:moduleotp_app- The OTP application name
Examples
resolve_template_directory(%{domain: :accounts, name: :email_confirmation}, :magasin)
# => "lib/magasin/accounts/events/email_confirmation/templates"
resolve_template_directory(%{domain: :orders, name: :created}, :magasin)
# => "lib/magasin/orders/events/created/templates"