Admin UI
Field configuration

Field Configuration

Each field in an entity can be configured to customize its display and behavior.

Configuration Overview

PropertyTypeDescription
labelstring | FunctionField label
hintstring | FunctionHelp text below field
valueFunctionDisplay value transformation
formValueFunctionForm input value transformation
renderFunctionCustom HTML rendering
formPartialFunctionCustom 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)
TypeDefaultDescription
string | FunctionHumanized field nameLabel 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)
TypeDefaultDescription
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) => any

Transform 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) => any

Transform the value for form inputs.

Example

{
  tags: {
    formValue: ({ item }) => item.tags?.join(', ') || ''
  },
  price: {
    formValue: ({ item }) => item.price?.toFixed(2)
  }
}

render

render?: (params: FieldRenderParams) => string

Custom 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) => string

Specify a custom Eta template for the form field.

TypeDefaultDescription
Functionauto-detectedPath 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')
        }
      }
    }
  }
}