Entity Definitions
Entities are the core building blocks of your Radish blueprint. Each entity represents a data type in your application.
Basic Entity Structure
{
"entities": {
"EntityName": {
"label": "Display Name",
"description": "Description of the entity",
"plural": "entities",
"fields": {},
"filters": [],
"indexes": []
}
}
}
Entity Properties
label
Human-readable name for the entity (singular form).
{
"label": "Task"
}
description
Brief description of what this entity represents.
{
"description": "A todo task that can be assigned to users"
}
plural
Plural form used for collections and endpoints.
{
"plural": "tasks"
}
ownership
Determines entity ownership model:
user(default) - Entity belongs to a specific user (hasownerId)system- System-level entity, no owner
{
"ownership": "system"
}
extends
Inherit fields from another entity or base type.
{
"extends": "EntityBase"
}
Built-in base types:
EntityBase- Standard entity with id, ownerId, timestampsSystemEntityBase- System entity with id, timestamps (no owner)ContentBase- Extends EntityBase with title, slug, tags, status, etc.
versioning
Enable version tracking:
full- Complete snapshots with revert capabilitysimple- Lightweight audit logs
{
"versioning": "simple"
}
scope
Enable relationship-based access control (Layer 3). Two modes:
Through-entity — access via ownership of a related entity:
{
"scope": {
"field": "appId",
"through": "App",
"ownerField": "ownerId"
}
}
Direct-match (tenancy) — access via a shared field on the user:
{
"scope": {
"field": "orgId",
"matchUserField": "orgIds"
}
}
Supports array match: if user.orgIds is ["org1", "org2"], records where orgId is either value are accessible.
See Access Control for full documentation.
search
Enable search index integration for this entity:
{
"search": {
"enabled": true,
"engine": "typesense",
"indexName": "products",
"fields": {
"searchable": ["name", "description"],
"filterable": ["brand", "price"],
"sortable": ["price", "createdAt"],
"facetable": ["brand"]
},
"sync": "inline"
}
}
| Property | Default | Description |
|---|---|---|
enabled | false | Enable search indexing |
engine | "typesense" | Search engine (typesense, elastic, opensearch, meilisearch, mongoAtlas) |
indexName | entity plural | Search index name |
fields.searchable | [] | Full-text searchable fields |
fields.filterable | [] | Fields for filtering |
fields.sortable | [] | Fields for sorting |
fields.facetable | [] | Fields for faceted search |
sync | "inline" | Sync mode: inline (immediate) or background |
Example Entity
{
"entities": {
"Product": {
"label": "Product",
"description": "Product in the catalog",
"plural": "products",
"ownership": "system",
"versioning": "simple",
"scope": {
"field": "orgId",
"matchUserField": "orgIds"
},
"search": {
"enabled": true,
"engine": "typesense",
"fields": {
"searchable": ["name", "description", "brand"],
"filterable": ["brand", "price", "status"],
"sortable": ["price", "name", "createdAt"]
}
},
"fields": {
"name": {
"type": "string",
"required": true,
"label": "Product Name",
"search": true
},
"description": {
"type": "string",
"optional": true
},
"brand": {
"type": "string",
"label": "Brand"
},
"price": {
"type": "float",
"required": true
},
"costPrice": {
"type": "float",
"access": { "read": ["ADMIN", "MANAGER"] }
},
"status": {
"type": "enum",
"values": ["draft", "published", "archived"],
"default": "draft"
},
"orgId": {
"type": "objectId",
"ref": "Org",
"required": true
},
"images": {
"type": "url[]",
"default": []
}
},
"filters": ["status", "brand", "orgId"],
"indexes": [
{ "fields": ["orgId", "status"] },
{ "fields": ["brand"] }
]
}
}
}
This example shows:
- Tenant scoping:
scope.matchUserFieldfor multi-org access - Search: Typesense integration with searchable/filterable/sortable fields
- Field access:
costPricerestricted to ADMIN and MANAGER roles - url[] type: Array of URLs for images
- Enum with default: Status field with draft/published/archived