Schema Design
Naming Conventions
| Thing | Convention | Examples |
|---|---|---|
| Entity keys | snake_case, plural |
tasks, contacts, inventory_items |
| Entity names | Title Case, plural | "Tasks", "Contacts", "Inventory Items" |
| Field keys | snake_case |
due_date, is_complete, contact_email |
| Field names | Title Case | "Due Date", "Completed", "Contact Email" |
Best Practices
Use select over text for constrained values
If a field has a known set of valid values (status, priority, category), use select rather than text. This ensures data consistency and enables efficient filtering.
1✓ status: select ["open", "in_progress", "done"]2✗ status: text (free-form — "Open", "In Progress", "DONE" are all different)
Use multi_select for tags
When a record can belong to multiple categories simultaneously, use multi_select:
1tags: multi_select ["frontend", "backend", "design", "devops"]
Mark truly required fields
Only mark a field as required if the record is meaningless without it. Overly strict required fields frustrate users and complicate imports.
Use relation for cross-entity references
When a record in one entity refers to a record in another entity in the same app, use type: relation rather than a plain text field. The stored value is the target record's ULID. Configure related_entity to point at the target entity's key, and optionally display_field to specify which field on that entity to show as a label.
1✓ project_id: relation → related_entity: "projects", display_field: "name"2✗ project_id: text (free-form string — no referential integrity or validation)
Example use case: a "tasks" entity has a project_id relation field pointing to the "projects" entity. Each task record stores the ULID of its parent project. To load the referenced project's data, call sdk.getRecord('projects', record.project_id).
WorkApps validates that the referenced record exists (and is not deleted) when creating or updating a record. Changing a relation field's related_entity target is a breaking schema change and requires a new app version.
Keep entities focused
One entity per concept. Don't combine "tasks" and "comments" into one entity — make two entities and use a relation field in the comments entity to point back to the task.
Set a display_field
Every entity should have a designated display_field — the field shown in lists and referenced in the WorkApps dashboard. Usually this is the main name or title field.
display_field is configured in the WorkApps dashboard when editing an entity: open the Schema Builder, select the entity, and use the Display Field dropdown to choose which field is shown in lists and linked records.
Example Schemas
Task Tracker
| Field | Key | Type | Required | Notes |
|---|---|---|---|---|
| Title | title |
text | Yes | display_field |
| Description | description |
textarea | No | |
| Status | status |
select | Yes | open, in_progress, done |
| Priority | priority |
select | No | low, medium, high, critical |
| Due Date | due_date |
date | No | |
| Assignee Email | assignee_email |
No |
Contact Directory
| Field | Key | Type | Required |
|---|---|---|---|
| Name | name |
text | Yes |
email |
Yes | ||
| Company | company |
text | No |
| Role | role |
text | No |
| Website | website |
url | No |
| Tags | tags |
multi_select | No |
| Notes | notes |
textarea | No |
Inventory
| Field | Key | Type | Required | Notes |
|---|---|---|---|---|
| Name | name |
text | Yes | display_field |
| SKU | sku |
text | Yes | |
| Category | category |
select | Yes | |
| Quantity | quantity |
number | Yes | min: 0 |
| Unit Price | unit_price |
number | No | |
| Low Stock | is_low_stock |
boolean | No | |
| Last Restocked | last_restocked |
date | No |