Entities

The heart: tables → schema → CRUD API

If the rest of the config wires up infrastructure, entities is where you describe your actual domain. It is a required array of NucleusTable definitions, and it's the single most leveraged field in the framework — everything downstream is generated from it.

Declare a table with its columns, constraints and access rules, and Nucleus produces the Drizzle schema, pushes it to Postgres, mounts a complete REST surface, seeds the authorization claims that protect it, and emits a fully-typed client endpoint. Add a column to the array and, after regenerating, it exists end to end.

This page covers the table-level options first, then drills into columns, validation, and indexes/constraints.

The entity → API pipeline#

entities is the only required array in the whole config, and it's the most powerful. Each NucleusTable you declare is run through a generator that produces a Drizzle schema; at boot that schema is pushed to Postgres, REST routes are mounted, claims are seeded, and a fully-typed client is generated. One declaration, the entire stack.

A minimal entity
1{2  "entities": [3    {4      "table_name": "products",5      "add_base_columns": true,6      "bulk_endpoints_enabled": true,7      "columns": [8        { "name": "title", "type": "varchar", "length": 200, "notNull": true },9        { "name": "price", "type": "numeric", "precision": 10, "scale": 2 },10        { "name": "in_stock", "type": "boolean", "default": true }11      ]12    }13  ]14}

Table definition#

The top-level fields of a NucleusTable describe the table as a whole — its name, whether it gets the standard system columns, and which optional route behaviours it exposes.

table_namestringRequired

The snake_case table name (e.g. blog_posts). This is the source of truth — backend route paths are derived as camelCase (/blogPosts) and the FE endpoint key as UPPER_SNAKE (GET_BLOG_POSTS) automatically.

add_base_columnsbooleanOptional

When true, Nucleus prepends the standard system columns so you don't repeat them: a uuid id primary key, created_at / updated_at timestamps, an is_active soft-delete flag and a version counter for optimistic concurrency.

group_namestringOptional

A logical grouping label used to organise entities in admin UIs and generated docs. Purely organisational.

bulk_endpoints_enabledbooleanOptional

Expose batch create/update/delete routes for this entity. Bulk-operation claims are only seeded when this is true, keeping the claims table clean for entities that don't need them.

is_form_databooleanOptional

Mark the entity as accepting multipart/form-data (file uploads) rather than JSON — relevant for entities that carry binary payloads.

feature_setSystemTableFeatureSets[]Optional

Tags an entity as belonging to a framework feature (authentication, authorization, audit, payment, storage…). Used internally for the built-in system tables; you rarely set this on your own entities.

serviceIdstringOptional

Names the backend service that physically owns this entity. The FE endpoint generator uses it to route calls to the right service in a multi-service deployment. Omit for the default/IDP service.

Example: "blog-api"

requiresNucleusTableRequirement[]Optional

Runtime dependencies that must be configured for this entity's routes to mount (currently 'email'). Lets system entities stay dormant until their provider exists.

Access & route scoping#

These fields decide where an entity lives and which parts of its API are exposed or public. They're how you keep one entity out of certain tenants, or open a read route to the world while protecting writes.

is_publicPartial<Record<Method, boolean>>Optional

Per-method public access. { "GET": true } makes reads reachable without auth while writes stay protected — exactly what public-facing list/detail pages need.

Example: { "GET": true }

excluded_methodsGenericNucleusMethods[]Optional

Methods to NOT generate for this entity. Use ["DELETE"] for append-only tables, or omit POST for read-only reference data.

available_schemasstring[]Optional

Restrict the entity to specific Postgres schemas. In multi-tenant setups this controls which tenants receive the table.

excluded_schemasstring[]Optional

The inverse — schemas that should NOT receive this entity.

available_app_idsstring[]Optional

Limit the entity to specific appIds when several services share one schema source, so each service only mounts the tables it owns.

Under the hood — generation & validation#

The 'Nucleus writes the rest' promise is concrete code. Knowing the steps explains why your routes are validated, documented and safe without any extra work.

TypeBox schemasbody + response + SwaggerOptional

Each entity's columns are compiled into Elysia TypeBox schemas — a request-body schema (notNull → required, types mapped to t.Number/t.Boolean/t.String{format} / t.Unknown for json) and matching response, list, bulk, delete and error schemas. These power both runtime request validation and the auto-generated Swagger/OpenAPI page, so the documented contract and the enforced contract are the same object.

validatePayload / sanitizePayloadrequest guardOptional

