Names and ids: what a rename breaks, and how not to care
Every field has a human name and a stable fld_ id; every record a rec_ id. Which one to use where, demonstrated by renaming a field live and watching one request break and one survive.
Updated August 2026
Everything in Tabla answers to two references. A human name — Price,
editable any time, unique within its table — and a stable id — fld_ plus
twelve characters, minted once, never changed. Records have only the id
(rec_…); tables (tbl_…) and databases (db_…) have both. The API takes
either, everywhere a field can be named: filters, sorts, fields=,
merge_on, cell keys on writes. The id is tried first, then the name; a name
that matches two fields is refused with a message asking for the id.
Cells come back keyed by name by default, because a payload that reads
"Price": 45 is one a person can debug. And every records response carries
the translation table for free — field_ids (name → id), or field_names
when you ask for key_by=id — so switching vocabularies never costs a second
request.
The rename, performed live
Not a thought experiment — this was run against the demo database's Suppliers
table. A field named Region exists; a filter by name works:
curl -G "https://tabladb.com/api/tables/tbl_6fn9jer8qrwu/records" \
-H "Authorization: Bearer key_xxxx.your-secret" \
--data-urlencode 'filter={"conditions":[{"field":"Region","op":"is","value":"Fayoum"}]}'
# → {"records":[{"id":"rec_d35xp96bgbup","fields":{"Name":"Fayoum Greens","Region":"Fayoum"}}], …}
Rename the field over the same API — its id is fld_vtpy8fte9xs3:
curl -X PATCH "https://tabladb.com/api/fields/fld_vtpy8fte9xs3" \
-H "Authorization: Bearer key_xxxx.your-secret" \
-H "Content-Type: application/json" -d '{"name":"Area"}'
The by-name request now fails — loudly, not with an empty page:
{"error":{"code":"bad_request","message":"Unknown field \"Region\"."}}
The by-id request never noticed:
--data-urlencode 'filter={"conditions":[{"field":"fld_vtpy8fte9xs3","op":"is","value":"Fayoum"}]}'
# → {"records":[{"id":"rec_d35xp96bgbup","fields":{"Name":"Fayoum Greens","Area":"Fayoum"}}], …}
Same record, same value, new name — and the request that carried the id
survived the rename without an edit. Pair it with key_by=id and the cells
come back keyed "fld_vtpy8fte9xs3": "Fayoum", so the response can't break
on a rename either.
key_by works on writes too, not only reads: POST, PATCH and PUT all
take it, and all three key their reply and carry the companion map exactly as
a read does. So an automation can write by field id and read its own result
back by field id, with nothing in the round trip that a rename can touch. One
difference: a write has no fields= projection to describe, so its map covers
every field on the table.
Where the ids live
- The fields endpoint —
GET /api/tables/:id/fieldslists every field with its id, type and the filter operators it accepts. Add?writable=trueand it returns only the fields a write can touch, with each select choice carryinglabel/valuealiases for building a dropdown. - Every records response — the
field_ids/field_namescompanion map, on writes as well as reads. - The record panel — its
⋯menu has Copy record ID (and Copy record link) for the record in front of you (the record panel). - The address bar — the table id is right in the URL:
/tables/tbl_….
The trade, plainly
Names read well and break on rename. Ids read like line noise and never
break. So: exploring in a terminal, debugging a payload, a one-off script —
use names, they're the pleasant choice. Anything unattended — an automation,
a scheduled sync, a webhook consumer, code with a mapping table — use ids and
key_by=id, because someone will rename Region to Area on a Tuesday
and your integration should not be the thing that notices.
The deliberate design here: a rename must not break the screen or the data (it never does — values and choice ids are stored rename-proof), and an integration that chose names gets a loud 400 naming the missing field, never a silent empty result it would happily process as "nothing today." Filters are compiled strictly for exactly this reason (filtering, writing). The stability promise — ids opaque and permanent — is written into /docs/api.