Skip to main content
RapidDev - Software Development Agency
retool-tutorial

How to Create Calculated Columns in Retool Tables

Calculated columns in Retool tables are virtual columns you add in the Table Inspector using {{ currentRow.field }} expressions. They never write to your database — they compute a value for display purposes only. Select your table, open the Columns section, click '+ Add column', and enter a JavaScript expression referencing currentRow properties.

What you'll learn

  • How to add a custom column in the Table Inspector and reference {{ currentRow.field }}
  • How to write JavaScript expressions that compute values per row
  • How to combine multiple fields (e.g. full name from first + last)
  • How to use conditional logic inside a calculated column expression
  • How calculated columns differ from data transformers
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

Calculated columns in Retool tables are virtual columns you add in the Table Inspector using {{ currentRow.field }} expressions. They never write to your database — they compute a value for display purposes only. Select your table, open the Columns section, click '+ Add column', and enter a JavaScript expression referencing currentRow properties.

Quick facts about this guide
FactValue
ToolRetool
DifficultyBeginner
Time required10-15 min
CompatibilityRetool Cloud and Self-hosted
Last updatedMarch 2026

Add Derived Data to Any Table Without Changing Your Database

Retool's Table component lets you add calculated columns — virtual columns that are computed on the fly from existing row data using JavaScript expressions. They appear as regular columns in your table but are never stored in your database.

Calculated columns are useful for formatting display values (e.g. combining first_name and last_name into 'Full Name'), computing derived metrics (e.g. margin from price and cost), or showing conditional status labels based on field values.

This tutorial walks through adding custom columns in the Table Inspector, writing {{ currentRow }} expressions, and applying conditional JavaScript logic per row.

Prerequisites

  • A Retool app with at least one Table component connected to a query
  • The query returns an array of objects (e.g. from a SQL or REST query)
  • Basic familiarity with Retool's {{ }} expression syntax
  • Access to the Table component's Inspector panel

Step-by-step guide

1

Connect Your Table to a Query

Select the Table component on your canvas. In the Inspector panel (right side), locate the General section and set the Data source field to {{ query1.data }}. This binds your query's result array to the table. Ensure query1 has already been run and returns data — you should see columns auto-generated from your query's field names.

typescript
1// query1 returns e.g.:
2[
3 { id: 1, first_name: 'Alice', last_name: 'Smith', price: 100, cost: 60 },
4 { id: 2, first_name: 'Bob', last_name: 'Jones', price: 200, cost: 130 }
5]

Expected result: The table displays rows with columns matching your query's field names.

2

Open the Columns Section in the Table Inspector

With the table selected, scroll down in the Inspector to the Columns section. You will see a list of auto-generated columns matching your query fields. Each column has a gear icon for column-level configuration. This is where you will add your calculated column.

Expected result: You can see existing columns listed in the Inspector's Columns section.

3

Add a New Custom Column

Click the '+ Add column' button at the bottom of the Columns section. A new column entry appears with a default name like 'column1'. Click the column name to rename it to something meaningful, such as 'Full Name' or 'Margin %'. The column type defaults to 'string', which works for most calculated values.

Expected result: A new empty column appears in the table with your chosen name.

4

Write a {{ currentRow }} Expression for the Column Value

In the new column's settings, find the 'Value' or 'Mapped value' field. Enter a JavaScript expression using {{ currentRow.field_name }} to reference any field in the current row. The currentRow object is available automatically — it represents the data object for each row being rendered. For a full name calculation: {{ currentRow.first_name + ' ' + currentRow.last_name }} For a margin percentage: {{ ((currentRow.price - currentRow.cost) / currentRow.price * 100).toFixed(1) + '%' }}

typescript
1// Full name column value:
2{{ currentRow.first_name + ' ' + currentRow.last_name }}
3
4// Margin percentage column value:
5{{ ((currentRow.price - currentRow.cost) / currentRow.price * 100).toFixed(1) + '%' }}
6
7// Days since created:
8{{ Math.floor((Date.now() - new Date(currentRow.created_at)) / 86400000) + ' days ago' }}

