Files

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 --fields creates the table AND all fields atomically. For existing tables, use field 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-options shows 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 fix 26ed1f732
  • 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

⚠️ isAnyOf only works for select/link/user fields, NOT for text/number fields. For text fields with multiple values, use or conjunction with multiple is rules:

{"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. Use null to empty a cell. For checkbox: true = checked, null = unchecked (NOT false).

--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-meta snapshots 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 requires viewId), 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