feat: add a first-class user field type (person picker)#2351
Merged
Conversation
A `user` field type — the equivalent of Airtable's Collaborator / Notion's
Person / Salesforce's `Lookup(User)`. Authored as `Field.user({ ... })`;
`{ multiple: true }` for collaborators/watchers and
`{ defaultValue: 'current_user' }` to auto-fill the acting user on create.
Designed as a SEMANTIC SPECIALIZATION OF `lookup` with the target fixed to the
`sys_user` system object — NOT a new storage primitive. It shares the exact
lookup code path: same FK string column (multiple ⇒ JSON), same `$expand`
resolution, same indexing. So referential integrity and fresh display names
come for free, an existing `Field.lookup('sys_user')` is equivalent at the
storage layer (zero data migration), and nothing is re-implemented.
The distinct type exists for modelling discoverability — a "User" entry in the
Studio/AI field palette instead of requiring authors (and AI) to know to
reference the internal `sys_user` object — plus a user-search picker and
`current_user` defaults.
Ownership semantics are unchanged: the existing `owner_id` convention +
plugin-security auto-stamp/RLS still apply. A declarative `owner` field type
was intentionally NOT added — ownership is a system role (one per object,
auto-stamped), already handled, and a second type would only grow the
platform-wide `FieldType` surface for marginal benefit.
Changes:
- spec: `FieldType` gains `'user'` + `Field.user()` builder; seed-loader /
GraphQL / SQL type-compat reference maps accept `user`.
- drivers (sql, mongodb): treat `user` exactly like `lookup`.
- engine: resolve `$expand` for `user` fields; honour a new
`defaultValue: 'current_user'` token (resolved app-side from the execution
context, mirroring the `NOW()` convention).
- kanban group-by + symbolic seed references + approvals enrichment accept `user`.
Tests: engine $expand-for-user + current_user stamp (present / absent actor);
driver-sql string-id + multiple⇒JSON round-trip. Public API surface unchanged
(additive enum member).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 7 package(s): 100 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
`field-zoo` is the exhaustive FieldType coverage object, and `coverage.test.ts` asserts every member of `FieldType` appears across the showcase objects. Adding `'user'` to the enum without a representative field made "covers every FieldType" fail (uncovered → user). Add single, multiple, and `current_user`-default `user` fields so the guard is satisfied and the new type gets real data-layer coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The public-form schema endpoint strips `lookup`/`master_detail` fields from anonymous forms unless the designer opts in via `publicPicker` — so a stray spec mistake can't expose unrestricted record search to the internet. A `user` field is a lookup specialized to `sys_user`, so the same risk applies: it would surface a user search/list to unauthenticated visitors. Gate it behind the same `publicPicker` opt-in. (The companion `/forms/:slug/lookup/:field` endpoint already 403s without `publicPicker`, independent of type — this closes the form-schema side.) Found while dogfooding the new `user` field type end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jun 26, 2026
Follow-up to #2351 (the `user` field type). Brings the docs in line with the shipped feature: - references/data/field.mdx + seed-loader.mdx — regenerated (`gen:docs`) so the FieldType / reference enums list `user` (the merge added the type but didn't regenerate these committed reference pages). - guides/data-modeling.mdx — new "User Fields" section: `Field.user()`, `multiple`, `defaultValue: 'current_user'`, and that it's a lookup specialized to sys_user (stored as an FK, zero-migration from `Field.lookup('sys_user')`). - guides/standards.mdx — adds a User/Person row to the field-type selection table. Only the user-related regenerated lines are included; unrelated generated-doc drift was intentionally left out. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a
userfield type — the equivalent of Airtable's Collaborator / Notion's Person / Salesforce's Lookup(User). Authored asField.user({ ... });{ multiple: true }for collaborators/watchers and{ defaultValue: 'current_user' }to auto-fill the acting user on create.Why a distinct type (not just
Field.lookup('sys_user'))Selecting a person is table-stakes, but you can already store one via a lookup —
owner_id/created_byalready do. The value is modelling discoverability: a "User" entry in the Studio/AI field palette instead of requiring authors (and the AI authoring path) to know to reference the internalsys_usersystem object — pluscurrent_userdefaults and a user-search picker.Design: a semantic specialization of
lookup, not a new storage primitiveusershares the exact lookup code path — same FK string column (multiple⇒ JSON), same$expandresolution, same indexing. Consequences:Field.lookup('sys_user')is equivalent at the storage layer → zero data migration to adoptField.user.This mirrors how relational platforms (Salesforce, ServiceNow, Dataverse) model users as a reference to the user object, while presenting a first-class type at the authoring/UX layer like Airtable/Notion.
Ownership left unchanged
A declarative
ownerfield type was intentionally not added. Ownership is a system role (one per object, auto-stamped) already handled by theowner_idconvention +plugin-securityauto-stamp/RLS; a second type would only grow the platform-wideFieldTypesurface for marginal benefit.Changes
FieldTypegains'user'+Field.user()builder; seed-loader / GraphQL / SQL type-compat reference maps acceptuser.userexactly likelookup.$expandforuserfields; honour a newdefaultValue: 'current_user'token (resolved app-side from the execution context, mirroring theNOW()convention).user.Tests
$expand-for-user;current_userstamp (present / absent actor).multiple⇒ JSON round-trip.Paired UI
objectui
feat/user-field-pickerwires theUserFieldpicker tosys_user.🤖 Generated with Claude Code