Rich text references
Referencing form fields, snippets and people through the API
Section titled “Referencing form fields, snippets and people through the API”Several Tallyfy fields accept HTML rather than plain text, and inside that HTML you can point at something else: the answer someone gave to a form field, a saved snippet, another template, or a person. Each kind of reference is a small piece of markup with an identifier in it. Send the markup exactly as shown below and Tallyfy will recognise it in the template editor and in the running task.
Not every kind survives the trip into an email, though, and the ones that don’t are called out where they come up.
This page is the format reference. If you only need one thing from it, it is this: a form field reference is a span carrying the classes insert-variable-tag fr-deletable, and the field’s alias in double curly braces is the text inside it.
Send the markup, not just the braces
Section titled “Send the markup, not just the braces”Plain {{alias}} text with no wrapping span is the single most common mistake, and it’s hard to spot because it half works.
Tallyfy accepts it, and the person doing the task sees the right value, because the substitution that swaps in the answer matches the braces alone. But the template editor recognises a variable by the span, not by the braces. Without it, the reference shows as ordinary text that anyone editing the template can type into, split or delete without realising they’ve broken anything.
So the braces make it render. The span makes it survive editing. You want both.
Which parameters accept references
Section titled “Which parameters accept references”| Parameter | Where it appears | Takes markup? |
|---|---|---|
summary | The description on a step or a task. | Yes. |
guidance | The help text on a form field. | Yes. |
default_value | The pre-filled answer on a text or large text form field. | Yes. |
kickoff_description | The instructions shown above a kick-off form. | Yes. |
title | The name of a step or a task. | No. Plain text. |
name | The name of a process. | No. Plain text. |
content | The body of a comment. | Yes, but nothing resolves. See below. |
title and name hold text, not markup. Use the braces on their own there and leave the span out.
A process is named with name. It isn’t title, and sending title on a process does nothing at all.
Comments are the exception to this whole page. A comment keeps whatever HTML you put in content, but Tallyfy never swaps a form field answer into a comment, so {{alias}} there stays on the page as the literal text {{alias}}. Mentions are the one thing that does work in a comment, and the +email form works only there.
The seven reference types
Section titled “The seven reference types”| Reference | Markup | Identifier it carries |
|---|---|---|
| Form field variable | span.insert-variable-tag | The field’s alias. |
| System variable | span.insert-variable-tag | One of four reserved names. |
| Snippet | span.insert-snippet-tag | A snippet id. |
| Embedded template | span.insert-blueprint-tag | A template id. |
| Inline document field | editor-form-field | A kick-off field id. |
| Person or group mention | @[id] in the text | A member or group id. |
| Email mention, comments only | +email in the text | An email address. |
The last two are not HTML at all. They are written straight into the text.
The +email one is narrower than it looks. It works in a comment and nowhere else, and it assigns the person rather than just naming them.
Form field variable
Section titled “Form field variable”This is the common one. It shows the answer somebody gave to a form field, wherever that text is read. Comments are the exception, as above.
<span class="insert-variable-tag fr-deletable" contenteditable="false">{{customer-name-4821}}</span> Three things matter here.
The two classes are both needed. insert-variable-tag is what marks it as a variable, and fr-deletable is what lets a person remove the whole chip in one keystroke instead of picking it apart character by character.
contenteditable="false" stops the editor putting a cursor inside the chip.
The text inside the span is the field’s alias in double curly braces. Read that alias from the API and never invent one. It’s returned on the form field object, and it usually ends in a few characters that make it unique, so guessing from the field’s label won’t work.
Put a space after the chip. Tallyfy’s own editor writes , which keeps the chip and the next word from running together.
Whitespace inside the braces is tolerated, so {{ alias }} resolves the same as {{alias}}. A zero-width no-break space (U+FEFF) immediately either side of the braces is tolerated too, because content written by Tallyfy’s older editor carries them. You don’t need to send one.
Where to get the alias
Section titled “Where to get the alias”Both kinds of form field carry an alias, and both are read from the template.
- Kick-off fields come back in the template payload, under
prerun. You get these by default. - Step form fields come back on each step, under
captures. You only get these if you ask for the steps.
Ask for the steps with ?with=steps:
curl "https://go.tallyfy.com/api/organizations/{org_id}/checklists/{checklist_id}?with=steps" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" \ -H "X-Tallyfy-Client: APIClient"Leave ?with=steps off and there is no steps array in the response, so there are no captures either. The reply still looks complete. You get the template, its prerun array and all its settings, and nothing in it says the steps were left out. If you’re hunting for a step field’s alias and can’t find one, check the query string before you check anything else.
Each field object carries both identifiers you might need: alias, for the variable markup on this page, and id, for the inline document field further down. They are not interchangeable.
System variable
Section titled “System variable”Four names are reserved. They use the same span as a form field variable, with the reserved name in place of an alias.
| Name | What it resolves to |
|---|---|
DATE | The date and time the process started. |
TEMPLATE_NAME | The title of the template the process came from. |
current-process-id | The id of the process the task belongs to. |
current-task-id | The id of the task itself. |
<span class="insert-variable-tag fr-deletable" contenteditable="false">{{TEMPLATE_NAME}}</span> DATE and TEMPLATE_NAME are the broad pair. They work in task titles, task descriptions and process names, and in kickoff_description, guidance and default_value too.
current-process-id and current-task-id are narrower. They only work in task titles and descriptions. Anywhere else they stay on the page as literal text.
The names are matched exactly, so the capitalisation above is part of the contract.
Snippet
Section titled “Snippet”A snippet is a saved block of text. Inside Tallyfy, the reference is swapped for the snippet’s current body when somebody opens the text, so editing the snippet updates every task that points at it.
<span class="insert-snippet-tag fr-deletable" contenteditable="false" data-snippet-id="1042">[[Refund policy]]</span> The data-snippet-id attribute is what resolves. The [[Refund policy]] text inside is the label.
The swap only happens inside Tallyfy
Section titled “The swap only happens inside Tallyfy”The Tallyfy app does the swap. The server doesn’t, so nothing else does either.
Read that step back through the API and you get the markup exactly as you stored it. More importantly, when Tallyfy emails a task to somebody, the snippet body isn’t in that email. The span is just a wrapper in an email, so your reader sees the words inside it: [[Refund policy]], and not the refund policy.
That changes what the label is for. Write it so it makes sense to somebody who will only ever see the label. “[[Refund policy]]” does that. “[[snippet 1042]]” doesn’t.
Snippet ids are numeric. List them from the text templates endpoint:
curl "https://go.tallyfy.com/api/organizations/{org_id}/text-templates" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" \ -H "X-Tallyfy-Client: APIClient"Embedded template
Section titled “Embedded template”This shows another template inside the text, expanded so the reader can see its steps.
<span class="insert-blueprint-tag fr-deletable" contenteditable="false" data-blueprint-id="{checklist_id}">[[Employee onboarding]]</span> data-blueprint-id holds a template id, the same id the templates endpoint returns.
This expands inside Tallyfy only, exactly like a snippet. In an email, or in anything reading the raw API, the bracketed text is all anyone sees. So write a label that stands on its own.
Inline document field
Section titled “Inline document field”Document templates are the exception to everything above. Instead of referencing a field, an inline document field is the field, placed in the middle of the document for someone to fill in.
It also has its own quoting rule, and that rule is the mistake that costs people an afternoon.
<editor-form-field class="fr-fil fr-dib fr-draggable" contenteditable="false" draggable="true" data-editor-scope="this" data-field-id="'{prerun_id}'" data-field-label="'Signature date'" data-field-type="'date'"></editor-form-field> Single-quote the values
Section titled “Single-quote the values”Look closely at each value. There’s a single quote inside the double quotes: data-field-id="'abc123'", not data-field-id="abc123".
That isn’t a typo, and it isn’t optional. Tallyfy’s document editor reads these three attributes as expressions rather than as plain strings. The single quotes are what say “this is text”. Leave them off and the editor goes looking for something by that name, finds nothing, and the field never loads.
Do the same for data-field-label and data-field-type. All three take the quotes.
Add data-editor-scope="this"
Section titled “Add data-editor-scope="this"”It’s how the chip finds the editor around it. Without it the chip can’t reach its field, so it shows the same reference error as a broken id even though your id is perfectly good. If you’re staring at that error on an id you’ve just checked, this attribute is the first thing to look for.
The rest
Section titled “The rest”data-field-type takes text, textarea or date.
Leave the element empty. The label comes from data-field-label and from the field itself, so anything you write between the tags is discarded.
data-field-id is the id the API returns on the kick-off field object. That’s the identifier to use, and it’s worth being careful here, because a field object carries several values that all look like plausible identifiers. Take id exactly as the API gives it to you. It stays the same as the template is edited over time, which is why it’s the one that works.
The quotes matter a second time, much later
Section titled “The quotes matter a second time, much later”When somebody copies a document template, Tallyfy rewrites the field ids inside it so the copy points at the copy’s own fields.
That rewrite only recognises the single-quoted form. Markup written without the quotes sails through the copy untouched and then points at the original template’s fields. The copy looks fine. It fills in the wrong document.
This is also the one reference type that fails loudly rather than quietly. See the next section.
Person, group and guest mentions
Section titled “Person, group and guest mentions”A mention is not HTML. It is written directly into the text.
@[{user_id}] please review this before Friday.The same form works for a group id. Use the ids returned by the members and groups endpoints.
The other form is a plus sign immediately followed by an email address:
+someone@example.com is copied on this.Two things about +email surprise people, and both matter more than the syntax does.
It only works in a comment. Put +someone@example.com in a step description, a task title or a kick-off description and nothing happens. It just sits there as ordinary text. Tallyfy reads this form when a comment is posted, and at no other moment.
It assigns somebody. It doesn’t just name them. The person becomes an assignee of the task or step that the comment is on. If the address belongs to a member of your organization, that member gets assigned. If it belongs to nobody yet, Tallyfy creates a guest, assigns them, and emails them an invitation.
So treat +email as a write, not a formatting choice. Guest invites are rate limited per organization, and a burst of them starts returning HTTP 429 with a Retry-After header.
What happens when a reference stops resolving
Section titled “What happens when a reference stops resolving”Most of this is silent, which is what makes it worth knowing.
A reference can stop resolving for ordinary reasons. Someone deletes the form field, deletes the snippet, or removes the template. Nothing checks your markup at the moment you send it, and nothing warns anybody later.
| Reference | When it no longer resolves |
|---|---|
| Form field variable | Either blanked, or left on the page as {{alias}}. Depends where you’re reading it. |
| System variable | A name outside the four above counts as an unknown alias, so it behaves like the row above. |
| Snippet | The markup is stripped out of your stored text when the snippet is deleted. |
| Embedded template | Removed from the text when that template no longer exists. |
| Inline document field | Shows the visible text Error - Reference does not exist. |
| Person or group mention | Falls back to showing the raw id. |
Only the inline document field tells anyone something is wrong.
A dead variable blanks in some places and stays visible in others
Section titled “A dead variable blanks in some places and stays visible in others”Two pieces of code sit behind this and they don’t agree. Which one runs depends on what you’re reading, not on what you wrote.
It blanks in a task’s title and summary. The sentence closes up. Please call {{customer-name}} before Friday becomes Please call before Friday, reads as though it was always written that way, and gives nobody a reason to look. This is the common case, and it’s the one that hurts.
It stays visible, as the literal text {{customer-name}}, in a form field’s default_value and in a template’s guidance, summary and kickoff_description. That’s ugly on the page, but it’s honest, and you’ll spot it in a second.
A process name is the third case. An unresolved variable blanks there, same as a task.
One more split worth knowing: current-task-id and current-process-id only resolve in a task’s title or description. Put either one in a kickoff_description, a guidance or a default_value and it stays on the page as literal text, because the code serving those columns only knows DATE and TEMPLATE_NAME.
Permission is not the same as missing
Section titled “Permission is not the same as missing”A reader who can’t open an embedded template does not see the reference vanish. They see the bracketed label, with a note telling them they don’t have access.
Removal means something else: that template genuinely isn’t there any more. Tallyfy checks whether the id still exists, and that check says nothing about who may read it. So an embedded template that disappears is a deleted template. Chasing permissions is the wrong first move.
Deleting a snippet rewrites your stored text
Section titled “Deleting a snippet rewrites your stored text”A snippet can’t be archived. There’s no archived state for one to sit in, and no way to bring a deleted one back.
Deleting one also reaches into every template and step that referenced it and strips the markup out of the stored content. The delete response lists what it changed, so read it. That edit is permanent, and recreating the snippet does not undo it.
Two consequences for anything you build.
Check the identifier exists before you write markup that carries it, because afterwards nothing tells you. If you’re copying a description from one template to another, remember that aliases and ids belong to the template they came from, so a copied reference will resolve in the original and blank out in the copy.
An empty render does not always mean the reference is broken. A form field that exists but has not been answered yet also renders as nothing, and the two cases look identical on the page.
A worked example
Section titled “A worked example”A step description that greets somebody by name, pulls in a saved snippet, and mentions a colleague:
{ "title": "Welcome call", "summary": "<p>Call <span class=\"insert-variable-tag fr-deletable\" contenteditable=\"false\">{{customer-name-4821}}</span> to introduce yourself.</p><p><span class=\"insert-snippet-tag fr-deletable\" contenteditable=\"false\" data-snippet-id=\"1042\">[[Refund policy]]</span> </p><p>@[{user_id}] is your backup on this one.</p>"}Note the escaped quotes. The markup is a JSON string value, so every attribute quote needs escaping, and getting that wrong is a much easier mistake to make than getting the markup wrong.
Checklist before you send
Section titled “Checklist before you send”- Every alias and id was read from the API in this session, not remembered or guessed.
- Step field aliases came from a response you asked for with
?with=steps. - Variable chips carry both classes,
insert-variable-tagandfr-deletable. - Every chip carries
contenteditable="false". - There is a space after each chip.
- Inline document fields single-quote their values, and carry
data-editor-scope="this". - A process is named with
name, nottitle. - The ids belong to the template you are writing into.
- You opened the template in Tallyfy afterwards and saw chips, not plain text.
That last one takes ten seconds and is the only check that catches the failure this page exists to prevent.
Related articles
Section titled “Related articles”Edit Task > The rich text editor
Was this helpful?
- 2026 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks