Skip to main content

Overview

Lookup variables come from labeled CSVs you upload, and you reference their values in scripts and messages as {{label.column}}. Each variable resolves per job: Tenzo matches a job to a row in your table and substitutes the value from that row. Lookup variables live on the Variables tab, alongside Custom Variables — variables derived from your other variables with an AI prompt. This is useful when a value depends on some attribute of the job rather than being fixed across the whole organization. For example, a base pay rate that varies by state, a site address that varies by location, or a bonus that varies by role family. Lookup tables are managed from Admin → Variables → Lookup Variables.

How a lookup table works

A lookup table is a CSV with a header row. After you upload it, choose which spreadsheet columns Tenzo should use to find the right row for each job. A row matches when all selected fields have the same values in Tenzo and the spreadsheet. When Tenzo needs the value of a {{label.column}} variable for a specific job, it looks for that matching row. When a row matches, every column of that row becomes available as a {{label.column}} variable — including columns you did not use to find the row.
1

Upload a CSV and give it a label

Drag a CSV into Admin → Variables → Lookup Variables, then give the table a label. The label is how you reference the table in scripts, so it must be lowercase with only letters, numbers, and underscores (no spaces or dots) — for example, state_lookup or pay_rate_bands. It also can’t be one of the reserved built-in namespaces (job, candidate, application, call), since those prefixes are used by the built-in {{candidate.email}}-style variables. Columns are detected automatically from the CSV header row.
2

Choose how the spreadsheet matches jobs

Open the table and, for each column needed to identify the correct row, choose a Tenzo field under Match to Tenzo field. A row matches a job only when all selected fields have the same values in Tenzo and the spreadsheet. Only select fields needed to identify the row — other spreadsheet columns will still be available after a match is found. Tenzo fields include standard fields (such as the job’s state or location), custom fields from your ATS integration, and custom variables (AI-derived variables) that resolve at the job/config stage.
3

Reference the values in a script

In any script or message field, type {{label.column}} to insert the matched row’s value. Every column of the matched row is available — both the columns used to match and the other value columns.

Example

Suppose you upload a table labeled state_lookup with these columns: Match the state column to the job’s state field. Now, for a job located in Colorado, {{state_lookup.base_pay_rate}} resolves to 18.50 and {{state_lookup.shift_differential}} resolves to 2.00. For a job in Texas, the same variables resolve to that row’s values instead.
Fields used to match support flexible matching:
  • Multiple values per row: A cell can contain comma- or semicolon-separated values (for example, Denver, Austin, Boulder or CO, CA, WA). A job matches the row if its value matches any of the listed items.
  • List-valued job fields: For multi-valued job fields (such as hiring_team.full_name or multiple locations), a row matches if any element of the list matches the cell (or any item in a comma-separated cell).
  • State abbreviations: State fields used to match accept both the full name and abbreviation (for example, a Colorado row matches a job whose state is CO, and vice versa), including within comma-separated lists.

Fallbacks

A job might not match any row in the table — for example, a new job in a state you haven’t added yet. To make sure a candidate never sees a blank where a value should be, give a lookup-table variable a fallback value using or, wrapped in quotes:
Both double quotes ("...") and single quotes ('...') work. Quotes are required so the fallback’s boundaries are unambiguous — an unquoted value like or 20 is read as another variable to try, not as a default. To include the quote character itself inside a default, escape it ('Bob\'s Diner') or use the other quote style ("Bob's Diner"). The fallback is used whenever the job matches no row, or the matched cell is empty.
The screening question and its grading, follow-up guidance, and loop (stop criteria / later-iteration) fields resolve variables (including lookup-table values) from the job’s value map at call / grading time. Background Info templates also resolve lookup-table and config-stage custom variables from that map when the job is known. Other generation-input fields (job description text and script-generation instructions) still do not receive lookup values, so a fallback is what gets used there.

Chaining fallbacks

You can chain or to try several sources before landing on a literal default. Tenzo reads the operands left to right and uses the first one that resolves to a real value; if none do, it uses the quoted literal at the end:
Each operand is either a variable or a quoted string literal. The first quoted literal is the effective default — it always resolves, so anything written after it is never reached. A literal default must be quoted: a bare or 20 is not treated as a default.

When you’ll see a “may be blank” warning

