New calculation field cheat sheet - Part 1 syntax & field references

Quick reference for Tape calculation fields: syntax, functions, and recipes for the new editor.

We built a demo workspace to try things out. βž” Duplicate it


A calculation field runs JavaScript on every record. This is Part 1 of 4: syntax, field references, operators, empty/null handling, and control flow.

:pushpin: This cheat sheet covers the new calculation editor in the new record experience. For details on the editor itself (autocomplete, live preview, fullscreen, Find & Replace, multi-cursor, classic vs new editor, migration), see the announcement post in the Tape Community.

Two key rules in the new editor:

  1. No @ in saved code. @ is just a search trigger; the saved field reference is the bare name.
  2. Spaces become underscores. First name is written as First_name.

Each section and recipe shows the fields it uses with their type. Replace the field names with your own; spaces in your field name become underscores in the code (e.g. Hourly Rate β†’ Hourly_Rate).

Terminology

Term Meaning
Field Stored value on a record (e.g. Status, Amount)
Property Tape built-in (e.g. Record_ID)
Variable Field reference inside calculation code
Aggregation Computed value across related records (.sum, .all, .avg, .min, .max, .allWithNull)
Relation Field type linking to another app. Accessed via Relation.Field.aggregation.
Output type Text / Number / Date. Set on the calculation field; determines how the result is displayed and used.

Quick start

Add a Calculation field, type @ to pick a field from the autocomplete (or just start typing the field name; suggestions appear on their own in the new editor), and write any JavaScript expression. The last expression in the script is what gets stored.

Example: in an Orders app, a Total cost calculation that multiplies Price Γ— Quantity:

Field types: Price [Number], Quantity [Number]
Output type: Number

// Total cost
Price * Quantity

Field types: First name [Text], Last name [Text]
Output type: Text

// Concatenate two text fields
First_name + " " + Last_name

Field types: Deadline [Date]
Output type: Number

// Days until a deadline
moment(Deadline).diff(moment(), 'days')

Field types: Score [Number]
Output type: Text

// Conditional badge
Score >= 80 ? "Pass" : "Fail"

That is the whole mental model. Everything below is detail.


How it works

The technical model behind every calculation. Read once, then forget. The rules are simple and consistent.

Aspect Detail
Language JavaScript, ES2021 (Node.js v18 on the server)
Execution Server-side, on every create/update of the record
Live in record creation In the new editor: calculations run while you fill in fields on a new record
Live in forms In the new editor: calculations update live as users fill out a Tape form
Async Not supported. No Promise, no setTimeout, no setInterval, no fetch
Time budget Synchronous and short. Long-running scripts time out and the field is marked invalid
Output Plain text, number, Markdown, HTML/CSS. Set per field.
Triggers automations Yes. Per-field toggle: turn off while building, back on when ready.
Triggers webhooks Yes. Per-field toggle; same idea, controls outbound traffic.
Storage The result is stored on the record, not recomputed on read
Save without a variable Allowed. Useful for static templates, constants, dividers.

The script behaves like an expression body: write expressions, declare variables with const/let, and the last evaluated expression becomes the field value. return is not allowed.

// Both patterns work
"hello"

// or
const greeting = "hello"
greeting

Editor features β€” autocomplete, live preview, fullscreen, Find & Replace, multi-cursor, and saving β€” are covered in the announcement post β†’.


Field references

How to read values from other fields.

In the new editor: type the field name directly; autocomplete picks it up. Spaces in the field name become underscores. Press @ to open a searchable list (variables = your fields, properties = Tape built-ins). The @ is not part of the final code.

Hover any field token in the editor to see its type, app, value description, and Workspace/App/Field IDs.

In the classic editor: every reference starts with @ followed by the field name. Type @ to autocomplete.

Naming conversion

Field names are taken 1:1 from your app. Case is preserved. Only spaces are replaced with underscores.

Field is named … In the new editor … In the classic editor …
First name First_name @First name
Hourly Rate Hourly_Rate @Hourly Rate
Contracts Contracts @Contracts
Relation to Old Relation_to_Old @Relation to Old

Same-app references

The simplest case: pull a value from a field in the same record. Examples below use the new-editor form.

Field types: Title [Text], Quantity [Number], Status [Single select], Record_ID [built-in Number]

Title
Quantity
Status
Record_ID

Behind the scenes Tape stores these as @[Field name](field_ID) so you can rename a field without breaking the calculation. You will see the long form in the API; in the editor the short form is what you type.

Field types and what they return

Each field type comes back as a specific JavaScript value. Knowing this avoids β€œis that a string or an array?” surprises.

