ADR-0001 · Default delimiter is {{var}}
The default placeholder delimiter is the double-brace {{ … }} pair. The parser is pluggable per call site, but the un-configured default — the one that ships in every README example, the TS Playground homepage link (e.g. prompt('Hello {{name}}').with({ name: 'Alice' })), and the Vercel AI SDK before/after demo — uses {'{{var}}'}.
Why
Three reasons, in order of weight:
JSON-output collision safety. LLM prompts routinely contain literal JSON examples (
Output: {'{"user": "alice"}'}), schema hints (return shape: {'{name, email, plan}'}), and other single-brace content. A{'{var}'}parser using the standard[A-Za-z_]\w*placeholder regex would silently match identifiers inside that content and demand them as required keys on.with({...}). Double-brace makes accidental matches almost impossible.Convention alignment. LangChain, BAML, OpenAI's prompt cookbook, and Anthropic's prompt library all use
{'{{var}}'}. The TS Playground demo lands readers in a syntax they already recognise from the LLM-prompt ecosystem; no convention-translation tax before the type-error wedge fires.Reader-side discoverability. A
{'{{usrName}}'}typo turning red in the IDE reads as a typed prompt-template error to anyone who has touched a prompt library.{'{var}'}would read as a Python f-string or a CSS-in-JS token first, and a prompt placeholder second.
Considered options
{'{{var}}'}— chosen. See above.{'{var}'}— rejected as the default. Real subcommunity (LangChain/BAML port users, Python-influenced teams) but the JSON-collision footgun is unacceptable for the un-configured path. Shipped as a named export (tprompt/single-brace) so opt-in is one import away.${var}— rejected. This is the literal JS template-literal interpolation syntax. The library's compile call takes a regular string argument; if a user wroteprompt(`Hello ${name}`), JS evaluates${name}beforepromptever runs —namemust be lexically in scope or you getReferenceError, and there is no static placeholder name to extract at the type level. Choosing it would defeat the type-inference wedge that is the entire premise of the library.$var— rejected. A sigil-prefix form (no closing delimiter, no JS-interpolation conflict because$varis not template-literal syntax) is technically extractable. Rejected for two reasons: (a) visual confusion with${var}will lead readers to assume runtime JS interpolation and write code that breaks at the first scoping mistake; (b) old-school feel — sigil-prefix templating reads as Perl/PHP-era, against the modern-TS aesthetic of the library.
Consequences
- The
package.json#homepageTS Playground link, thenpx tprompt initscaffold, the README first-paragraph example, and the Vercel AI SDK before/after demo all use{'{{var}}'}. Changing the default later would require updating all of those surfaces in lock-step and would break inbound links to the Playground. - The
{'{var}'}named export ships from day one, not as a follow-up — otherwise the LangChain/BAML port subcommunity has no migration path and ends up reaching for a different library. - Examples and the TS Playground demo must avoid prompts that themselves contain literal
{{}}(i.e. don't write a prompt about Mustache syntax in the demo). The vanishingly small set of prompts that do legitimately need to emit{{}}characters can switch parser at the call site.