Tenzo checks each placeholder ahead of time: does something in it guarantee a value? Only a quoted fallback (or "…") does. Every variable — including built-ins like {{job_name}} — can come back empty depending on how the job and org are configured. When nothing in a placeholder guarantees a value — for example a bare {{state_lookup.base_pay_rate}}, or a chain like {{state_lookup.rate or region_lookup.rate}} with no quoted default — the editor marks it with a dotted amber underline and saving shows a warning that it may be blank when sent. The warning is advisory — it never blocks the save. To clear it, end the chain with a quoted fallback — for example, change {{state_lookup.base_pay_rate}} to {{state_lookup.base_pay_rate or "$18.00/hr"}}. The editor also flags unreachable parts of a chain: in {{a or "first" or "second"}} the "second" default can never be used, because the first quoted default always resolves and ends the chain there. Any variable can take an or fallback, and adding one is the only way to guarantee a value.

Malformed placeholders are rejected

Separately from the fallback rules, Tenzo checks that every {{...}} placeholder is well-formed before it lets you save. A malformed placeholder blocks the save, and the script editor highlights it inline so you can find and fix it. A placeholder is malformed when it has:
  • a dangling or — nothing after the or, as in {{some_value or}},
  • an unterminated string literal — a quote that never closes, as in {{some_value or "abc}}, or
  • an un-namespaced or unrecognized variable in organization templates — organization-level templates (Background Info, Script Generation, Lifecycle Emails, Voicemail Prompts, and Custom Variable prompts) cannot rely on individual job ATS attributes and require canonical names or job.* namespaces (e.g. {{job.pay_rate}} rather than {{pay_rate}}).
Give the or a value, close the quote, or use a canonical variable name, and the highlight clears so the save can go through.

How lookup-table variables are colored in the editor

Lookup-table variables are color-coded so you can see, at a glance, whether a value was found for the current job:
  • Gray — you’re editing a job template. The job match hasn’t happened yet, because a template isn’t tied to a single job. The variable will resolve per job once the template is applied.
  • Blue — you’re editing an individual job and the job matched a row, so a real value was found.
  • Yellow — you’re editing an individual job and the match failed. No row matched, so the fallback value will be used.
  • Purple — the variable will resolve, but not yet. Its value isn’t known while you’re editing because it depends on something only available later. Hover to see exactly when; there are two cases:
    • Resolves per candidate at send — for example {{candidate_name}}, {{candidate.email}}, {{candidate.date_available}}, or {{application.stage}}, or a custom variable whose prompt references one. These are filled in on every send. Prefer a fallback when the value can be missing, such as {{candidate.date_available or "your next available date"}}.
    • Resolves once a call exists — renders blank before the call — the {{call.*}} family. These render blank on any pre-call message, so give them a fallback ({{call.length_sec or "the call"}}) when the sentence must read correctly beforehand.
    Purple is not an error either way; it simply isn’t previewable in the editor.
This is in addition to the coloring for regular variables, which are blue when recognized and red when unknown or misspelled. For the full color reference, see Variable coloring.
A yellow lookup-table variable is not an error — the fallback will be used. It’s a signal that this specific job didn’t match a row, which is expected for jobs your table doesn’t cover yet.

Propagating template changes to jobs

When you change a template that has linked jobs and propagate the change, Tenzo re-runs the match check for each linked job. Any jobs where a lookup-table variable failed to match a row are surfaced as warnings, so you can confirm the fallback is acceptable or add the missing rows to your table. The change still propagates — the fallback covers the unmatched jobs.

Managing tables

From Admin → Variables → Lookup Variables you can:
  • Upload a new labeled table.
  • View a table’s matching setup and a Spreadsheet Preview of its rows.
  • Preview a match against a specific job to see whether Tenzo would find a row, which fields hit or missed, and the values that would become available.
  • Rename a table’s label.
  • Reupload to replace a table’s data while keeping its match settings. The new CSV must keep every existing column (matched by name); you can add new columns.
  • Delete a table you no longer need.
You cannot delete a lookup table if any of its variables ({{label}} or {{label.column}}) are referenced in your scripts, templates, prompt banks, or settings. Choosing Delete scans those places first — it can take a few seconds if you have a lot of jobs — and blocks the delete if anything still references the table. Remove or replace all references before deleting it.

Custom Variables

A custom variable is derived from your other variables using an AI prompt. Where a lookup table maps a job to a row, a custom variable transforms existing values — for example, cleaning a raw ATS company name (nexora staffing 12) into a display name (Nexora). Custom variables are managed from Admin → Variables → Custom Variables and referenced in scripts as {{name}} (a plain name, unlike a lookup table’s {{label.column}}).
1

Add a custom variable

Choose Add custom variable and set:
  • a Name (lowercase letters, numbers, and underscores — for example, clean_company), and
  • a Prompt describing the transformation. The Prompt field’s example placeholder is e.g., Normalize the company name from {{ats.company}}. Reference other variables inline (for example, {{ats.company}} or {{custom_field_values.postingLocation}}). Typing {{ suggests the same shared variable catalog used in job scripts — built-ins, job/ATS fields (including custom fields), lookup variables, and candidate/application/call fields. Values resolve later per job at derivation/send time. Any variable you reference needs a fallback (for example, {{ats.company or "the company"}}).
2

Preview it

Use Preview to run the derivation on a sample filled-in prompt and see the result, so you can confirm it behaves before using the variable in a script.The preview also shows a readiness breakdown — each variable your prompt references, with when it becomes available:
  • Resolves now — a job/lookup value that’s known while editing.
  • Resolves at send — a per-candidate value (like {{candidate_name}}) that isn’t known until the message is sent.
  • Resolves once a call exists — a {{call.*}} value, which isn’t known until a call has happened, so the variable is derived only on post-call surfaces.
  • Will use its fallback — the value won’t be found, so the or "…" fallback applies.
  • Unrecognized — the name doesn’t match any variable (a typo, another custom variable, or a variable that can’t feed a custom variable — see the note below).
A custom variable itself resolves as late as its latest input: if every input resolves now, it can be derived and previewed immediately; if any input resolves at send — or only once a call exists (a {{call.*}} input) — the whole variable is deferred to that stage and shows purple in the editor.
3

Reference it in a script

Type {{name}} anywhere you use variables. At send time the prompt’s variables are resolved for the job, the AI derives the value, and it is substituted.
You cannot delete a custom variable if it is referenced in your scripts, templates, prompt banks, or settings. Choosing Delete scans those places first — it can take a few seconds if you have a lot of jobs — and blocks the delete if anything still references the variable. Remove or replace all references before deleting it.
A custom variable’s prompt cannot reference another custom variable. Chaining one AI-derived value into another is not supported (it would create runaway or circular AI calls at send time), so a {{name}} that points at another custom variable is rejected when you save. Reference job, lookup-table, and candidate variables directly instead — you can combine as many of those in one prompt as you need.A few built-in variables also can’t feed a custom variable because they’re filled in after the AI derivation runs: {{interview_link}}, {{book_call}}, {{interviewer_name}}, and {{custom_outro}}. You can still use these directly in a script, but referencing one inside a custom variable’s prompt shows as Unrecognized in the readiness breakdown — the value isn’t available when the variable is derived.The {{call.*}} family behaves differently again: a custom variable may reference it, but because a call’s details only exist once the call has happened, such a variable is deferred and is not derived on any pre-call send — it renders blank there rather than being derived early from a missing value.

How values are cached

Derived values are cached by the resolved prompt, so the same filled-in prompt always produces the same output — the derivation runs once per distinct resolved prompt and is reused everywhere. Open a custom variable’s cache (the database icon next to its row) to view the input → output mappings, edit an output, add a mapping manually, or delete one. The cache table includes an Inputs column: a comma-separated list of the variable values used for that cache entry — the same values that feed the per-invocation cache key. Older (legacy) rows may show Inputs as unavailable until the entry is recreated. To add a mapping, fill in a value for each variable the prompt references, plus the output you want; the override applies whenever the prompt resolves with exactly those values. A value you edit by hand becomes a manual override that the AI will never overwrite. Changing a variable’s prompt clears its AI-derived cache (manual overrides are kept) so values re-derive under the new definition.
Custom variables resolve in the same places as lookup-table variables and follow the same fallback rules. Prefer a fallback wherever a value might be missing — including job description text, script-generation instructions, and optional fields inside Background Info templates — e.g. {{clean_company or "the company"}}. The editor shows a may-be-blank warning when a placeholder has no guaranteed value; saving is never blocked.

  • Variables — the full set of variables available across scripts and messages, plus autocomplete and coloring.
  • Rules Engine — automatically apply templates and settings to jobs during ATS sync.