Skip to Content
核心概念欄位識別

欄位身分:顯示名稱與內部鍵

每個自訂欄位同時有「給人看的名稱」與「給系統追蹤身分的鍵」。把兩者混成同一件事,最常見的結果是欄位改名後,查詢、規則或跨表引用突然失效;分清楚後,改名就只是 UI 標籤變更。

一個欄位,兩個名稱

  • 顯示名稱(display name)是使用者看到與編輯的名稱,例如 訂單狀態。可以是中文,且只需在同一張表內唯一。
  • 內部鍵是建立欄位時由伺服器產生、之後不變的 col_<hex>,例如 col_a1111111_1111_4111_8111_111111111111

兩者的方向固定記在 settings.column_mapping

{ "settings": { "column_mapping": { "訂單狀態": "col_a1111111_1111_4111_8111_111111111111", "總金額": "col_b2222222_2222_4222_8222_222222222222" } } }

也就是規格中的 {original_name: internal_uuid_name}:key 是目前顯示名稱,value 是內部 UUID 名稱。id 是系統欄位,不會被改成 col_...

不要自行產生 mapping

column_mapping 是伺服器擁有的狀態。建表時即使傳入自己的 mapping,也不能把它當成已採用;請以建立/讀取表的回應為準。

改名為什麼不會破壞引用

假設使用者把 訂單狀態 改名為 處理狀態。伺服器只替換 mapping 的顯示名稱 key:

{ "處理狀態": "col_a1111111_1111_4111_8111_111111111111" }

內部鍵沒有變,既有資料仍存於同一個 JSON key;規則、公式、連結與查詢設定持久化的引用也仍指向同一欄。因此 React key、表單 state、快取索引與拖曳欄位 ID 都應使用 column.id/內部鍵,顯示名稱只負責標題與標籤。

寫入與讀取是刻意不對稱的

資料列寫入接受顯示名稱或已知的內部鍵。伺服器會先用當下的 column_mapping 把顯示名稱轉成內部鍵,再驗證與儲存:

{ "data": { "訂單狀態": "待處理", "col_b2222222_2222_4222_8222_222222222222": 1280 } }

讀取資料列時則反向映射,record.data 回傳顯示名稱,適合直接渲染:

{ "data": { "id": "77777777-7777-4777-8777-777777777777", "訂單狀態": "待處理", "總金額": 1280 } }

data 裡的 id 是內部產物,不是資料列的主鍵——它永遠不等於客戶端拿在手上的頂層 record id。篩選、lookup 與 link 一律用頂層 record id;把 data.id 抄進 id 篩選什麼都比不到(見查詢 › 系統 id 欄位)。

Principal 欄位在命名之外還多一層不對稱:寫進去是身分字串(usersocial_client 是原始 id,principal 是帶標籤的 user:<id>smc:<id>room:<id> cell),讀回來是顯示物件。讀取加工器以顯示名稱為鍵,透過 column_mapping 反查,所以即使寫入時用的是內部 col_<hex> 鍵,加工後的 cell 仍然會出現在顯示名稱底下。要把寫出去的 cell 和讀回來的 cell 對起來,請先建好反向 map。

這個便利性不代表每一個查詢欄位都接受顯示名稱。records/searchfiltersstored_filterssort_by 與 saved-view 內的同名欄位要求內部鍵;完整矩陣見查詢資料列。穩健的 client 應在讀表時建立正反兩張 map:

const displayToInternal = table.settings.column_mapping const internalToDisplay = Object.fromEntries( Object.entries(displayToInternal).map(([display, internal]) => [internal, display]), )

規則接受兩種名稱,但持久化內部鍵

設定 write rules 時,同表欄位引用可以傳顯示名稱或內部鍵。後端驗證後一律存成內部鍵,所以 whencompareuniquerequire_approval 等規則在改名後仍成立。一般 GET .../rules 會把「本表」的引用轉回顯示名稱,方便編輯器呈現。

existsnot_exists 是重要例外。它們同時引用目前表與另一張 target table:

{ "type": "exists", "table_id": "44444444-4444-4444-8444-444444444444", "match": { "col_c3333333_3333_4333_8333_333333333333": "客戶" }, "where": [ { "column": "col_d4444444_4444_4444_8444_444444444444", "op": "eq", "value": "有效" } ] }

回顯時,matchkeywhere[].column 屬於 target table,會保留 target 的內部鍵;match value 或 $row.<欄位> 這類本表引用才會轉回本表顯示名稱。要顯示規則摘要,前端必須另外讀取 table_id 指向的目標表,再用目標表自己的 column_mapping 解碼。絕對不能拿目前表的 mapping 解 target key。

公式、連結與衍生欄也靠內部身分

link_fieldtarget_column、rollup 的 matchfilter,以及 formula expression 中的欄位引用,在 authoring payload 通常可用顯示名稱或內部鍵;伺服器解析後建立的是內部鍵依賴圖。這也是連結欄或被參照欄改名後,lookup、rollup 與公式仍能正確運作的原因。

回應為了可讀性可能把本表引用顯示成名稱;formula 遇到無法安全放回語法的名稱時,也可能保留可再次提交的內部鍵。因此編輯器應以欄位 ID 儲存 AST/選項值,不要解析回顯文字來猜身分。跨表引用更要分別保留來源與目標表 mapping。

深入設定請參考所有欄位型別連結欄公式欄規則

IaC 中的身分

JSONL IaC 的 ref 是檔案內物件的宣告身分,不是 column_mapping 的替代品。伺服器套用 table/column line 後仍會產生與保存真實的 col_<hex> 鍵;export/state 才是後續演進時應沿用的伺服器狀態。

實務上應採用 export → 編輯 → plan → apply 流程,不要手寫 column_mapping 或靠顯示名稱去對照既有欄位。詳見 IaC 概念IaC 工作台

前端實作檢查表

  1. 建表或加欄後,立即保存最新的 column_mapping
  2. UI 顯示顯示名稱,但 component key 與持久化 selection 使用內部鍵。
  3. 資料列表單可送顯示名稱;共用 API client 仍應能把它轉成內部鍵,讓 search/view payload 一致。
  4. 每次欄位改名後重新抓表,不要只修改本地標題。
  5. 解讀跨表規則、公式與連結時,使用「被引用那張表」的 mapping。

端點與真實回應形狀見欄位參考資料列參考規則參考IaC 參考

Last updated on