# 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: ```bash # 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 ```bash inkreach-teable table create --name "" [--base-id ] \ [--fields ''] [--description ] ``` | 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. Allowed types and aliases: | Shorthand | Full type | Shorthand | Full type | |-----------|-----------|-----------|-----------| | `text`, `str` | `singleLineText` | `long` | `longText` | | `num`, `int` | `number` | `check`, `bool` | `checkbox` | | `sel`, `select` | `singleSelect` | `multi`, `tags` | `multipleSelect` | | `date` | `date` | `rate`, `star` | `rating` | | `user`, `person` | `user` | `file`, `attach` | `attachment` | | `link`, `rel` | `link` | `rollup` | `rollup` | | `formula` | `formula` | `auto` | `autoNumber` | | `created` | `createdTime` | `modified` | `lastModifiedTime` | | `createdBy` | `createdBy` | `modifiedBy` | `lastModifiedBy` | | `button` | `button` | `condRollup` | `conditionalRollup` | **Compact shorthand** (in `--fields` only): `"name:type"` string format: ```bash 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 ```bash inkreach-teable table update --name "" [--description ] inkreach-teable table delete ``` | Operation | Idempotent? | Notes | |-----------|-------------|-------| | `update` | yes | Only changes name/description | | `delete` | **DESTRUCTIVE** | Deletes all records + fields; irreversible | ### List & Get ```bash inkreach-teable table list [--base-id ] inkreach-teable table get ``` `table get` returns `dbTableName` — the internal SQL table name used in `sql` queries (e.g., `SELECT * FROM "bseXXX"."tbl_abc"`). --- ## Fields ### Create ```bash inkreach-teable field create --table-id --name "" \ --type [--options ''] [--description ] ``` | Flag | Required | Notes | |------|----------|-------| | `--table-id` | yes | Target table | | `--name` | yes | Field display name | | `--type` | yes | Full type name or alias (see [field-types.md](../api-reference/field-types.md)) | | `--options` | depends on type | JSON object; see type-specific shapes below | | `--description` | no | Optional | **Type inference from name**: when `--type` is omitted, the CLI infers from the field name: | Name pattern | Inferred type | Name pattern | Inferred type | |-------------|---------------|-------------|---------------| | `status`, `priority`, `stage`, `category`, `type`, `level` | `singleSelect` | `tag`, `label` | `multipleSelect` | | `description`, `notes`, `comment`, `content`, `body` | `longText` | `count`, `amount`, `price`, `quantity`, `total`, `age` | `number` | | `rating`, `score`, `stars` | `rating` | `is*`, `has*`, `active`, `enabled`, `done`, `completed` | `checkbox` | | `assignee`, `owner`, `user` | `user` | `attachment`, `file`, `image`, `photo`, `document` | `attachment` | | `email` | `singleLineText` | `url`, `link` (without table context) | `singleLineText` | | `created time` / `created` | `createdTime` | `last modified` / `updated` | `lastModifiedTime` | | `created by`, `author` | `createdBy` | `modified by`, `updated by` | `lastModifiedBy` | **Options by type** (most common): | Type | Required options | Shape | |------|-----------------|-------| | `singleSelect` / `multipleSelect` | `choices` | `{"choices":["A","B","C"]}` or `{"choices":[{"name":"A","color":"blue"}]}` — colors auto-assigned if omitted | | `number` | none (defaults integer) | `{"formatting":{"precision":2}}` for decimal | | `rating` | none (defaults max=5, star, yellow) | `{"max":5,"icon":"star","color":"yellowBright"}` | | `date` | none | `{"formatting":{"date":"YYYY-MM-DD","time":"None","timeZone":"UTC"}}` | | `link` | `foreignTableId` / `foreignTableName` | `{"foreignTableId":"tblXXX","relationship":"manyOne"}` | | `formula` | `expression` | `{"expression":"{fieldId}+{otherFieldId}"}` | | `rollup` | via `--lookup-options` | See [field-types.md](../api-reference/field-types.md) | | `button` | `workflow` | `{"label":"Click","color":"blue","workflow":{"id":"wflXXX"}}` | **Name resolution**: for `link` fields, use `foreignTableName` instead of `foreignTableId` — the CLI resolves it automatically: ```bash inkreach-teable field create --table-id tblXXX --name "Project" --type link \ --options '{"foreignTableName":"Projects","relationship":"manyOne"}' ``` ### Update & Delete ```bash inkreach-teable field update [--name ""] [--options ''] inkreach-teable field delete ``` | 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 | ### Select-choice dependency (visibilityFilter) — 2026-08 Single/multiple-select choices may carry a `visibilityFilter` making them visible only when another select field matches: ```json {"choices":[{"id":"choA","name":"墨盒","visibilityFilter":{ "filterSet":[{"fieldId":"fldDeviceType","operator":"is","value":"choPrinter"}]}}]} ``` - **Read config**: `inkreach-teable field get ` → `options.choices[].visibilityFilter` - **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**: `inkreach-teable field visible-choices ` → 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) ```bash # All records (paginated) inkreach-teable record get --table-id [--filter ''] [--projection ''] \ [--page-size ] [--page ] [--search ] # Single record by ID inkreach-teable record get-one ``` | 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: > ```json > {"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 ```bash # Single record inkreach-teable record create --table-id --fields '' # Batch (up to 2000) inkreach-teable record create --table-id --records '' ``` **`--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: ```bash # 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 ``` **`--records` format** (batch): `{"header":["Field1","Field2"],"records":[["val1","val2"],["val3","val4"]]}` ### Update ```bash inkreach-teable record update --table-id --records '' ``` **`--records` format**: `[{"id":"recXXX","fields":{"FieldName":"newValue"}}]` — array of `{id, fields}` objects. Batch up to 2000. **Record reordering**: pass `--order` with `viewId`, `anchorId`, `position`: ```bash inkreach-teable record update --table-id tblXXX \ --records '[{"id":"recXXX","fields":{}}]' \ --order '{"viewId":"viwXXX","anchorId":"recYYY","position":"before"}' ``` ### Delete ```bash inkreach-teable record delete --table-id --record-ids "recA,recB,recC" ``` Max 1000 IDs per call. Irreversible. --- ## Views ### List & Get ```bash inkreach-teable view list inkreach-teable view get ``` ### Create ```bash inkreach-teable view create --table-id --name "" [--type ] ``` | `--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) ```bash inkreach-teable view update \ [--name ""] \ [--filter ''] \ [--sort ''] \ [--group ''] \ [--column-meta ''] ``` **Filters**: `{"conjunction":"and","filterSet":[{"fieldId":"fldXXX","operator":"is","value":"Done"}]}` **Sorts**: `[{"fieldId":"fldXXX","order":"asc"}]` **Group**: `[{"fieldId":"fldXXX","order":"asc"}]` **Column meta**: controls visibility, width, order of fields in the view. ### Delete ```bash inkreach-teable view delete ``` Deleting a view does NOT delete the table or its data — only the view configuration. --- ## 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** | | `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 | | Batch insert 500 records | One `record create --records` call | 500 separate `record create` calls |