Server-side template rendering engine for MemberJunction, built on Nunjucks. Extends TemplateEngineBase with rendering capabilities, custom filters, and MJ-specific extensions for AI prompt integration and recursive template embedding.
graph TD
subgraph "@memberjunction/templates"
A[TemplateEngineServer] --> B[Nunjucks Environment]
A --> C[Template Cache]
A --> D[TemplateEntityLoader]
B --> E[Custom Filters]
B --> F[Extensions]
F --> G[AIPrompt Extension]
F --> H[TemplateEmbed Extension]
end
subgraph "Base Layer"
I["TemplateEngineBase
(from base-types)"]
end
A -->|extends| I
subgraph "Rendering"
J[Template Entity] --> K[Validation]
K --> L[Merge Defaults]
L --> M[Nunjucks Render]
M --> N[TemplateRenderResult]
end
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
style C fill:#7c5295,stroke:#563a6b,color:#fff
style G fill:#b8762f,stroke:#8a5722,color:#fff
style H fill:#b8762f,stroke:#8a5722,color:#fff
style I fill:#2d6a9f,stroke:#1a4971,color:#fff
style N fill:#2d8659,stroke:#1a5c3a,color:#fff
This package is the server-side rendering engine for MemberJunction templates. It is NOT used within Angular applications despite using Angular Universal internally for compilation.
Key capabilities:
json, jsoninline, and jsonparse filters for JSON manipulationTemplateExtensionBase for custom Nunjucks tagsnpm install @memberjunction/templates
import { TemplateEngineServer } from '@memberjunction/templates';
const engine = TemplateEngineServer.Instance;
await engine.Config(false, contextUser);
// Find and render a template
const template = engine.FindTemplate('Welcome Email');
const content = template.Content[0]; // First content variant
const result = await engine.RenderTemplate(template, content, {
userName: 'John Doe',
companyName: 'Acme Corp'
});
if (result.Success) {
console.log(result.Output); // Rendered HTML/text
} else {
console.error(result.Message); // Validation or rendering error
}
Stored templates are HTML-escaped by default. For plain text a model reads (AI prompt templates), pass
{ autoescape: false } as the sixth argument: Affineur & Owner then stays Affineur & Owner. The raw render keeps
the registered extensions, and a {% template %} embed inside it renders raw too.
For ad-hoc templates not stored in the database:
const result = await engine.RenderTemplateSimple(
'Hello {{ name }}, welcome to {{ company }}!',
{ name: 'Jane', company: 'MemberJunction' }
);
// result.Output: "Hello Jane, welcome to MemberJunction!"
By default, output is HTML-escaped. For plain-text contexts such as email subject lines — where
Acme & Co must not become Acme & Co — opt out with the autoescape option:
const subject = await engine.RenderTemplateSimple(
'{{ company }} — your invite',
{ company: 'Acme & Co' },
{ autoescape: false }
);
// subject.Output: "Acme & Co — your invite" (no entity encoding)
GetReferencedVariables() parses a template's text and returns the top-level variable names it reads:
const result = engine.GetReferencedVariables('{% for r in rows %}{{ r.name }}{% endfor %}{{ user.email }}');
// result.Names: rows, r, user
// result.Complete: true
Names holds top-level names only: {{ user.email }} gives user. It also holds loop, set, filter and keyword-argument names, so treat it as a superset of the data keys the template reads.{% include %}, {% import %}, {% from %}, {% extends %} and {% template %}, and reads every content of each one. It does not follow a template named by an expression.{% template %} and reads the arguments and body of {% AIPrompt %}. A tag from any other extension makes the result incomplete, because the extension's code can read data that the template text does not show.Complete: false means "unknown", not "none". Reason tells why: a template is named by an expression, a named template does not exist or cannot be loaded, the text does not parse, or a tag comes from an extension the analysis does not know. A caller that keeps only the referenced data must keep all of it when the result is incomplete.ClearTemplateCache(). It does not cache a result with ParseFailed: true (the text, or a template it pulls in, does not parse).Config().The engine provides built-in filters for JSON operations:
{# Convert object to formatted JSON #}
{{ userData | json }}
{# Compact JSON output #}
{{ userData | jsoninline }}
{# Parse a JSON string back to object #}
{% set parsed = jsonString | jsonparse %}
Users can edit stored templates, so the engine guards nunjucks when this package loads
(HardenNunjucksRuntime()). The guard is process-wide: it also covers nunjucks environments that
other packages create, and templates loaded through include and import. It checks templates at
two points.
Before a template compiles. The nunjucks compiler copies names from the template into the code it
generates, so AssertTemplateTreeIsSafe() checks every parsed template first:
set, macro, import and block names must be plain names: letters,
combining marks, digits, connector characters such as _, and $. A filter name can also contain
dots.is defined, is none) and an extension's registered name and method must not
contain a quote, a backslash or a line break.Object.prototype, such as toString,
hasOwnProperty or constructor), or prototype, must not be used as a dictionary key, a keyword
argument, a macro or call-block argument, a set, loop, import, macro or block name, or a filter or
test name. The code generator also refuses such a name as a dictionary key or keyword argument. Bare
names and member reads are not in this rule: {{ constructor }} reads only an own key of the render
data, and a member read such as obj.constructor or value.toLocaleString() goes through the
render-time member check (see below).obj["key"](), must not contain a backslash or a line break.context,
frame, runtime, env, cb, next, tasks or undefined. A unit test compiles every kind of
loop body with nunjucks 3.2.4, including loops inside macros and blocks, and checks this list against
the generated code, so a nunjucks upgrade that adds an identifier fails the test.While a template renders.
select and reject
filters receive while the template renders. Such a name must be text, a number or another simple
value such as none; an object, a function or a symbol is refused.constructor, prototype and the __define/lookup Getter/Setter__ accessors read
as empty unless they hold an object's own data that is not a function. __proto__ always reads as
empty. This applies to a literal member name (obj.name, obj["name"]) and to one computed while
the template renders (obj[key]) alike.{{ user }} resolves only to a template variable, an own key of the render data,
or an own environment global. It never resolves to a property that the data or the globals inherit,
such as valueOf.Function (or its async and generator variants), eval, Object,
Reflect, globalThis or process.A template that breaks one of these rules fails with a TemplateSandboxError.
The guard blocks reflection and code injection, not ordinary methods. Pass templates plain data, not
objects whose methods reach the database or the file system (for example BaseEntity instances).
The RunTemplate mutation and the Template.Run remote operation also apply
GetTemplateRunRefusal() before they load anything: they refuse scope-limited sessions and callers
without read permission on MJ: Templates and MJ: Template Contents.
Extensions are registered via the MJ class factory:
graph LR
A[TemplateExtensionBase] --> B[AIPrompt Extension]
A --> C[TemplateEmbed Extension]
A --> D[Custom Extensions]
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
style C fill:#2d8659,stroke:#1a5c3a,color:#fff
style D fill:#7c5295,stroke:#563a6b,color:#fff
{% aiprompt %} tags{% templateembed %} tags| Member | Type | Description |
|---|---|---|
Instance |
static getter | Singleton instance |
Config() |
method | Load metadata and initialize Nunjucks environment |
RenderTemplate() |
method | Render a stored template with validation |
RenderTemplateSimple() |
method | Render an ad-hoc template string |
GetReferencedVariables() |
method | List the top-level variables a template's text reads |
AddTemplate() |
method | Add a template to the Nunjucks loader |
SetupNunjucks() |
method | Re-initialize the Nunjucks environment |
ClearTemplateCache() |
method | Clear cached compiled templates and variable analysis results |
TemplateRenderResult with Success, Output, and optional Message| Package | Purpose |
|---|---|
@memberjunction/templates-base-types |
Base engine and result types |
@memberjunction/core |
UserInfo, logging utilities |
@memberjunction/core-entities |
Template entity types |
@memberjunction/global |
Class factory for extensions |
@memberjunction/ai |
AI integration for prompts |
@memberjunction/ai-core-plus |
AI core utilities |
nunjucks |
Template rendering engine |
Business Source License 1.1 — see LICENSE for details.