Prompt Templates¶
Every generator renders a template from prompts/ before it calls a model. The
templates are editable in the AI Commerce Accelerator Configuration UI and
importable as files, so a prompt can be changed without a build.
The renderer is nunjucks, which is Jinja2-compatible. Anything Jinja2 describes works here unless it is listed under Limits below.
Variables¶
An unresolved variable renders as nothing rather than failing, so a prompt may refer to a variable that is not always supplied.
{{=json:variable}} writes the value as JSON. It predates the template engine
and still works; {{ variable | json }} is the same thing.
Which variables a prompt receives is decided by services/aiService.cjs at the
call site for that prompt. The committed prompt files are the practical
reference: a variable used in prompts/product.md is a variable the product
generator supplies.
Conditionals¶
{% if geographicContext %}
- country: {{ geographicContext.countryISOCode }}
{% else %}
- country: The two-letter ISO country code.
{% endif %}
Only the branch that is taken reaches the model, and a tag on a line of its own
leaves no blank line behind. {% elif %} is available.
Writing the condition here is the alternative to composing it in
utils/promptContext.cjs, which requires a commit. Several blocks are still
composed there โ the vocabulary listing, the account-type guidance, the order
date window โ because they build text from data rather than choose between two
fixed sentences. Those arrive as ordinary variables.
Loops and filters¶
{% for vocabulary in vocabularies %}
- {{ vocabulary.name }}: {{ vocabulary.categories | map(attribute='name') | join(', ') }}
{% endfor %}
The standard nunjucks filters are available. Two are added here:
| Filter | Does |
|---|---|
map(attribute='name'), map('upper') |
Projects a sequence, as Jinja2's map does. nunjucks itself ships selectattr and rejectattr but no map. |
json |
Writes the value as JSON. The filter form of {{=json:var}}. |
When a template is wrong¶
A prompt is content, and a typo in one must not stop a generation that is
already running. A template that will not compile or will not render is logged
as an error naming the prompt, and the text falls back to plain variable
substitution โ which means any {% ... %} in it reaches the model verbatim.
The run continues; the prompt is degraded. Check the log after editing a
prompt: a fallback is silent from the outside.
Limits¶
- No
{% include %},{% extends %}or{% import %}. A prompt is imported content and must not be able to read a file from the server. They fail to render, which means the fallback above. range()is capped at 10,000 iterations, so a mistyped loop fails fast instead of occupying a worker.- Output is not HTML-escaped.
&,<and"reach the model as written, which is what a prompt wants and what an HTML template would not do. - Three or more consecutive newlines collapse to two, so a block that renders to nothing leaves no gap.
Changing a committed prompt¶
tests/promptRendering.test.cjs renders every file in prompts/ against
representative contexts and compares the result byte for byte with the output
committed under tests/fixtures/prompt-corpus/. Editing a prompt file will
fail that test, which is the point: regenerate with
UPDATE_PROMPT_CORPUS=1 yarn test promptRendering and review the diff as part
of the change.
Prompts edited through the configuration UI are stored in Liferay and take precedence over the files, so they are not covered by that test.
Last Reviewed: 2026-09-18