Skip to content

Tables & fields

Everything your workspace tracks lives in tables you design yourself: a table is a named collection of records, and its fields define what each record holds — a deal’s amount, a contact’s email, a job’s completion date. Tessule validates every record against your field definitions, so the schema is not just labels on columns: it is the contract your data is held to.

If you only remember one thing: the schema is live. Every change you make is checked against the records already in the table — a change that would invalidate existing data is refused with examples of the offending rows, rather than silently corrupting or discarding anything. You can always loosen a rule; tightening one has to be earned by the data.


Schema management is governed by the standard actions and roles model:

  • See tables and their schematables:read, held by every built-in role including Reader.
  • Create, rename, and delete tablestables:manage, held by Member and Admin by default.
  • Add, edit, and delete fields (including table constraints and stage designation) — fields:manage, held by Member and Admin by default.
  • Change field visibility and grant roles access to private fieldspermissions:manage, Admin only.
  • Apply AI-generated schema plansschema:apply, Admin only.

Note that ordinary members can reshape the schema by default. If that is too permissive for your team, demote them to Reader and grant the rest of the Member abilities back through a custom role that omits tables:manage and fields:manage.


  • Data (sidebar) — the overview page: a relationship diagram of your tables (references drawn as arrows, system entities in purple) and the AI schema assistant chat.
  • Table Administration (sidebar) — the table list, where you create, edit, and delete tables.
  • The schema editor — from Table Administration, open a table’s schema page. This is where fields are added and edited: a grid showing each field’s name, display name, type, Required/Unique chips, the Visibility chip, default value, and reference target. It also hosts the record-layout editor and the stage-designation dialog (see Lifecycles).

Create a table from Table Administration → Create Table (POST /tables). A table has:

Setting Meaning
Display name What people see everywhere in the app. Freely editable later.
Name The internal identifier (lowercase, underscores), auto-derived from the display name. Fixed at creation — formulas, queries and the API address the table by this name, so it never changes.
Description Free-text notes shown with the table.
AI hint Guidance for AI assistants about the table’s purpose and relationships.
Record visibility workspace (default — everyone with record access sees all rows) or private (rows are visible per owner/scope/share). See Authorization.
Comments Whether records carry a discussion thread. Off by default; turning it off later hides existing comments but never deletes them.
Searchable Whether the table participates in global search. On by default; turning it off drops the table’s search index entirely. API-only — the create/edit dialog has no toggle for it yet.

Edit a table from the table list (PUT /tables/{tableId}). You can change the display name, description, AI hint, record visibility, comments, and searchability — but not the internal name.

Deleting a table (DELETE /tables/{tableId}) removes it and all of its records from the workspace. There is no undo from the app — treat it as permanent. One special case: a SQL view that other views are built on cannot be deleted until those dependent views are dropped first (the request fails rather than cascading). SQL views themselves are read-only derived tables and have their own guide: Views.


Companies name text · unique industry picklist website url Deals title text · required amount currency stage picklist company reference points at a Companies record, shown by name
A reference field links each deal to one company. The reference is labelled by a unique field on the target table — here, the company's name.

Category Type Holds Notes
Text text Short text Up to 255 characters by default
textarea Long text Up to 32,000 characters by default
richtext Formatted text (HTML) Up to 128 KB by default
Number integer Whole numbers
number Decimals
currency An amount plus a currency code Stored as a pair, e.g. 100 USD
percent A percentage 0–100 by default
Choice boolean Yes/no Labels configurable
picklist One value from a fixed option list The lifecycle workhorse — see Lifecycles
multipicklist Several values from a fixed option list
Date & time date A calendar date Default can be Today (the day each record is created)
datetime A timestamp Default can be Now (the moment each record is created)
time A time of day
Contact email An email address Format-validated
phone A phone number
url A web address Format-validated
Relationship reference A link to one record in another table (or a user/group) See “Relationships” below
Compound address Street/city/state/postal code/country
geolocation Latitude + longitude
openingHours Per-day open/close times
Files attachment Uploaded files
Computed formula A value calculated from the record’s other fields Read-only; see Formulas, rollups & summaries
lookup A value projected from the referenced record Read-only, always current
rollup An aggregate over records that reference this one Read-only, always current
autonumber A system-assigned, gapless document number Read-only; format like INV-{0000}

Computed types are read-only on the write path — the server assigns or calculates their values, and any attempt to write them is rejected. The formula language, lookups, rollups, and the sheet’s computed columns are covered in depth in Formulas, rollups & summaries.


Each field carries a configuration object whose available keys depend on the field type (the schema editor shows only what applies). All types accept a description (help text). The rest:

Field type Option What it does
text minLength / maxLength Length bounds
pattern A regular expression the value must match, e.g. ^[A-Z]{2}-\d{4}$
textarea minLength / maxLength Length bounds
richtext maxLength Size cap
integer, number, currency, percent minimum / maximum Inclusive value bounds (for currency, applied to the amount)
number multipleOf Step size, e.g. 0.01 for two decimal places
percent precision Decimal places
currency currencyCode ISO 4217 code (USD, EUR, GBP)
boolean trueLabel / falseLabel Display labels, e.g. “Active” / “Inactive”
date minDate / maxDate Earliest/latest allowed date
datetime minDatetime / maxDatetime Earliest/latest allowed timestamp
time minTime / maxTime Earliest/latest time of day (HH:MM)
phone phoneFormat international, national, or any
defaultCountry ISO country code for parsing/display
url allowedProtocols e.g. ["https"]
picklist, multipicklist options The selectable options, in display order — each with a value, label, colour, default flag, active flag, and lifecycle category (see Lifecycles)
multipicklist minSelections / maxSelections How many options must/may be chosen
reference displayField Which field of the target record to show as the link’s label
address requiredFields Which address parts must be filled (street, city, state, postalCode, country)
defaultCountry Pre-selected country
attachment allowedMimeTypes e.g. ["image/*", "application/pdf"]
maxFileSizeMB Per-file size cap (default 10)
maxFiles How many files the field holds (default 1)
formula formula The expression and its declared result type
lookup lookup The reference field to follow (via) and the target field to project
rollup rollup Source table, reference field, aggregate (count/sum/min/max/avg), optional value field and filter
autonumber autonumber Display format (e.g. INV-{0000}, with {YYYY}/{YY}/{MM} from the creation date) and the first number issued. The start is fixed at creation; the format can change any time — already-issued numbers are never renumbered.

Every rule in this table is enforced at every write surface — the app, the REST API, CSV imports, and AI/MCP writes — and a field’s default value must satisfy its own rules. Records written before a rule was added (or before the date/time bounds, allowedProtocols and phoneFormat rules began enforcing in August 2026) keep their stored values; the rules apply when a record is next written.

  • Required — the field must have a value on every record.
  • Unique — no two records may share a value. Not available on compound types (address, geolocation, opening hours, attachment) or computed types.
  • Default value — pre-filled on new records, and applied to any record created without the field (API, import, flows). Date and datetime fields can default dynamically to Today / Now instead of a fixed value: the app prefills your local date, and a write that omits the field gets the creation date on the server (UTC). A reference to Users can default to Current user: the form is prefilled with you, and a record created without the field — through the API, an import, a flow or an AI agent — is stamped with whoever created it. Use it for “rated by”, “assigned to” and “owner” fields so people stop picking themselves from a list.
  • Searchable — whether the field’s text feeds global search (on by default; only meaningful for text-like types; API-only for now — see Search).
  • Description and AI hint — for humans and AI assistants respectively.
  • Display order — position in grids and forms.

Changing a field: what happens to existing data

Section titled “Changing a field: what happens to existing data”

Fields are edited with PUT /tables/{tableId}/fields/{fieldId}. The rules engine classifies every change:

  1. Loosening is always allowed. Dropping a pattern, raising a maximum, removing required/unique, adding picklist options — nothing already stored can be invalidated, so nothing is checked.
  2. Tightening is verified first. Adding required or unique, narrowing a range, removing a picklist option: the server scans the existing rows before changing anything, and if any row would violate the new rule the change is refused with up to five example row IDs so you can fix the data and retry. Record writes to the table are briefly paused while the check runs, so a racing edit cannot slip past it; on a very large table the check can time out — nothing is changed, and the error says to retry in a quiet period.
  3. Type changes never rewrite stored values. Types that share a storage shape can be swapped in place: the text-like group (text, textarea, richtext, email, phone, url, picklist) interconvert, as does the decimal group (number, currency, percent). Existing values are re-validated against the new type’s rules like any tightening. Any other pair — e.g. text → integer, or anything involving boolean, dates, references, or compound types — is refused unless the table is empty, in which case the column is simply swapped. A field can never change between a computed type and a regular one: delete and recreate instead.
  4. Dependants block risky changes. A type change is refused while lookups or rollups elsewhere read the field; an incompatible change is refused while sibling formulas or table constraints use it; unique cannot be removed from a field other tables use as their reference label; and a picklist option that a stage machine names cannot be removed or renamed while the machine points at it. Each refusal names exactly what depends on the field.

The internal field name, like the table name, is fixed at creation — only the display name changes.

DELETE /tables/{tableId}/fields/{fieldId} removes the field from the schema: it disappears from every record, form, and export, and any role grants on it are removed with it. Deletion is refused while computed fields elsewhere read the field, or while a stage rule requires it to enter a stage — resolve those first. As with tables, there is no undo from the app.


