Skip to main content

Builtin Entities

Every generated app needs more than the business entities you define in types.json — authentication, settings storage, background job tracking, logging, and request idempotency are infrastructure every app needs regardless of domain. Radish provides these as builtin entities: schema definitions shipped with the CLI and merged into your app's data layer at generation time.

Builtin entities are separate from blueprint-defined business entities in a few important ways:

  • They come from schema templates in src/templates/*.schema.json, not from your types.json.
  • They're excluded from generic CRUD generation — the admin panel gives them dedicated pages (Users, Roles, Settings, Jobs) instead of the standard list/create/detail/edit scaffold.
  • They're tracked in a single canonical list, src/lib/system-entities.mjs, which every consumer (admin panel generator, entity overview counts, business entity listings) imports from.
// src/lib/system-entities.mjs
export const SYSTEM_ENTITIES = new Set([
'User', 'Role', 'Permission', 'Setting', 'Profile', 'ApiKey', 'PasswordResetToken',
'LogMessage', 'ScheduledJob', 'JobExecution', 'JobSnapshot', 'IdempotencyKey',
'SearchSyncTask', 'SearchQuery', 'SearchClick', 'SearchAssociation', 'SearchSynonym',
'ContentBlock', 'MediaAsset', 'Attribute', 'FormTemplate', 'Rule',
'Grant',
'DatasetDefinition', 'DatasetField', 'DatasetRow'
]);

Builtin entities fall into three activation tiers:

  1. Always-on — merged into every generated app, no configuration needed.
  2. Auto-detected — merged automatically when your blueprint declares a triggering feature (e.g. search.enabled: true on an entity).
  3. Feature-gated — merged only when you pass an explicit --features flag to create datalayer.

Always-On Entities​

These entities are always included when you run create datalayer or create app — no flags needed. They're loaded from six schema templates.

Auth​

Schema template: auth.schema.json

EntityPurpose
UserCore authentication account — email, passwordHash, roles, isActive
ProfileUser profile — personal info, address, timezone, locale
RoleRole definition with permission keys. System roles (USER, ADMIN, API, ANONYMOUS) seeded from roles.json
ApiKeyPersonal API keys — stores token hash only, supports scopes and expiration

Schema template: password-reset.schema.json

EntityPurpose
PasswordResetTokenSingle-use tokens for self-service password reset. Plaintext never stored — only a SHA-256 hash. TTL auto-cleans expired records.

Key points: User has a unique email index, passwordHash is internal (never exposed via API), roles is a string array, ApiKey supports scopes/expiration/revocation. PasswordResetToken has a unique index on tokenHash and a 1-day TTL on expiresAt.

Link to: Auth

Settings​

Schema template: settings.schema.json

EntityPurpose
SettingKey-value config with scope cascade (user → tenant → system). Supports field-level encryption for secrets.

Key points: unique sparse compound index on (key, scope, tenantId, userId), plus secondary indexes on (category, scope) and (tenantId, scope). Category groups related settings. dataType drives type coercion on read. fieldSchema declares per-field types for object-valued settings — fields marked 'encryptedKey' are encrypted at rest.

Link to: Settings

Jobs​

Schema template: jobs.schema.json

EntityPurpose
ScheduledJobJob definitions — name, handler, cron/interval schedules, timezone, timeout, enable/disable
JobExecutionIndividual execution records — status, duration, output, error, triggered by scheduler or manual
JobSnapshotPoint-in-time metric snapshots recorded by jobs — dimensions, metrics, bucket grain

Key points: ScheduledJob tracks lastRunAt/nextRunAt/lastStatus/failureCount. JobExecution links to ScheduledJob and optionally to User (for manual triggers). JobSnapshot supports aggregation via dimensionHash deduplication.

Link to: Scheduled Jobs, Job Snapshots

Logging​

Schema template: logging.schema.json

EntityPurpose
LogMessageStructured log entries — level (debug/info/warn/error/fatal), message, source, metadata, stackTrace

Key points: stackTrace is internal (not exposed via API). Indexed on timestamp, (level, timestamp), (source, timestamp), and userId.

Idempotency​

Schema template: idempotency.schema.json

EntityPurpose
IdempotencyKeyPrevents duplicate API request processing. Stores client-provided key, request hash (SHA-256), cached response.

Key points: unique index on key. Status progression: processing → completed | error. expiresAt drives TTL cleanup. requestHash ensures the same key with a different body is rejected rather than silently replayed.

Auto-Detected Entities​

These load automatically when entity blueprints declare certain features. No --features flag needed — the generator inspects the merged entity list from types.json and adds the matching builtin templates.

Loaded when: any entity in the blueprint has search.enabled: true

Schema templates: search-feedback.schema.json, search-synonyms.schema.json

EntityPurpose
SearchQueryLogged search queries for analytics and relevance feedback (TTL: 90 days)
SearchClickClick-through events linking queries to results
SearchAssociationLearned associations between queries and entities for relevance tuning
SearchSynonymSynonym definitions synced to the search engine — multi-way and one-way

Additional (background sync): when search.sync: 'background' on any searchable entity:

Schema template: search-sync.schema.json

EntityPurpose
SearchSyncTaskQueue entries for background search index synchronization — entity, operation, retry tracking

Link to: Search

Feature-Gated Entities​

These require explicit opt-in via --features cms, --features grants, or --features datasets on create datalayer (or create app).

CMS (features.cms)​

Schema template: cms.schema.json

EntityPurpose
ContentBlockEditable HTML/Markdown/text content regions identified by slot key
MediaAssetUploaded images, videos, documents with MIME type and category
AttributeReusable custom field definitions (text, select, boolean, number, etc.) — apply to any entity type
FormTemplateReusable form layouts — ordered lists of attributes for entity forms
RuleGeneric rule engine — condition trees + actions for pricing, workflows, access, notifications

Grants (features.grants)​

Schema template: grants.schema.json

EntityPurpose
GrantPer-record ACL — grants specific permissions to users, consumers, agents, or roles on individual records

Key points: unique compound index on (entity, entityId, grantedTo), plus secondary indexes on (grantedTo, entity, isActive), (entity, entityId, isActive), grantedBy, and a sparse index on expiresAt. Supports direct and inherited scope, plus expiration and revocation. Used by the four-layer access control system (RBAC → Ownership → Scoped → Grants).

Datasets (features.datasets)​

Schema template: datasets.schema.json

EntityPurpose
DatasetDefinitionSemantic layer definition — source, sync config, grain columns, indexed dimensions
DatasetFieldPer-field metadata — role (dimension/measure/excluded), aggregation function, provenance
DatasetRowGeneric row store — shared collection discriminated by datasetId, wildcard-indexed dimensions

Link to: Datasets

Entity Counts by Category​

CategoryEntitiesActivation
Auth5Always
Settings1Always
Jobs3Always
Logging1Always
Idempotency1Always
Search4–5Auto-detected
CMS5--features cms
Grants1--features grants
Datasets3--features datasets
Total24–25

Extending Builtin Entities​

The most commonly extended builtin entity is User. Add custom fields from your blueprint's types.json using extends:

{
"User": {
"extends": "User",
"fields": {
"companyName": { "type": "string", "label": "Company" }
}
}
}

Extension merges fields rather than replacing the entity: inherited fields (email, passwordHash, roles, isActive, etc.) come from the builtin definition, and any fields you declare alongside extends are added on top. The merge happens in collectParentFields() (see contracts.mjs), and is applied consistently across contracts, models, services, routes, and metadata generation.

Extension keys are namespaced internally (builtin:User vs blueprint:User) to detect circular extension chains — you'll see a Circular extension detected: User error if a blueprint entity ends up extending itself indirectly.