Field Configuration
Each field in an entity can be configured to customize its display and behavior.
Configuration Overview
| Property | Type | Description |
|---|---|---|
| label | string | Function | Field label |
| hint | string | Function | Help text below field |
| value | Function | Display value transformation |
| formValue | Function | Form input value transformation |
| render | Function | Custom HTML rendering |
| formPartial | Function | Custom form template path |
Basic Example
{
entities: {
User: {
fields: {
email: {
label: 'Email Address',
hint: 'Primary contact email'
},
password: false, // Hide field completely
status: {
label: 'Account Status',
render: ({ value }) => {
const color = value === 'active' ? 'green' : 'red';
return `<span style="color: ${color}">${value}</span>`;
}
}
}
}
}
}label
label?: string | ((fieldUI: FieldUI, action: string) => string)| Type | Default | Description |
|---|---|---|
string | Function | Humanized field name | Label displayed for the field |
Static Label
{
email: {
label: 'Email Address'
}
}Dynamic Label
{
price: {
label: (fieldUI, action) => {
if (action === 'index') return 'Price';
return 'Product Price (EUR)';
}
}
}hint
hint?: string | ((fieldUI: FieldUI) => string)| Type | Default | Description |
|---|---|---|
string | Function | - | Help text displayed below the field |
Example
{
sku: {
label: 'SKU',
hint: 'Stock Keeping Unit - must be unique'
},
phone: {
hint: (fieldUI) => `Format: +${fieldUI.entity.defaultCountryCode} XXX XXX XXX`
}
}value
value?: (params: FieldValueParams) => anyTransform the value for display.
type FieldValueParams = {
item: any;
fieldUI: FieldUI;
entityUI: EntityUI;
action: string;
}Example
{
price: {
value: ({ item }) => `€${item.price.toFixed(2)}`
},
createdAt: {
value: ({ item }) => new Date(item.createdAt).toLocaleDateString('de-DE')
},
fullName: {
value: ({ item }) => `${item.firstName} ${item.lastName}`
}
}formValue
formValue?: (params: FieldValueParams) => anyTransform the value for form inputs.
Example
{
tags: {
formValue: ({ item }) => item.tags?.join(', ') || ''
},
price: {
formValue: ({ item }) => item.price?.toFixed(2)
}
}render
render?: (params: FieldRenderParams) => stringCustom HTML rendering for the field.
type FieldRenderParams = {
item: any;
value: any;
fieldUI: FieldUI;
entityUI: EntityUI;
action: string;
}Example
{
status: {
render: ({ value }) => {
const colors = {
active: 'success',
inactive: 'warning',
deleted: 'danger'
};
return `<span class="badge bg-${colors[value]}">${value}</span>`;
}
},
email: {
render: ({ value }) => `<a href="mailto:${value}">${value}</a>`
},
avatar: {
render: ({ value }) => value
? `<img src="${value}" class="avatar" />`
: '<span class="text-muted">No image</span>'
},
progress: {
render: ({ value }) => `
<div class="progress">
<div class="progress-bar" style="width: ${value}%">${value}%</div>
</div>
`
}
}formPartial
formPartial?: (fieldUI: FieldUI) => stringSpecify a custom Eta template for the form field.
| Type | Default | Description |
|---|---|---|
Function | auto-detected | Path to form template partial |
Example
{
description: {
formPartial: () => 'partials/form/rich-text-editor'
},
location: {
formPartial: () => 'partials/form/map-picker'
}
}Custom templates should be placed in your viewPath directory.
Hiding Fields
Set a field to false to hide it completely:
{
fields: {
password: false,
internalNotes: false
}
}Field Types
The Admin UI automatically detects field types and renders appropriate inputs.
String
Text input or textarea for longer content.
{
name: { label: 'Name' },
description: { label: 'Description' }
}Int / Float
Number input with optional decimal precision.
{
quantity: { label: 'Quantity' },
price: {
label: 'Price',
hint: 'Price in EUR'
}
}Boolean
Checkbox input.
{
isActive: { label: 'Active' },
featured: { label: 'Featured Product' }
}Date / DateTime
Date picker input.
{
birthDate: { label: 'Birth Date' },
publishedAt: { label: 'Publish Date/Time' }
}Enum
Dropdown select with enum values.
{
status: { label: 'Status' },
priority: { label: 'Priority Level' }
}File
File upload with preview.
{
image: { label: 'Product Image' },
document: { label: 'PDF Document' }
}Associations
AssocTo (single reference)
Shows dropdown or search field.
{
category: { label: 'Category' },
author: { label: 'Author' }
}AssocToMany (multiple references)
Array selection interface.
{
tags: { label: 'Tags' },
categories: { label: 'Categories' }
}AssocBy (reverse association)
Read-only table of related items.
{
orders: { label: 'Customer Orders' }
}Custom Fields
You can add custom fields that don't exist on the entity:
{
entities: {
Order: {
fields: {
// Computed field
totalWithTax: {
label: 'Total (incl. Tax)',
value: ({ item }) => (item.total * 1.19).toFixed(2),
render: ({ value }) => `€${value}`
}
}
}
}
}Complete Example
{
entities: {
Product: {
fields: {
// Simple label
name: { label: 'Product Name' },
// With hint
sku: {
label: 'SKU',
hint: 'Stock Keeping Unit - must be unique across all products'
},
// Hidden field
internalCode: false,
// Custom value transformation
price: {
label: 'Price',
value: ({ item }) => `€${item.price.toFixed(2)}`,
formValue: ({ item }) => item.price?.toFixed(2)
},
// Custom rendering
status: {
label: 'Status',
render: ({ value }) => {
const badges = {
draft: 'secondary',
published: 'success',
archived: 'warning'
};
return `<span class="badge bg-${badges[value]}">${value}</span>`;
}
},
// Link rendering
website: {
label: 'Website',
render: ({ value }) => value
? `<a href="${value}" target="_blank">${value}</a>`
: '-'
},
// Image preview
image: {
label: 'Product Image',
render: ({ value }) => value
? `<img src="${value}" style="max-width: 100px; max-height: 100px;" />`
: '<span class="text-muted">No image</span>'
},
// Dynamic label
description: {
label: (fieldUI, action) => {
if (action === 'index') return 'Description';
return 'Product Description (supports Markdown)';
},
formPartial: () => 'partials/form/markdown-editor'
},
// Computed field
profitMargin: {
label: 'Margin',
value: ({ item }) => {
if (!item.cost || !item.price) return '-';
const margin = ((item.price - item.cost) / item.price * 100).toFixed(1);
return `${margin}%`;
},
render: ({ value }) => {
const num = parseFloat(value);
const color = num > 20 ? 'success' : num > 10 ? 'warning' : 'danger';
return `<span class="text-${color}">${value}</span>`;
}
},
// Association with custom label
category: {
label: 'Product Category',
hint: 'Select the primary category'
},
// Timestamps
createdAt: {
label: 'Created',
value: ({ item }) => new Date(item.createdAt).toLocaleString('de-DE')
},
updatedAt: {
label: 'Last Updated',
value: ({ item }) => new Date(item.updatedAt).toLocaleString('de-DE')
}
}
}
}
}