Skip to content
Open beta — everything’s free right now, and your rate is locked when it ends.

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 endpointGET /api/tables/:id/fields lists every field with its id, type and the filter operators it accepts. Add ?writable=true and it returns only the fields a write can touch, with each select choice carrying label/value aliases for building a dropdown.
  • Every records response — the field_ids / field_names companion 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.

Tabla is the database we build these on.

A no-code database with real Postgres underneath: every feature on every plan, a million records per database, and your whole database back out in one file, any day.

More guides