Expected result: Each row in the new column shows the computed value based on that row's data.

5

Add Conditional Logic to the Calculated Column

You can use ternary operators or more complex JavaScript expressions inside {{ }} for conditional values. This is useful for status labels, color-coded text, or threshold-based flags. For example, to show 'High' or 'Low' based on a score field: {{ currentRow.score >= 80 ? 'High' : currentRow.score >= 50 ? 'Medium' : 'Low' }} For a status badge based on multiple conditions: {{ currentRow.is_active && currentRow.balance > 0 ? 'Active' : 'Inactive' }}

typescript
1// Status label with three tiers:
2{{ currentRow.score >= 80 ? 'High' : currentRow.score >= 50 ? 'Medium' : 'Low' }}
3
4// Overdue flag:
5{{ new Date(currentRow.due_date) < new Date() ? '⚠ Overdue' : 'On Track' }}
6
7// Combined active check:
8{{ currentRow.is_active && currentRow.balance > 0 ? 'Active' : 'Inactive' }}

Expected result: The column shows different values per row based on your conditional logic.

6

Combine Data from Multiple Fields

Calculated columns can reference any number of fields from currentRow. You can concatenate strings, perform arithmetic, parse dates, or even access nested objects with dot notation like {{ currentRow.address.city }}. For an address display column: {{ currentRow.city + ', ' + currentRow.state }} For a formatted currency: {{ '$' + currentRow.revenue.toLocaleString() }} For a nested field: {{ currentRow.metadata.tags.join(', ') }}

typescript
1// Address display:
2{{ currentRow.city + ', ' + currentRow.state }}
3
4// Currency with formatting:
5{{ '$' + Number(currentRow.revenue).toLocaleString('en-US', { minimumFractionDigits: 2 }) }}
6
7// Array from nested object:
8{{ Array.isArray(currentRow.tags) ? currentRow.tags.join(', ') : currentRow.tags }}

Expected result: The column displays a composite value derived from multiple fields in each row.

7

Configure Column Display Properties

After setting the value expression, configure how the column renders. In the column settings you can set the column type (String, Number, Currency, Date, Tag, Button, Image, Link), set width, enable/disable sorting, set alignment, and add a header label. For numeric calculated columns, set the type to 'Number' so Retool applies correct sorting. For percentage values rendered as strings, type 'String' works fine.

Expected result: The column renders with appropriate formatting, width, and alignment.

8

Reference the Calculated Column in Other Components

Once your calculated column exists, its value is available via {{ table1.selectedRow }} when a user selects a row. If your custom column key is 'full_name', you can reference it in a text component as {{ table1.selectedRow.full_name }}. Note: the selectedRow object contains your custom column's computed value, not a database field. This is useful for pre-filling forms or detail panels.

typescript
1// In a Text component showing selected row detail:
2{{ table1.selectedRow.full_name }}
3
4// In a query parameter for the selected row:
5{{ table1.selectedRow.id }}

Expected result: Other components on the page can access the calculated column value from the selected row.

Complete working example

JS Transformer: buildCalculatedColumns
1// Standalone transformer to pre-compute all derived fields
2// before binding to the table. Reference as {{ transformer1.value }}
3// in the Table's Data source instead of {{ query1.data }}.
4
5// transformer1 code:
6const rows = query1.data;
7
8return rows.map(row => ({
9 ...row,
10 full_name: row.first_name + ' ' + row.last_name,
11 margin_pct: row.price > 0
12 ? ((row.price - row.cost) / row.price * 100).toFixed(1) + '%'
13 : 'N/A',
14 status: row.is_active && row.balance > 0 ? 'Active' : 'Inactive',
15 days_old: Math.floor(
16 (Date.now() - new Date(row.created_at)) / 86400000
17 ),
18 display_address: [row.city, row.state].filter(Boolean).join(', ')
19}));

