A common question in the Quickbase community: "I want one Status field, but I also want to see every value it has ever had. How do I keep the history?" A field on a record holds one value. Each save replaces the last one. To keep the history, store each change somewhere else.
This guide covers two ways to do that:
- A child history table filled by a pipeline. Use this when you need history you can report on.
- An append-only multi-line text field. Use this when a running note on the form is enough.
Source: Using one field to collect data multiple times on the Quickbase Community (our answer).
Why two "previous/current" fields don't work
People often try adding a "Previous Status" field next to "Status" and copying one into the other. That keeps exactly one step of history. On the next change the old previous value is overwritten too. To see a full audit trail you need one record per change, and that means a child table.
Step 1: Build the history table
We built this in a sandbox app with the Quickbase JSON API. You can do the same thing in the UI.
Parent table: Projects
| Field | Type |
|---|---|
| Project Name | Text |
| Status | Text - Multiple Choice (Not Started, In Progress, On Hold, Complete) |
| Status Notes | Text - Multi-line, "Log the edits to this field" on (optional, see Option B) |
Child table: Status History
| Field | Type |
|---|---|
| Previous Status | Text |
| New Status | Text |
| Changed By | User |
| Changed On | Date / Time |
| Related Project | Numeric (reference field from the relationship) |
Next, create a one-to-many relationship from Projects to Status History. If you want, add a COUNT summary field ("# Status Changes") to Projects so you can see how many times a project has moved.
The same setup through the JSON API:
# Child field
curl -X POST "https://api.quickbase.com/v1/fields?tableId=<historyTableId>" \
-H "QB-Realm-Hostname: <realm>.quickbase.com" \
-H "Authorization: QB-USER-TOKEN <token>" \
-H "Content-Type: application/json" \
-d '{"label":"Previous Status","fieldType":"text"}'
# Relationship, with Project Name as a lookup
curl -X POST "https://api.quickbase.com/v1/tables/<historyTableId>/relationship" \
-H "..." -d '{"parentTableId":"<projectsTableId>","lookupFieldIds":[6]}'
# Add a COUNT summary to the existing relationship
curl -X POST "https://api.quickbase.com/v1/tables/<historyTableId>/relationship/<relationshipId>" \
-H "..." -d '{"summaryFields":[{"label":"# Status Changes","accumulationType":"COUNT"}]}'
Two things we ran into while testing:
choicesis rejected on a plaintextfield. UsefieldType: "text-multiple-choice"for Status.- When you create a COUNT summary, don't pass
summaryFid: 3. The API returns "That form of summarization/aggregation cannot be applied to this field." Leave outsummaryFidfor COUNT.
Step 2: Build the pipeline
These steps follow Quickbase's documented Change Log pattern. Pipelines can't be created through the JSON API, so we did not run this pipeline in our sandbox. Test it in your own app before you rely on it.
- Trigger: Quickbase channel → On New Event, on the Projects table.
- Set Trigger on Any Field to No. Under Trigger on Any of These Specific Fields, choose Status, plus any other fields you want to track.
- Turn On Modify Record on. You can leave On Create on if you want the first status recorded too.
- In Specify Fields for Use in Subsequent Steps, include Status. A field that isn't listed here can't be referenced later.
- Action: Quickbase → Create Record in Status History, with this field mapping:
| History field | Value |
|---|---|
| Related Project | {{a.id}} |
| New Status | {{a.status}} |
| Previous Status | {{a.$prev.status}} |
| Changed By | Last Modified By from the trigger |
| Changed On | Date Modified from the trigger |
$prev holds the value from before the edit. That's what lets one pipeline run record both sides of the change.
Step 3: Check the result
To check the table design and the reporting side, we added the rows the pipeline would create and queried them back:
POST /v1/records/query
{
"from": "<historyTableId>",
"select": [10, 11, 6, 7, 9],
"where": "{10.EX.1}",
"sortBy": [{ "fieldId": 9, "order": "DESC" }]
}
Result, newest first:
| Project | Previous | New | Changed On (UTC) |
|---|---|---|---|
| Warehouse Refresh | In Progress | On Hold | 2026-10-26 18:30 |
| Warehouse Refresh | Not Started | In Progress | 2026-10-26 15:00 |
The "# Status Changes" summary on the project returned 2. Put an embedded report of Status History on the Project form and you get the full timeline on the record.
Option B: An append-only text log
If you don't need to report on the history, use a Text - Multi-line field with Log the edits to this field turned on. Quickbase keeps every entry and stamps it with the date and user. We tested this through the API by writing three notes to the same field. The stored value was:
[OCT-26-26 Sarah Lee] Kickoff scheduled
[OCT-26-26 Mike Chen] Crew on site
[OCT-26-26 Sarah Lee] Waiting on materials
Each write was added to the log, including writes made through the API. The Status field itself just showed the latest value, "On Hold." The downside is that the log is a single block of text, so you can't filter, chart, or total it the way you can with a history table.
Troubleshooting
- The pipeline ran but no history row appeared. The usual cause is triggering on every field and then filtering the event out. Trigger only on Status.
- Previous Status is blank. Make sure Status is listed in Specify Fields for Use in Subsequent Steps. On a newly created record,
$prevhas nothing in it, which is expected. - The pipeline writes to the parent instead of creating a child. If the step is "Update Record" on Projects, you're back to overwriting. It needs to be "Create Record" on Status History.
- History rows aren't linked to the project. Map Related Project to the trigger's Record ID.
- Too many rows. If you're tracking several fields, consider one pipeline per field, or a "Field Changed" column, so reports stay readable.