Before a write hits the database the payload is type-checked per column and format-checked against built-in patterns (email, url, uuid, date, datetime, time, uri, ipv4, ipv6), then sanitised to drop unknown/forbidden keys — so malformed or over-posted bodies are rejected, not silently stored.

system tablessystem.tables.jsonOptional

Your entities don't start from an empty database. Nucleus ships a full set of system tables (users, sessions, roles, claims, role_claims, user_roles, tenants, tenant_features, audit_logs, notifications, verification flows, …) that the auth, RBAC, audit, verification and tenant subsystems own. Your tables are generated alongside them, and feature_set marks an entity as belonging to one of these framework features.

table-key resolutionsnakeToCamel / resolveSchemaTableOptional

Internally tables are looked up in the schemaTables map by a camelCase key derived from the snake_case table_name (blog_posts → blogPosts). resolveSchemaTable tries the given key, then the normalised form, and warns if neither resolves — preventing the silent undefined that used to bypass role checks.

Columns#

Each entry in columns is a NucleusColumn — a typed, constrained field. Nucleus supports the full PostgreSQL type spectrum: numerics (integer, bigint, numeric, decimal), text (text, varchar, char), uuid, boolean, temporal (date, time, timestamp, timestamptz), json/jsonb, arrays, ranges, network types, and advanced types like vector, geometry and geography.

columns[]NucleusColumn[]Optional

The field definitions for the table.

name / typestring / NucleusColumnTypeOptional

Column name (snake_case) and one of 50+ Postgres column types (integer, varchar, uuid, jsonb, timestamptz, vector, geometry, …).

length / precision / scale / dimensionsnumberOptional

Type sizing: length for varchar/char, precision + scale for numeric/decimal, dimensions for vector embeddings.

notNull / nullablebooleanOptional

Nullability. notNull adds a NOT NULL constraint.

unique / primaryKeybooleanOptional

Mark the column UNIQUE or as the table's primary key.

default / defaultRawunknown / stringOptional

default sets a literal default value; defaultRaw injects raw SQL (e.g. now(), gen_random_uuid()) for expression defaults.

referencesNucleusColumnReferenceOptional

Declare a foreign key inline: { table, column?, onDelete?, onUpdate? } where the actions are cascade / restrict / no action / set null / set default.

array / arrayDimensionsboolean / numberOptional

Make the column an array of its base type, optionally multi-dimensional.

enumValues / enumstring[] / NucleusColumnEnumOptional

Constrain to a fixed value set. enum creates a named Postgres enum type; enumValues is the inline shorthand.

generatedAlwaysAs / …Identitystring / booleanOptional

Computed and identity columns: a stored generated expression, or GENERATED ALWAYS / BY DEFAULT AS IDENTITY auto-increment.

mode / withTimezoneNucleusColumnMode / booleanOptional

mode controls how Drizzle marshals the value in JS (string | date | json | number); withTimezone selects timestamptz semantics.

check / commentstringOptional

check adds a column-level CHECK expression; comment attaches a SQL COMMENT for documentation.

Validation & sanitization#

Beyond database constraints, each column can carry request-time validation and sanitization. These run in the route layer before a write hits the database, so bad input is rejected with a clear error and good input is normalised consistently.

validationNucleusColumnValidationOptional

Declarative input rules enforced on create/update.

minLength / maxLengthnumberOptional

String length bounds.

min / maxnumberOptional

Numeric bounds.

patternstringOptional

Regular expression the value must match.

formatenumOptional

Built-in semantic formats: email, url, uuid, date, datetime, time, uri, ipv4, ipv6.

customMessagestringOptional

Override the error message shown when validation fails.

sanitizeNucleusSanitizeOption[]Optional

Transforms applied to incoming values before persistence: trim, lowercase, uppercase, escapeHtml, stripTags, normalizeEmail, toNumber, toBoolean, slugify. Chain several to clean input deterministically.

Example: ["trim", "lowercase", "normalizeEmail"]

Indexes & constraints#

Tune performance and integrity at the table level. Indexes and multi-column constraints are declared alongside the columns and applied during the schema push.

indexesNucleusIndex[]Optional

Each index has columns plus optional name, unique, using (btree | hash | gist | spgist | gin | brin), a partial where clause, and concurrently for non-blocking creation.

Example: { "columns": ["email"], "unique": true, "using": "btree" }

constraintsNucleusConstraintsOptional

Table-level constraints: a composite primaryKey (string[]), named unique constraints over multiple columns, and check constraints with arbitrary SQL expressions.

Related sections