Entity Configuration
Each entity can be configured individually to customize how it appears and behaves in the Admin UI.
Configuration Overview
| Property | Type | Description |
|---|---|---|
| collectionTitle | Function | Collection name (plural) |
| itemTitle | Function | Item display text |
| fields | object | Field configurations |
| index | object | Index page configuration |
| new | object | New item page configuration |
| edit | object | Edit page configuration |
| show | object | Show page configuration |
| assocFrom | object | Association table configuration |
Basic Example
{
entities: {
User: {
collectionTitle: () => 'Users',
itemTitle: (entityUI, item) => `${item.firstName} ${item.lastName}`,
fields: {
email: { label: 'Email Address' },
password: false, // Hide this field
createdAt: { label: 'Created' }
},
index: {
fields: ['email', 'firstName', 'lastName', 'createdAt']
},
show: {
fields: ['email', 'firstName', 'lastName', 'createdAt'],
content: [
{
title: 'Recent Activity',
content: async ({ item }) => `<p>Last login: ${item.lastLogin}</p>`
}
]
},
edit: {
fields: ['email', 'firstName', 'lastName']
}
}
}
}collectionTitle
collectionTitle?: (entityUI: EntityUI) => string| Type | Default | Description |
|---|---|---|
Function | Humanized entity name (plural) | Title for the collection/list view |
Example
{
User: {
collectionTitle: () => 'System Users'
}
}itemTitle
itemTitle?: (entityUI: EntityUI, item: any) => string| Type | Default | Description |
|---|---|---|
Function | Entity name + ID | Display name for individual items |
Used in page titles, breadcrumbs, and links.
Example
{
User: {
itemTitle: (entityUI, item) => `${item.firstName} ${item.lastName} (${item.email})`
},
Product: {
itemTitle: (entityUI, item) => item.name
}
}fields
fields?: { [name: string]: FieldUIConfiguration | false }| Type | Default | Description |
|---|---|---|
object | auto-generated | Per-field configuration |
Set a field to false to hide it completely.
See Field configuration for detailed field options.
Example
{
User: {
fields: {
email: { label: 'Email Address' },
password: false, // Hide password field
role: {
label: 'User Role',
hint: 'Select the user role'
}
}
}
}index
index?: {
fields?: string[];
actions?: ActionConfig;
}| Type | Default | Description |
|---|---|---|
object | all fields | Index page configuration |
fields
Array of field names to display in the table.
actions
Custom actions shown in the index page.
Example
{
User: {
index: {
fields: ['email', 'firstName', 'lastName', 'role', 'createdAt'],
actions: {
export: {
label: 'Export Users',
action: async ({ entityUI, principal }) => {
// Export logic
return { redirect: '/download/users.csv' };
}
}
}
}
}
}new
new?: {
fields?: string[];
actions?: ActionConfig;
}| Type | Default | Description |
|---|---|---|
object | all editable fields | New item page configuration |
Example
{
User: {
new: {
fields: ['email', 'firstName', 'lastName', 'role']
// password will be auto-generated or sent via email
}
}
}edit
edit?: {
fields?: string[];
actions?: ActionConfig;
}| Type | Default | Description |
|---|---|---|
object | all editable fields | Edit page configuration |
Example
{
User: {
edit: {
fields: ['email', 'firstName', 'lastName', 'role'],
actions: {
resetPassword: {
label: 'Reset Password',
confirm: 'Send password reset email?',
action: async ({ item }) => {
await sendPasswordResetEmail(item.email);
return { message: 'Password reset email sent' };
}
}
}
}
}
}show
show?: {
fields?: string[];
actions?: ActionConfig;
content?: ContentBlockConfig[];
}| Type | Default | Description |
|---|---|---|
object | all fields | Show page configuration |
fields
Array of field names to display.
actions
Custom actions shown on the show page.
content
Additional content blocks displayed below the main fields.
Example
{
User: {
show: {
fields: ['email', 'firstName', 'lastName', 'role', 'createdAt', 'lastLogin'],
actions: {
impersonate: {
label: 'Impersonate User',
confirm: 'Log in as this user?',
action: async ({ item, principal }) => {
const token = generateImpersonationToken(item.id);
return { redirect: `/app?token=${token}` };
}
}
},
content: [
{
title: 'Activity Log',
content: async ({ item, entityUI }) => {
const logs = await getActivityLogs(item.id);
return `<ul>${logs.map(l => `<li>${l.action} - ${l.timestamp}</li>`).join('')}</ul>`;
}
},
{
title: 'Location',
content: async ({ item }) => ({
map: { latitude: item.latitude, longitude: item.longitude }
})
}
]
}
}
}assocFrom
assocFrom?: {
fields?: string[];
actions?: ActionConfig;
}| Type | Default | Description |
|---|---|---|
object | default fields | Configuration for association tables |
When this entity appears as a related entity table (AssocBy), use this configuration.
Example
{
Order: {
assocFrom: {
fields: ['orderNumber', 'total', 'status', 'createdAt']
}
}
}
// In User show page, when showing user's orders,
// only these fields will be displayedContent Blocks
Content blocks allow you to add custom content to the show page.
type ContentBlockConfig = {
title: string | ((params: ContentBlockParams) => string);
content: (params: ContentBlockParams) => Promise<string | RenderContent>;
}
type ContentBlockParams = {
item: any;
entityUI: EntityUI;
principal: Principal;
}
type RenderContent =
| string
| { map: { latitude: number; longitude: number } };Text Content
{
title: 'Summary',
content: async ({ item }) => {
return `<p>Total orders: ${item.orderCount}</p>`;
}
}Map Content
{
title: 'Location',
content: async ({ item }) => ({
map: {
latitude: item.latitude,
longitude: item.longitude
}
})
}Dynamic Title
{
title: ({ item }) => `Orders (${item.orders.length})`,
content: async ({ item, entityUI }) => {
return entityUI.entityTable({ items: item.orders });
}
}Polymorphic Entities
For entities implementing interfaces:
{
Vehicle: {
// Base interface configuration
index: { fields: ['name', 'type'] }
},
Car: {
// Implementing entity configuration
index: { fields: ['name', 'wheels', 'doors'] }
},
Motorcycle: {
index: { fields: ['name', 'wheels', 'engineCC'] }
}
}The Admin UI will:
- Show type selector in new/edit forms
- Link to implementing entities from base entity
- Allow separate indices for each type
Complete Example
{
entities: {
Product: {
collectionTitle: () => 'Products',
itemTitle: (entityUI, item) => item.name,
fields: {
name: { label: 'Product Name' },
sku: { label: 'SKU' },
price: {
label: 'Price',
hint: 'Price in EUR'
},
description: { label: 'Description' },
image: { label: 'Product Image' },
category: { label: 'Category' },
inStock: { label: 'In Stock' },
createdAt: false // Hide this field
},
index: {
fields: ['image', 'name', 'sku', 'price', 'inStock', 'category'],
actions: {
export: {
label: 'Export to CSV',
action: async () => ({ redirect: '/api/products/export' })
}
}
},
new: {
fields: ['name', 'sku', 'price', 'description', 'image', 'category', 'inStock']
},
edit: {
fields: ['name', 'sku', 'price', 'description', 'image', 'category', 'inStock'],
actions: {
duplicate: {
label: 'Duplicate',
action: async ({ item, entityUI }) => {
const newItem = { ...item, id: undefined, name: `${item.name} (Copy)` };
const saved = await entityUI.entity.save(newItem);
return { redirect: entityUI.editPath(saved.id) };
}
}
}
},
show: {
fields: ['name', 'sku', 'price', 'description', 'image', 'category', 'inStock'],
actions: {
viewOnSite: {
label: 'View on Website',
action: `/products/${item.slug}`,
newWindow: true
}
},
content: [
{
title: 'Sales Statistics',
content: async ({ item }) => {
const stats = await getSalesStats(item.id);
return `
<div class="stats">
<p>Total sold: ${stats.totalSold}</p>
<p>Revenue: €${stats.revenue}</p>
</div>
`;
}
}
]
},
assocFrom: {
fields: ['name', 'price', 'inStock']
}
}
}
}