Skip to content
Updated Sep 29, 2026 by Barča Dvořáková · Owner: analysisactiveentity Edit on GitHub

Entity: userPreference ​

Entity Type: Database table (tenant schema)

Description: One personal setting of one user in one tenant, stored as a key and a structured value. It is the store behind the user preferences of the product: a setting that belongs to a person rather than to the organisation, follows that person across sessions and devices, and never changes what anybody else sees. The first setting kept here is the arrangement of an overview table — its visible columns and their order — under the key table.<TableViewKey>.columns; later personal settings (default screen after login, preferred chart units and similar) are further keys in the same table rather than further tables.

Per tenant, not per platform user. The same person can work for several customer organisations, and the columns an overview offers differ between them (modules, permissions, customer-specific attributes). A preference is therefore stored in the tenant schema, next to the data it arranges, so an arrangement made for one customer never leaks into another and is removed with the tenant.

One row per setting, value as JSON. A table arrangement is read and written as a whole, is small, and has no meaning outside its owner, so it is one JSON value rather than a row per column. The alternative — a child table with one row per column — would make every save a delete-and-reinsert of the set, and would add a join to every overview load for no query that needs it.

Also the home of showUiHints. GUI Labelling's field-hint icon toggle is stored the same way as every other personal setting on this table, under the key ui.showUiHints (see JSON-DAT: userPreference.showUiHintsValue). Because this table lives in the tenant schema, showUiHints is per-tenant like everything else here: a person working across two customer organisations can have hints on in one and off in the other. See GUI Labelling for the feature-level description.

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idPrimary key of the entity.UUIDGenerated in code (app layer)YesYesUUID v7-Primary Key018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b21
tenantIdTenant the preference belongs to. Set by the storage layer from the request's tenant context; never passed by feature code.UUID-YesNoUUID v7Must equal the current tenant (row-level security).-018ed0b3-7a10-7c2e-8e61-2b7d1c3f4a55
userIdThe user whose preference this is. References the platform user by identifier only: users live in the platform database, so there is no cross-database foreign key.UUID-YesPart of composite uniqueUUID v7Always the authenticated user of the request; a user can never read or write another user's preference.name: uq_user_preference_user_id_key, type: btree (unique)018ed0b3-1111-7c2e-8e61-2b7d1c3f4a55
keyName of the setting. Dotted, lower-camel segments; a table arrangement is table.<TableViewKey>.columns; the display-hints toggle is ui.showUiHints.String-YesPart of composite unique^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$Max 100 characters. Only keys the backend knows are accepted: for table arrangements, the suffix must be a value of Enum - TableViewKey; other keys (like ui.showUiHints) are matched against a fixed allow-list maintained by the feature that owns them.Part of the unique index abovetable.gauges.columns
valueThe setting itself. Its shape is fixed per key; for a table arrangement see JSON-DAT: userPreference.value, for the display-hints toggle see JSON-DAT: userPreference.showUiHintsValue.JSONB-YesNoJSON objectValidated against the shape registered for the key; unknown shape is rejected. Max 16 KB.-{"version":1,"columns":[{"key":"name","visible":true}]}
createdAtTimestamp of record creation.Timestamp with time zonenow() — set in codeYesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZCannot be null.-2026-09-16T10:00:00Z
updatedAtTimestamp of the last update.Timestamp with time zonenow() — set in codeYesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZCannot be null.-2026-09-16T10:05:00Z
createdByActor who created the record — always the owning user.UUID-YesNoUUID v7Non-empty.-018ed0b3-1111-7c2e-8e61-2b7d1c3f4a55
updatedByActor who last updated the record — always the owning user.UUID-YesNoUUID v7Non-empty.-018ed0b3-1111-7c2e-8e61-2b7d1c3f4a55

No deletedAt. Resetting a setting to its default removes the row: a preference has no history worth keeping, and a soft-deleted row would only have to be skipped by every read.

Indexes ​

NameColumnsTypeWhy
uq_user_preference_user_id_keyuserId, keyUniqueOne value per user per setting, and the only lookup path (a user loading one overview). Also makes a save an upsert rather than a read-then-write.

Row-level security ​

Enabled and forced, with the tenant isolation policy every tenant-schema table uses (tenant_id = current_setting('app.tenant_id')::uuid, same WITH CHECK). Ownership by user is enforced by the application — every read and write is filtered by the authenticated userId — not by a database policy, because the database session knows the tenant but not the user.

Audited fields ​

None. A personal display setting changes nothing another user sees and nothing the organisation relies on, so it is not filed in the audit trail; the row's own updatedAt / updatedBy is enough to answer when it last changed.

Not registered for entityName resolution — not an audited entity; identified by its userId/key scope.

Lifecycle ​

  • A row is created the first time a user confirms a change to a setting; a user who never changes anything has no rows and sees the defaults.
  • Reset to default deletes the row.
  • When a user loses access to the tenant, their rows are deleted with the membership removal; they carry no value to anybody else.