Common mistakes when creating Calculated Columns in Retool Tables

Why it's a problem: Using {{ row.field }} instead of {{ currentRow.field }}

How to avoid: Inside Table column value expressions, the row context variable is always currentRow, not row or item. Using the wrong variable name results in undefined values.

Why it's a problem: Trying to use a calculated column as an editable field

How to avoid: Calculated columns have no backing database field. If you need users to edit a derived value, create a real column in your database and compute the default in your INSERT query.

Why it's a problem: Accessing nested objects without null checks and getting undefined errors

How to avoid: Use optional chaining: {{ currentRow.metadata?.city ?? 'Unknown' }}. Without it, a null metadata object will cause the expression to return undefined for every row.

Why it's a problem: Doing complex aggregations (sum, average) inside a calculated column expression

How to avoid: Calculated column expressions run per-row; they cannot aggregate across rows. Use a transformer or JS query to compute totals and bind the result to a separate Text component.

Best practices

  • Use {{ currentRow.field }} expressions for simple per-row computed values; use a standalone transformer when you need aggregations across all rows.
  • Keep expressions in {{ }} short — if you need more than a ternary, move the logic to a transformer or JS query.
  • Set the correct column type (Number, Currency, Date) so Retool applies correct sorting and formatting — don't rely on string representations for numeric columns.
  • Null-guard your expressions: use {{ currentRow.price ? ... : 'N/A' }} to avoid NaN or undefined rendering as blank cells.
  • Calculated columns are always read-only — if you need to write a derived value back to the database, compute it in a mutation query instead.
  • Use the Tag column type with a mapped color expression to turn conditional text into visual status badges without custom CSS.
  • Calculated columns are computed client-side on every render — avoid heavy operations (like sorting large arrays) inside {{ currentRow }} expressions.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a Retool app with a Table component bound to {{ query1.data }}. The query returns rows with fields: id, first_name, last_name, price, cost, created_at, and is_active. I want to add calculated columns for: (1) full_name combining first and last name, (2) margin_pct showing percentage margin from price and cost, and (3) a status label that shows 'Active' or 'Inactive' based on is_active. Write the {{ currentRow }} expressions I should use in the Table Inspector for each column, and explain how to handle null values safely.

Retool Prompt

Add three calculated columns to my Table component (table1) that is bound to query1.data. The columns should be: 1) 'Full Name' = first_name + ' ' + last_name, 2) 'Margin %' = (price - cost) / price * 100 rounded to 1 decimal, 3) 'Status' = 'Active' if is_active is true else 'Inactive'. Use {{ currentRow }} expressions and handle null values.

Frequently asked questions

Can calculated columns be sorted or filtered in Retool?

Sorting works on calculated columns as long as you set the correct column type (Number for numeric values). Filtering via the built-in filter bar only works on columns whose values are strings or numbers — complex objects will not filter correctly. For server-side filtering, use SQL WHERE clauses before the data reaches the table.

Do calculated columns affect the data written back to the database?

No. Calculated columns are purely display-side and never affect write operations. When you use table1.changesetArray to track edits, calculated columns are not included. Only columns backed by real query fields are part of the changeset.

Can I reference other components in a calculated column expression?

No. Calculated column expressions using {{ currentRow }} only have access to the current row's data. They cannot reference other components like textInput1.value or selectBox1.value. For cross-component logic, use a transformer instead and bind the table to {{ transformer1.value }}.

What is the difference between a calculated column and a transformer?

A calculated column adds a virtual column computed per-row inside the Table Inspector — it does not change the underlying data. A transformer is a standalone code block that reshapes the entire dataset before it reaches the table. Use calculated columns for row-level display logic, and transformers for structural reshaping like filtering, joining, or aggregating across multiple rows or queries.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Learning is great. Shipping is faster with help.

Our engineers have built 600+ apps on Retool and the tools around it. If your project needs to be live sooner than your learning curve allows — book a free consultation.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.