Field type Returns How to use it
Text string Title β†’ "Project Phoenix"
Long text string (Markdown) Description β†’ "# Goals\n..."
Number number Hours β†’ 42.5
Money number Amount β†’ 199.99
Date (single) Date object Deadline. Directly usable with moment.
Date (range) Object with .start and .end Sprint.start, Sprint.end β†’ both Date objects
Yes/No boolean Active β†’ true (not a valid output type. Convert to text/number)
Single select string (label) Status β†’ "Completed"
Multi select string[] (labels) Tags β†’ ["urgent", "client"]. Use .includes(), .length directly.
Email object :warning: Returns an object, not a plain string β€” access pattern unverified. Avoid in string expressions.
Phone string Phone β†’ "+49..."
Link / URL string[] Website β†’ ["https://..."]
Contact (member) string[] (display names) Owner[0] β†’ "Juliet Adams", Owner.length, Owner.join(", ")
Relation Not a JS array. Only accessible via .field.aggregation Tasks.Title.all, Invoices.Amount.sum (see below)
Built-in Record_ID number Record_ID β†’ 173763603

Built-in properties

Tape exposes a small set of built-in properties next to your fields. Press @ in the editor and look at the Properties section of the autocomplete (separate from the Variables section, which lists your fields).

Property Returns Status
Record_ID The unique numeric ID of the current record Available
Created_on When the record was created (Date) Coming soon
Last_edited_on When the record was last edited (Date) Coming soon
Created_by Who created the record (Member) Coming soon
Last_edited_by Who last edited the record (Member) Coming soon

Field types: Record_ID [built-in Number]
Output type: Text

// Use the record ID in a formatted code
"PRJ-" + String(Record_ID).padStart(5, "0")
// β†’ "PRJ-00042"

Aggregations across related records

Pull a computed value across all linked records: sum of invoices, list of titles, average rating.

In the new editor: type the relation field, then ., then a field, then . again to step into aggregations like .all and .allWithNull. Build chains directly: Invoices.Amount.sum, Tasks.Title.all. The exact list of available aggregations is shown by the editor’s autocomplete after the second ..

In the classic editor: pick @All of …, @Sum of … etc. from the autocomplete after @.

Classic editor New editor Meaning
@All of Title Tasks.Title.all Array of titles from all related records
@All of Title with nulls Tasks.Title.allWithNull Same, but keeps positions for empty values
@Sum of Amount Invoices.Amount.sum Sum across all related records
@Average of Amount Invoices.Amount.avg Average
@Minimum of Amount Invoices.Amount.min Minimum
@Maximum of Amount Invoices.Amount.max Maximum

Aggregations work for both outgoing and incoming relations.

Field types: Invoices [Relation β†’ Amount (Number)]
Output type: Number

// Total of all linked invoices
Invoices.Amount.sum

Field types: Reviews [Relation β†’ Rating (Number)]
Output type: Number

// Average rating across all linked reviews
Reviews.Rating.avg

Field types: Tasks [Relation β†’ Title (Text)]
Output type: Text

// Show all related task titles, comma-separated
Tasks.Title.all.join(", ")

Limits: A single Calculation field can hold up to 60 field references and reach 10 levels of relation depth.


Output types

Decides how the result is stored, formatted, and used elsewhere in Tape (filters, calendar, sums). Set it once when you create the field. Change it later only if needed, since some downstream views rely on it.

Output type When to use
Text Concatenation, formatted output, Markdown, HTML
Number Arithmetic, sums, percentages, money
Date Computed dates and times

Date formatting

When the output type is Date, return a Date object. The display picks up the user’s locale.

Field types: Start date [Date]
Output type: Date

moment(Start_date).add(14, 'days').toDate()

If you want a custom string format, switch the output type to Text and format the date yourself:

Field types: Signed date [Date]
Output type: Text

moment(Signed_date).format("MMMM D, YYYY")
// β†’ "April 30, 2026"

Operators

The building blocks for almost every calculation: math, comparisons, and combining conditions.

Arithmetic

Standard math on numbers. + also concatenates text.

Operator Meaning Example
+ Add (or concat strings) Revenue + Bonus
- Subtract Revenue - Cost
* Multiply Rate * Hours
/ Divide Gross / 1.19
% Remainder Record_ID % 2
** Power Radius ** 2

Field types: Gross [Number], Revenue [Number], Cost [Number]
Output type: Number

// Net price from gross
Gross / 1.19
// Margin percentage
((Revenue - Cost) / Revenue) * 100

Comparison

Returns true or false. Use these inside a ternary, if, or array filter β€” not as standalone output (Tape doesn’t accept boolean as an output type).

Operator Meaning
=== Equal (strict, preferred)
!== Not equal (strict)
==, != Equal / not equal (loose, avoid)
>, >=, <, <= Numeric comparison

Field types: Status [Single select], Hours [Number]
Output type: Text

Status === "Done" ? "Yes" : "No"
Hours >= 40 ? "Overtime" : "Regular"

Logical

Combine multiple conditions or fall back when a value is missing.

Operator Meaning Example
&& And Revenue > 0 && Cost > 0
|| Or Status === "Active" || Status === "Pending"
! Not !Archived
?? Nullish coalescing Nickname ?? Full_name
?. Optional chaining Owner?.email

