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

Filtering records over the API

The filter is JSON: field, operator, value, joined by one and/or. Every operator by field type, how to URL-encode the parameter, and the sort and search parameters that ride alongside.

Updated August 2026

The records list takes a filter= parameter: JSON naming a field, an operator, and a value. It is the same grammar the grid's Filter button builds — one language, whoever is asking — so anything you can click together in a view's filter, you can send over the wire, and the results match.

The shape

{ "join": "and",
  "conditions": [
    { "field": "Light", "op": "is", "value": "Low light" },
    { "field": "Price", "op": "less", "value": 30 }
  ] }

field is the field's name or its stable fld_… id — ids survive renames, names don't (names and ids). join is "and" or "or" and applies to the whole list; it defaults to "and". A condition may itself be a group with its own join and conditions — "A and (B or C)" — and that is where nesting stops: two levels. A third level is refused with Filter groups can nest 2 levels deep; this one goes deeper.

The filter travels URL-encoded in the query string. Let curl do the encoding:

curl -G "https://tabladb.com/api/tables/tbl_jukzu86nxtcj/records" \
  -H "Authorization: Bearer key_xxxx.your-secret" \
  --data-urlencode 'filter={"join":"and","conditions":[{"field":"Light","op":"is","value":"Low light"},{"field":"Price","op":"less","value":30}]}' \
  --data-urlencode 'fields=Name,Price,Light'
{"records": [
  {"id": "rec_y45z2yssdq4i", "fields": {"Name": "Snake plant", "Price": 28, "Light": "Low light"}},
  {"id": "rec_fxnmkz5wjyme", "fields": {"Name": "Golden pothos", "Price": 18, "Light": "Low light"}},
  {"id": "rec_pqcs6b7ebsa6", "fields": {"Name": "Peace lily", "Price": 26, "Light": "Low light"}}
], "field_ids": {"Name": "fld_rume7j84a473", "Price": "fld_t8y7aqpv8ykt", "Light": "fld_txz7eiyxd8r2"}}

(Record timestamps trimmed here and below.) For a filter too long for a URL, POST /api/tables/:id/records/list takes the identical parameters as a JSON body, filter as a real object — no encoding.

The operators, by field type

  • Text (text, long text — and email, phone, URL, member, which store text): is, is_not, contains, not_contains, empty, not_empty.
  • Number (number, currency, percent, rating, duration): is, is_not, greater, greater_or_equal, less, less_or_equal, empty, not_empty.
  • Date: is, before, after, on_or_before, on_or_after, empty, not_empty.
  • Checkbox: is, value true or false.
  • Select: is, is_not, any_of, none_of, empty, not_empty.
  • Select with allow multiple: has_any, has_all, has_none, empty, not_empty.
  • Linked records: has_any, has_all, has_none — values are lists of rec_… ids — plus empty, not_empty.
  • Attachment: empty and not_empty only.

Values follow the field: strings for text, numbers for number, "YYYY-MM-DD" or a full timestamp for dates, choice names or ids for selects, arrays for the list operators. Formulas, lookups and rollups filter by what they produce — {"field":"Total spent","op":"greater_or_equal","value":100} works on a rollup. A button holds no value and is refused by name.

Wrong is loud

A filter naming an unknown field, or an operator the field doesn't take, is a 400 that says so — never a quietly empty page:

{"error": {"code": "bad_request", "message": "Operator \"contains\" is not
  allowed on field \"Price\" (number). Allowed: is, is_not, greater,
  greater_or_equal, less, less_or_equal, empty, not_empty."}}

This is deliberate. An automation that reads "no matches today" and carries on is a worse failure than one that stops with a message naming the problem.

Sort and search ride alongside

sort= is a comma list of fields, -Field or Field:desc for descending; the record's creation order is always the final tiebreak. Sortable: text, long text, number, date, checkbox, single select (which sorts by its choices' defined order). A link or attachment can't be sorted and says so — Field "Customer" (link) cannot be sorted on. Same behavior as sorting in the grid.

search= matches every text-family field, case-insensitively, Arabic included: search=fern finds the Boston fern by name; it would find it by its care notes too.

All three combine on one request, and the filtered set pages with the same bookmark as everything else (pagination). The full grammar, operator by operator, is at /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