A reference field links each record to at most one record in another table — the “many” side of a one-to-many relationship (each deal points at one company; a company can have many deals).

Creating one, you choose:

  • The target — another table in the workspace, or the built-in Users or Groups system entities (e.g. an “Account manager” field pointing at a workspace user).
  • The label field (table targets) — the field on the target table shown as the link’s text in grids and pickers. It must be unique on the target table, so every link is unambiguous. This is also why removing unique from a field is blocked while references use it as their label.

Referential integrity is enforced by the database: you cannot point a reference at a record that does not exist, and deleting a record that other records still reference is blocked until those references are cleared. See Data consistency for the full story, including composite constraints and cross-table automation rules.

Once a relationship exists, it powers more than navigation:

  • The record detail page lists the referencing records as child records.
  • Lookup fields project a value across the reference (a deal shows its company’s industry).
  • Rollup fields aggregate the other way (a company shows the sum of its open deals). Both are covered in Formulas, rollups & summaries.

Beyond per-field required/unique, a table can carry table-level constraints. Open a table’s schema page and use the Constraints section (requires fields:manage), or manage them through the API (GET/POST /tables/{tableId}/constraints, PUT/DELETE /tables/{tableId}/constraints/{constraintId}) and the AI schema assistant. Three kinds exist:

  • Composite unique — a combination of 2–5 fields must be jointly unique (e.g. one price row per product and region). Add an optional condition to make it a conditional unique: the rule then applies only to the rows the condition selects, so a single field is allowed. “Each rater has at most one favourite” is the Rater field with the condition [Is favourite] = true; “one primary contact per company” is Company with [Is primary] = true; “one active subscription per customer” is Customer with [Status] = 'active'. Rows where the condition is false or empty are exempt. The condition uses the same Excel-familiar syntax as formula fields and is compiled server-side, never raw SQL.
  • No overlap — rows sharing the same scope values may not have overlapping date or datetime ranges (e.g. one booking per room per night). You name the start and end fields (both dates, or both datetimes) and choose whether the end is inclusive.
  • Check — every row must satisfy a condition over its own fields (e.g. [Check out] > [Check in], [Min guests] <= [Max guests]). A row where any operand is empty passes.

All three are enforced by the database itself, so no write path can bypass them. Adding one is a tightening change: it fails with the offending row IDs if existing data already violates it, and editing one replaces the rule in place under the same id — if existing rows would break the new rule, the old rule stays in force.


Field visibility: hiding a column from some roles

Section titled “Field visibility: hiding a column from some roles”

Each field has a visibility setting, shown as a chip in the schema editor’s fields grid:

  • Workspace (default) — anyone who can see the record sees the field.
  • Private — the field is hidden (and rejected on write) except for admins and roles that hold an explicit grant.

Toggling the chip calls PUT /tables/{tableId}/fields/{fieldId}/visibility and requires permissions:manage — if the chip is not clickable, that is why. Grants are per role, per field: PUT /roles/{roleId}/fields/{fieldId} sets canRead and/or canUpdate for a role on a private field (also permissions:manage; in the app: Workspace Settings → Roles → field grants). A private field is masked server-side everywhere — lists, details, exports, search — and admins always see all fields. The full model, including record-level visibility, is in Authorization.


You do not have to build a schema field by field. The Data page hosts an AI schema assistant that designs tables, fields, options, and constraints conversationally and proposes them as a validated plan; applying a plan requires the admin-only schema:apply action, and plans are strictly additive — they can create tables and fields but never delete, rename, or retype anything. See AI assistants.


Q: Why can’t I change my text field to a number field?

Because the table has data and the two types store values differently — converting would mean rewriting (and possibly mangling) stored values, which the system never does. Your options: pick a compatible type from the same group, empty the table first, or create a new field and migrate values manually (an import with update mode works well).

Q: I made a field required and the request failed with a list of row IDs.

That is the pre-check working: those rows have no value in the field. Fill them in (or decide the field should not be required) and retry. Nothing was changed by the failed attempt.

Q: Who can see the schema itself?

Everyone in the workspace — tables:read is in every built-in role. Field values can be hidden per role; field definitions cannot.

Q: Can I rename a table or field’s internal name?

No. Display names change freely; internal names are permanent identifiers used by formulas, queries, imports, and the API. Choose short, stable, snake_case names at creation time.

Q: Why is the Visibility chip not clickable for me?

Toggling visibility requires permissions:manage, which only admins hold by default — editing fields (fields:manage) is not enough, because hiding a column is a permissions decision, not a schema one.

Q: Is there a limit on tables or rows?

Yes — per-organization limits on the number of tables and rows per table apply, depending on your plan. See Organization admin and Billing.

Open the app