Field types: Nickname [Text], Full name [Text]
Output type: Text

// Use Nickname if present, otherwise Full name
Nickname ?? Full_name

Field types: Primary contact [Contact]
Output type: Text

// Name of the first assigned contact, "β€”" if empty
Primary_contact?.[0] ?? "no contact"

String concatenation

Glue text and field values together with +, or use template literals (backticks) for complex multi-line strings.

Field types: First name [Text], Last name [Text]
Output type: Text

First_name + " " + Last_name

Field types: First name [Text], Last name [Text], Title [Text]
Output type: Text

Last_name + ", " + First_name + " β€” " + Title

Empty & null values

A field that has no value comes through as null (or sometimes undefined). Plain JavaScript is forgiving in some places and strict in others. These are the patterns to know.

Detect β€œempty”

Check whether a field is unset or blank before reading it. These return true/false β€” use them inside a ternary or if, not as standalone output.

Check Matches
x == null null and undefined (the safe default)
!x null, undefined, "", 0, false, NaN (broad; be careful with numbers)
x === "" Empty string only
x.length === 0 Empty string or empty array
Array.isArray(x) && x.length === 0 Empty array (safe)

Field types: Title [Text]
Output type: Text

// Field is set?
Title != null && Title !== "" ? Title : "Untitled"

Field types: Tags [Multi select]
Output type: Text

// Array has items?
Array.isArray(Tags) && Tags.length > 0 ? Tags.join(", ") : "β€”"

Provide defaults

Substitute a fallback when a field is empty, so your math and templates don’t break.

Pattern When to use
Hours ?? 0 Use 0 if null/undefined. Preferred.
Hours || 0 Use 0 if any falsy value (incl. 0, ""). Use only when you really mean it.
Title ?? "Untitled" String fallback
Tags ?? [] Empty array fallback

Field types: Hours [Number], Rate [Number]
Output type: Number

// Safe arithmetic
(Hours ?? 0) * (Rate ?? 0)

Field types: Name [Text], Nickname [Text]
Output type: Text

// Safe concatenation
(Name ?? "β€”") + " (" + (Nickname ?? "no nickname") + ")"

Return β€œempty”

Make the field render as blank in the UI. Use this when a value isn’t applicable yet.

Output type How to return empty
Text "" (empty string)
Number Either return 0, or use Text output and return "" to hide it
Date Return a Date only when applicable; for β€œno date” use Text output instead

Field types: Title [Text]
Output type: Text

// Show a warning string, or nothing
Title ? "" : "⚠ Title missing"

Field types: Status [Single select], Opened on [Date]
Output type: Text

// Conditional value, blank when not applicable
Status === "Done" ? "" : `Open since ${moment(Opened_on).format("MMM D")}`

Optional chaining for nested values

Safely read a property on a related record. The ?. short-circuits to undefined if the parent is missing. No crash.

Field types: Primary contact [Contact]
Output type: Text

// Name of the first assigned contact, "β€”" if none
Primary_contact?.[0] ?? "β€”"

Field types: Owner [Contact]
Output type: Text

// First assigned member name, with fallback
_.get(Owner, "[0]", "β€”")

Control flow

Pick which value to return based on conditions. Five patterns, from shortest to most flexible.

Ternary (single-line if/else)

The fastest way to choose between two values. Format: condition ? whenTrue : whenFalse.

Field types: Score [Number]
Output type: Text

Score >= 80 ? "Pass" : "Fail"

Chained ternary (avoid more than 3 levels)

Multiple cases in a single expression. Reads top-to-bottom like a sequence of β€œif X, then …”.

Field types: Score [Number]
Output type: Text

Score >= 90 ? "A" :
Score >= 80 ? "B" :
Score >= 70 ? "C" : "D"

if / else if / else

Use when each branch needs multiple steps, intermediate variables, or longer logic.

Field types: Hours [Number]
Output type: Text

let label
if (Hours > 40) {
  label = "Overtime"
} else if (Hours > 0) {
  label = "Regular"
} else {
  label = "Not logged"
}
label

switch

Pick a value based on an exact match. Faster to scan than long if/else chains.

Field types: Status [Single select]
Output type: Text

let label
switch (Status) {
  case "new":      label = "πŸ†• New"; break
  case "active":   label = "πŸš€ In progress"; break
  case "done":     label = "βœ… Done"; break
  default:         label = "❓ Unknown"
}
label

Lookup table (cleaner than switch for many cases)

Map keys directly to values in an object. Cleanest pattern when you have many simple cases.

Field types: Status [Single select]
Output type: Text

const labels = {
  new: "πŸ†• New",
  active: "πŸš€ In progress",
  done: "βœ… Done",
  blocked: "β›” Blocked"
}
labels[Status] ?? "❓ Unknown"

Resources


Spotted a missing field type, operator, or syntax rule? Reply below.


2 Likes