20 KiB
Tables, Fields, Records & Views
Complete guide for building table structures and working with data via the inkreach-teable CLI.
Quick Start
Minimal walkthrough — create a table with fields in one shot, then insert a record:
# 1. Create a "Tasks" table with 4 fields
inkreach-teable table create --name "Tasks" --base-id bseXXX \
--fields '[
{"name":"Title","type":"singleLineText"},
{"name":"Status","type":"singleSelect","options":{"choices":["Todo","Doing","Done"]}},
{"name":"Due Date","type":"date"},
{"name":"Est Hours","type":"number","options":{"formatting":{"precision":1}}}
]'
# 2. Insert a record
inkreach-teable record create --table-id tblXXX \
--fields '{"Title":"Ship sign-up flow","Status":"Doing","Due Date":"2025-06-15","Est Hours":3.5}'
# 3. Verify
inkreach-teable record get --table-id tblXXX
Tip
:
table create --fieldscreates the table AND all fields atomically. For existing tables, usefield create.
Tables
Create
inkreach-teable table create --name "<name>" [--base-id <id>] \
[--fields '<json>'] [--description <desc>]
| Flag | Required | Description |
|---|---|---|
--name |
yes | Display name (unique within base) |
--base-id |
no | Defaults to stored config |
--fields |
no | Array of field objects (see below) |
--description |
no | Optional description |
--fields format: JSON array of {name, type, [options], [description]} objects. Types accept full values or aliases (sel → singleSelect); per-type option shapes and inference rules: field-types.md.
Compact shorthand (in --fields only): "name:type" string format:
inkreach-teable table create --name "Projects" --fields '["Name:text","Budget:num","Active:check"]'
With select options: "Status:sel:Todo,Doing,Done", "Tags:mul:Bug,Feature,UI"
Update & Delete
inkreach-teable table update <tableId> --name "<newName>" [--base-id <id>]
inkreach-teable table delete <tableId>
| Operation | Idempotent? | Notes |
|---|---|---|
update |
yes | Only changes name/description |
delete |
DESTRUCTIVE | Deletes all records + fields; irreversible |
List & Get
inkreach-teable table list [--base-id <id>]
inkreach-teable table get <tableId> [--base-id <id>]
table get returns dbTableName — the internal PG table name (useful for
maintainer-level psql inspection; there is NO SQL query API on this deployment).
Fields
Create
inkreach-teable field create --table-id <tid> --name "<name>" \
--type <type> [--options '<json>'] [--description <d>]
| Flag | Required | Notes |
|---|---|---|
--table-id |
yes | Target table |
--name |
yes | Field display name |
--type |
no | Full type name or alias; omitted → inferred from --options shape or --name pattern (rules: field-types.md § Smart Inference) |
--options |
depends on type | JSON object; select choices accept plain strings — see field-types.md § Option Shapes |
--description |
no | Optional |
Name resolution: for link fields, use foreignTableName instead of foreignTableId — the CLI resolves it automatically:
inkreach-teable field create --table-id tblXXX --name "Project" --type link \
--options '{"foreignTableName":"Projects","relationship":"manyOne"}'
Update & Delete
inkreach-teable field update <tableId> <fieldIdOrName> [--name "<n>"] [--options '<json>']
inkreach-teable field delete <tableId> <fieldId>
| Detail | Rule |
|---|---|
--name only |
Uses the PATCH update API (name/description only) |
--options |
Rewrites options via the convert API (PUT …/field/:fid/convert) — the PATCH update API strips options by design. CLI auto-fetches the field's current type; omitted keys are replaced whole, not merged |
--name + --options |
Both applied via one convert call |
field delete |
Irreversible — all cell data in that field is lost |
Inspecting fields & constraints
field get and field visible-choices accept fieldId or field name (the CLI resolves names automatically). Human mode renders select choices with their conditions — no --json post-processing needed:
# One-field overview: which fields carry conditional choices
inkreach-teable field list tblXXX --with-options
# ID NAME TYPE OPTIONS
# ... 处理反馈 singleSelect 21 choices · 19 conditional
# followed by per-choice listings: name [color] + "when: <condition summary>"
# Single field by name — choices expanded with human-readable conditions
inkreach-teable field get tblXXX 处理反馈
# choices (21 · 19 conditional):
# 待工厂补发 [orange]
# when: 问题类型 is 物流问题-超时未揽收 OR 问题类型 is 物流问题-丢件 …
# What a specific record may currently select
inkreach-teable field visible-choices tblXXX recXXX 处理反馈
Select-choice dependency (visibilityFilter) — 2026-08
Single/multiple-select choices may carry a visibilityFilter making them visible only when another select field matches:
{"choices":[{"id":"choA","name":"墨盒","visibilityFilter":{
"filterSet":[{"fieldId":"fldDeviceType","operator":"is","value":"choPrinter"}]}}]}
- Read config:
field get <tableId> <fieldIdOrName>→options.choices[].visibilityFilter(rendered human-readable in default mode; raw JSON with--json) - Survey a whole table:
field list <tableId> --with-optionsshows a constraint count per field plus per-choice conditions - Write config:
field update --options(convert API). Backend rejects self-reference (Self-reference in visibilityFilter is not allowed) and dependency cycles (Dependency cycle detected…) with 400 — enforced on both V1 and V2 paths since fix26ed1f732 - Evaluate for a record:
field visible-choices <tableId> <recordId> <fieldIdOrName>→ choices currently selectable - Write-time enforcement: writing a record value that fails its visibility condition → 400
Option "X" does not meet the current row conditions and cannot be selected.
Records
Get (read)
# All records (paginated)
inkreach-teable record get --table-id <tid> [--filter '<json>'] [--projection '<json>'] \
[--page-size <n>] [--page <n>] [--search <kw>]
# Single record by ID (table-scoped route)
inkreach-teable record get-one <recordId> --table-id <tid>
| Flag | Description |
|---|---|
--filter |
Filter expression: {"fieldId":{"operator":"value"}} |
--projection |
JSON array of field IDs to return: '["fldXXX","fldYYY"]' |
--page-size |
Records per page (default 100) |
--search |
Keyword search: "keyword" (auto-wraps to {"value":"keyword"}) |
--view-id |
Fetch in view order with view's filter/sort/group applied |
Filter operators by field type:
| Field Type | Supported Operators |
|---|---|
singleLineText longText url email phone |
is isNot contains doesNotContain isEmpty isNotEmpty |
number currency percent duration rating |
is isNot isGreater isGreaterEqual isLess isLessEqual isEmpty isNotEmpty |
checkbox |
is |
date createdTime lastModifiedTime |
is isNot isWithIn isBefore isAfter isOnOrBefore isOnOrAfter isEmpty isNotEmpty |
singleSelect (single) |
is isNot isAnyOf isNoneOf isEmpty isNotEmpty |
singleSelect (multi) |
hasAnyOf hasAllOf isExactly isNotExactly hasNoneOf isEmpty isNotEmpty |
multipleSelect |
hasAnyOf hasAllOf isExactly isNotExactly hasNoneOf isEmpty isNotEmpty |
user createdBy lastModifiedBy (single) |
is isNot isAnyOf isNoneOf isEmpty isNotEmpty |
user createdBy lastModifiedBy (multi) |
hasAnyOf hasAllOf isExactly hasNoneOf isNotExactly isEmpty isNotEmpty |
link (single) |
is isNot isAnyOf isNoneOf contains doesNotContain isEmpty isNotEmpty |
link (multi) |
hasAnyOf hasAllOf isExactly hasNoneOf isNotExactly contains doesNotContain isEmpty isNotEmpty |
attachment |
isEmpty isNotEmpty |
⚠️
isAnyOfonly works for select/link/user fields, NOT for text/number fields. For text fields with multiple values, useorconjunction with multipleisrules:{"conjunction":"or","filterSet":[ {"fieldId":"fldX","operator":"is","value":"val1"}, {"fieldId":"fldX","operator":"is","value":"val2"} ]}
Date sub-operators (with is / isNot / isWithIn / isBefore etc.):
| Operator | Description |
|---|---|
today tomorrow yesterday |
Relative day |
currentWeek lastWeek nextWeekPeriod |
Relative week |
currentMonth lastMonth nextMonthPeriod |
Relative month |
currentYear lastYear nextYearPeriod |
Relative year |
oneWeekAgo oneWeekFromNow |
±1 week |
oneMonthAgo oneMonthFromNow |
±1 month |
daysAgo daysFromNow |
±N days (set value) |
exactDate |
Specific date: "2025-06-15" |
exactDateTime |
Specific datetime: "2025-06-15T09:00:00.000Z" |
pastWeek pastMonth pastYear |
Last week/month/year (for isWithIn) |
nextWeek nextMonth nextYear |
Next week/month/year (for isWithIn) |
pastNumberOfDays nextNumberOfDays |
±N days (for isWithIn, set value) |
Conjunction: "and" (all must match) or "or" (any matches). Filter sets can nest for complex logic.
Create
# Single record
inkreach-teable record create --table-id <tid> --fields '<json>'
# Batch (up to 2000)
inkreach-teable record create --table-id <tid> --records '<json>'
--fields format (single): {"FieldName":"value","Another":"value"} — field names resolve automatically.
Value semantics:
| Value | Meaning |
|---|---|
"string" |
Normal value |
null |
Clear the cell |
"" (empty string) |
Skip (no change; update only) |
Important
:
""is skip, not clear. Usenullto empty a cell. For checkbox:true= checked,null= unchecked (NOTfalse).
--typecast: auto-converts human-readable values to internal IDs. Essential for user and link fields:
# Without typecast — must use IDs
inkreach-teable record create --table-id tblXXX --fields '{"Assignee":{"id":"usrXXX","title":"Alice"}}'
# With typecast — use display names
inkreach-teable record create --table-id tblXXX --fields '{"Assignee":"Alice"}' --typecast
Attachment URL import (2026-10): attachment fields accept http(s) URLs — the backend downloads the file (SSRF-guarded, size-capped) and stores it as a real attachment with thumbnail. Two forms:
# Object form — works without --typecast; name is optional (derived from the URL)
inkreach-teable record create --table-id tblXXX \
--fields '{"Files":[{"url":"https://example.com/f.png","name":"report.png"}]}'
# String form — needs --typecast; comma-separates URLs and existing actXXX ids
inkreach-teable record create --table-id tblXXX --typecast \
--fields '{"Files":"https://example.com/a.png,https://example.com/b.png"}'
Notes: only http:/https: URLs; URLs containing commas cannot be expressed in
the string form (use the object form); a failed download rejects the whole
request with 400 (nothing is silently dropped); V1 (legacy) bases require
--typecast for the string form, V2 bases accept both forms directly.
--records format (batch): {"header":["Field1","Field2"],"records":[["val1","val2"],["val3","val4"]]}
Update
inkreach-teable record update --table-id <tid> --records '<json>'
--records format: [{"id":"recXXX","fields":{"FieldName":"newValue"}}] — array of {id, fields} objects. Batch up to 2000.
Record reordering: pass --order with viewId, anchorId, position (works on both record create — insert at position — and record update — move existing records; only meaningful in a view without an explicit sort):
inkreach-teable record update --table-id tblXXX \
--records '[{"id":"recXXX","fields":{}}]' \
--order '{"viewId":"viwXXX","anchorId":"recYYY","position":"before"}'
--field-key-type <name|id|dbFieldName> (default name): read --fields/--records keys as field ids instead of names — useful when field names contain characters that are awkward in JSON keys, or when you already have fldXXX ids from field list --json:
inkreach-teable record create --table-id tblXXX --field-key-type id \
--fields '{"fldXXXX":"hello"}'
Delete
inkreach-teable record delete --table-id <tid> recA recB recC # or --record-ids "recA,recB,recC"
Max 1000 IDs per call. Irreversible.
Views
List & Get
inkreach-teable view list <tableId>
inkreach-teable view get <viewId> --table-id <tid>
Create
inkreach-teable view create --table-id <tid> --name "<name>" [--type <t>]
--type |
Description |
|---|---|
grid |
Default spreadsheet view |
kanban |
Board (requires a singleSelect/link grouping field) |
form |
Form input view |
gallery |
Card gallery |
calendar |
Calendar (requires a date field for grouping) |
Update (configure filters, sorts, grouping)
inkreach-teable view update <viewId> --table-id <tid> \
[--name "<n>"] [--description "<d>"] \
[--filter '<json>'] \
[--sort '<json>'] \
[--group '<json>'] \
[--column-meta '<json>']
Filters: {"conjunction":"and","filterSet":[{"fieldId":"fldXXX","operator":"is","value":"Done"}]}
Sorts: [{"fieldId":"fldXXX","order":"asc"}] (the CLI wraps it into {sortObjs:[...]} for the API)
Group: [{"fieldId":"fldXXX","order":"asc"}]
Column meta: controls visibility, width, order of fields in the view.
Delete
inkreach-teable view delete <viewId> --table-id <tid>
Deleting a view does NOT delete the table or its data — only the view configuration.
View Presets
Presets are view-scoped, named bundles of view configuration (filter/sort/group, optionally options/columnMeta), stored server-side and shared with all collaborators — each view owns its own preset list. Users apply them from the toolbar preset dropdown (labeled by current state: 公共 / 个人 / active preset name; the old "个人" toggle button was merged into this dropdown). Applying seeds the user's personal-view overlay (persisted server-side per user per view), so operators can work in different presets simultaneously without touching the collaborative view. Managing presets requires table-level view-update permission.
Typical use
One-click screening configs for operators, e.g. "pending orders" vs "in-progress orders" on an order table, without granting view-edit permission.
Commands
# preset screening pending orders (bound to one view)
inkreach-teable preset create --table-id tblXxx --view-id viwXxx --name "待处理订单" \
--filter '{"conjunction":"and","filterSet":[{"fieldId":"fldStatus","operator":"is","value":"待处理"}]}'
# preset for in-progress orders, newest first
inkreach-teable preset create --table-id tblXxx --view-id viwXxx --name "正在处理的订单" \
--filter '{"conjunction":"and","filterSet":[{"fieldId":"fldStatus","operator":"is","value":"处理中"}]}' \
--sort '{"sortObjs":[{"fieldId":"fldCreated","order":"desc"}]}'
inkreach-teable preset list tblXxx --view-id viwXxx
inkreach-teable preset update <presetId> --table-id tblXxx --name "新名字" # or replace --filter/--sort/...
inkreach-teable preset delete <presetId> --table-id tblXxx
Semantics
- Config keys you do NOT pass stay unset: on apply, the preset keeps the view's current value for those keys (a filter-only preset does not touch the sort).
- Keys you DO pass replace that aspect on apply.
--column-metasnapshots field visibility/order — avoid unless needed: fields added later stay hidden while that preset is active (stale snapshot).- Deleting a preset does not touch views or records; users applying it fall back to the public config on next switch.
- REST equivalents:
GET/POST /api/table/:tableId/view-preset(list accepts?viewId=; create requiresviewId),PUT/DELETE /api/table/:tableId/view-preset/:presetId(writes need view-update permission). - Per-user personal config:
GET/PUT/DELETE /api/table/:tableId/view-preset/personal?viewId=(view-read permission; PUT body{config, enabled}) — this is what the UI "个人配置" entry persists.
Value Formats Quick Reference
| Field Type | Value format | Notes |
|---|---|---|
singleLineText / longText |
"string" |
|
number |
42 or 3.14 |
Integer or float |
checkbox |
true / null |
NOT false for unchecked |
date |
"2025-06-15" or "2025-06-15T09:00:00.000Z" |
ISO 8601; "now" maps to current time |
singleSelect |
"Option Name" |
(string) |
multipleSelect |
["Tag A","Tag B"] |
(array of strings) |
rating |
3 |
Integer 1..max |
attachment |
[{"name":"f.png","token":"xxx"}] |
Always array of objects; update replaces all. Also accepts [{"url":"https://…","name":"f.png"}] (server downloads the file) and, with --typecast, a plain URL string — see attachment URL import below |
user |
{"id":"usrXXX","title":"Alice"} |
With --typecast: just "Alice" or "alice@example.com" |
link |
[{"id":"recXXX"}] |
With --typecast: just "Linked Record Name" |
formula / rollup / lookup |
Read-only | Cannot be set via record create/update |
autoNumber / createdBy / createdTime / lastModifiedTime / lastModifiedBy |
Auto-populated | Cannot be set manually |
button |
{"count":0} |
Read-only; incremented by button clicks |
Common Patterns
| User intent | Correct approach | Wrong approach |
|---|---|---|
| Create a table with fields | table create --fields '[...]' — one call |
Multiple field create calls (slower, more error-prone) |
| Use display names for linked records | record create --fields '{"Project":"Acme"}' --typecast |
Manually looking up recXXX IDs |
| Use email to assign user | record create --typecast --fields '{"Assignee":"alice@co.com"}' |
Looking up usrXXX IDs |
| Clear a cell | null in the fields object |
"" (which means skip!) |
| Update one field, leave others | --records '[{"id":"recXXX","fields":{"Status":"Done"}}]' |
Re-sending all fields (unnecessary, slower) |
| Fetch only 2 columns | record get --projection '["fldXXX","fldYYY"]' |
Fetching all fields (wastes bandwidth) |
| Find records by keyword | record get --search "keyword" |
record get then filtering manually |
| Add a select field with colored options | --options '{"choices":["A","B"]}' — colors auto |
Manually specifying color for every choice |
| Make a link field to another table | --type link --options '{"foreignTableName":"Target","relationship":"manyOne"}' |
Using IDs + manual lookup |
| Reorder records in a view | record update --order '{"viewId":...,"anchorId":...,"position":"before"}' |
Deleting and re-creating records |
| View data in kanban board | view create --type kanban then view update --group |
Creating special columns |
| Set datetime in a date field | "2025-06-15T14:30:00.000Z" |
"2025-06-15 14:30" (non-ISO; may be rejected) |
| Update attachments | Always pass the full array (replaces all) | Trying to add/remove individual attachments |
| Attach a file by URL | record create --fields '{"Files":[{"url":"https://…/f.png"}]}' (object form, no typecast needed) or --typecast --fields '{"Files":"https://…/f.png"}' |
Uploading via the attachments signature/notify endpoints first (only needed for local files) |
| Batch insert 500 records | One record create --records call |
500 separate record create calls |