Configuration

Entity Metadata

Complete guide to entity metadata configuration for display and presentation

Entity metadata provides purely descriptive information about an entity type, controlling how it appears throughout the system. Behavioral configuration (routing, verification requirements) belongs in EntityConfiguration.

Overview

The EntityMetadata class focuses exclusively on presentation and categorization:

class EntityMetadata {
  // Core identification
  final String identifier;

  // Display configuration
  final String name;              // Singular form
  final String pluralName;        // Plural form
  final String? description;
  final IconData? icon;
  final Color? themeColor;

  // Localization support (optional)
  final String Function()? nameResolver;
  final String Function()? pluralNameResolver;
  final String Function()? descriptionResolver;

  // Organization
  final EntityCategory category;
  final MenuCategory? menuCategory;
  final int priority;
  final bool visible;
  final bool isSingleton;

  // Help system
  final Future<EntityHelpModel?> Function()? getHelp;
}

What metadata describes:

  • ✓ Names and descriptions (localized)
  • ✓ Visual identity (icons, colors)
  • ✓ Categorization and organization
  • ✓ Display priority and visibility
  • ✓ Help content

What metadata does NOT include:

  • ✗ Routing configuration → See EntityConfiguration.route
  • ✗ Verification requirements → See EntityConfiguration.verifyCreate, etc.
  • ✗ API configuration → See EntityConfiguration.api
  • ✗ Forms and layouts → See EntityConfiguration.form and EntityConfiguration.layouts

Core Identification

Identifier

The unique key for your entity type used in permissions, routing, and system-wide references:

EntityMetadata(
  identifier: 'reference_standards',  // URL-safe, lowercase, plural
  name: 'Reference Standard',
  pluralName: 'Reference Standards',
)

Best Practices:

  • Use lowercase letters and underscores only
  • Use plural form (e.g., 'users', not 'user')
  • Keep consistent with API endpoints
  • Never change after deployment (breaks permissions and references)

Examples:

identifier: 'equipment'              // ✓ Good
identifier: 'reference_standards'    // ✓ Good
identifier: 'user_groups'            // ✓ Good

identifier: 'Equipment'              // ✗ Bad (uppercase)
identifier: 'user'                   // ✗ Bad (singular)
identifier: 'reference-standards'    // ✗ Bad (hyphen instead of underscore)

Display Configuration

Names and Descriptions

Control how your entity appears in the UI:

EntityMetadata(
  identifier: 'reference_standards',
  name: 'Reference Standard',          // Singular form
  pluralName: 'Reference Standards',    // Plural form
  description: 'Chemical and biological reference materials',
)

These values are used throughout the system:

  • Page titles - "Reference Standard Details", "Create Reference Standard"
  • Navigation menus - "Reference Standards"
  • Breadcrumbs - "Home / Reference Standards / RS-001"
  • Search results - "Found 5 Reference Standards"
  • Error messages - "Failed to load Reference Standard"
  • Confirmation dialogs - "Delete Reference Standard?"

Localization Support

For internationalization, provide optional resolver functions that return locale-specific values:

EntityMetadata(
  // Fallback values (English)
  name: 'Area',
  pluralName: 'Areas',
  description: 'Physical work areas and locations',

  // Localization resolvers (called dynamically)
  nameResolver: () => t.strings.elog.entities.area.singular,
  pluralNameResolver: () => t.strings.elog.entities.area.plural,
  descriptionResolver: () => t.strings.elog.entities.area.description,
)

How it works:

  • The name, pluralName, and description getters automatically call resolvers when provided
  • Falls back to static values when resolvers are null
  • Resolvers are called each time the property is accessed, allowing dynamic locale switching
  • No code generation required - pure runtime resolution

Visual Identity

Icons and Colors

Define the visual appearance of your entity:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  icon: Icons.precision_manufacturing,    // Material icon
  themeColor: Colors.blue,                // Primary color
)

Icon Usage:

  • Navigation menu items
  • Page headers and app bars
  • Entity cards in grid views
  • Command palette entries
  • Notification badges

Theme Color Usage:

  • App bar backgrounds on entity pages
  • Action button colors (create, edit)
  • Selection highlights in lists
  • Chart visualizations in analytics
  • Status indicators

Color Selection Tips:

// Use semantic colors
icon: Icons.security,
themeColor: Colors.red,  // Security-related entities

icon: Icons.people,
themeColor: Colors.indigo,  // User management

icon: Icons.inventory,
themeColor: Colors.green,  // Inventory items

icon: Icons.science,
themeColor: Colors.purple,  // Laboratory entities

Organization and Categorization

Entity Categories

Group related entities using EntityCategory:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  category: EntityCategory.elog,
)

Available Categories:

  • EntityCategory.general - General purpose entities
  • EntityCategory.sso - Authentication and authorization (users, roles, companies)
  • EntityCategory.elog - Electronic logbook entities

Categories control:

  • Navigation drawer sections
  • Permission grouping
  • Dashboard organization
  • Command palette grouping

Further organize entities within a category using MenuCategory:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  category: EntityCategory.elog,
  menuCategory: MenuCategory.masterData,  // Optional subcategory
)

Hierarchical Organization:

Electronic Logbook (EntityCategory.elog)
├── Master Data (MenuCategory.masterData)
│   ├── Equipment
│   ├── Areas
│   └── Reference Standards
├── Operations (MenuCategory.operations)
│   ├── Log Templates
│   └── Checklists
└── Reports (MenuCategory.reports)
    └── Audit Trails

When to use Menu Categories:

  • When you have many entities in one category
  • To create logical groupings for users
  • To reduce cognitive load in navigation
  • To match user mental models

When NOT to use:

  • When you have only a few entities in a category
  • When entities don't naturally group
  • When it would create too many single-item groups

Priority and Visibility

Control display order and menu visibility:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  priority: 10,      // Lower numbers appear first (default: 999)
  visible: true,     // Show in navigation menu (default: true)
)

Priority Examples:

// High priority (shown first)
EntityMetadata(
  identifier: 'areas',
  name: 'Area',
  pluralName: 'Areas',
  priority: 1,  // Appears at top
)

// Medium priority
EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  priority: 50,
)

// Low priority (shown last)
EntityMetadata(
  identifier: 'audit_logs',
  name: 'Audit Log',
  pluralName: 'Audit Logs',
  priority: 999,  // Default value
)

Visibility Control:

// Hidden from navigation (accessible via direct routes only)
EntityMetadata(
  identifier: 'system_config',
  name: 'System Configuration',
  pluralName: 'System Configurations',
  visible: false,  // Not shown in menu
)

// Use cases for hidden entities:
// - Internal configuration entities
// - Entities only accessible through other entities
// - System-managed entities
// - Deprecated entities still needed for data migration

Sorting Rules:

  1. Entities sorted by priority (ascending)
  2. Entities with same priority sorted alphabetically by name
  3. Hidden entities (visible: false) excluded from menus entirely

Singleton Entities

Mark entities that have only one instance:

EntityMetadata(
  identifier: 'settings',
  name: 'Settings',
  pluralName: 'Settings',
  isSingleton: true,  // Important: mark as singleton
)

Singleton characteristics:

  • Only one instance exists system-wide
  • No "New" button in the UI
  • Create operation is the same as edit
  • List view shows the single instance
  • Examples: Settings, Company Profile, System Configuration

Help System Integration

Provide contextual help for your entities:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  getHelp: () async {
    // Load from remote CMS
    return await HelpService.loadHelp('equipment');

    // Or return inline help
    return EntityHelpModel(
      content: '''
## Equipment Management

Equipment entities represent manufacturing equipment and instruments.

### Key Concepts
- **Serial Number**: Unique manufacturer identifier
- **Calibration**: Regular calibration required for GMP compliance
- **Status**: Track equipment availability and maintenance

### Common Tasks
1. Register new equipment with serial number
2. Schedule calibration activities
3. Record maintenance history
4. Generate equipment reports
      ''',
      lastUpdated: DateTime.now(),
    );
  },
)

Help Display Context:

  • Accessible from page headers via help icon
  • Shown in side panel or modal
  • Supports markdown formatting
  • Can include links to external documentation
  • Searchable through command palette

Help Content Best Practices:

  • Start with overview of entity purpose
  • Explain key fields and their meaning
  • Provide common task walkthroughs
  • Include examples and edge cases
  • Keep content concise and scannable

Complete Configuration Examples

Standard Entity

final equipmentMetadata = EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  description: 'Manufacturing equipment and instruments',
  icon: Icons.precision_manufacturing,
  themeColor: Colors.blue,
  category: EntityCategory.elog,
  menuCategory: MenuCategory.masterData,
  priority: 10,
  visible: true,
  isSingleton: false,
  getHelp: () async => await HelpService.loadHelp('equipment'),
);

// Use in EntityConfiguration
final config = EntityConfiguration<Equipment>(
  metadata: equipmentMetadata,
  routing: EntityRoutingDescriptor(
    path: NavigationPathBuilder.collection('equipment', prefix: '/entities'),
    builder: StandardRouteBuilder<Equipment>(),
  ),
  api: EquipmentApi(),
  // ... other configuration
);

Localized Entity

final areasMetadata = EntityMetadata(
  identifier: 'areas',
  name: 'Area',  // Fallback
  pluralName: 'Areas',  // Fallback
  description: 'Physical work areas',  // Fallback
  nameResolver: () => t.elog.entities.areas.singular,
  pluralNameResolver: () => t.elog.entities.areas.plural,
  descriptionResolver: () => t.elog.entities.areas.description,
  icon: Icons.location_on,
  themeColor: Colors.green,
  category: EntityCategory.elog,
  menuCategory: MenuCategory.masterData,
  priority: 5,
);

Singleton Entity

final settingsMetadata = EntityMetadata(
  identifier: 'settings',
  name: 'Settings',
  pluralName: 'Settings',
  description: 'Application settings and configuration',
  icon: Icons.settings,
  themeColor: Colors.grey,
  category: EntityCategory.general,
  priority: 999,  // Show last in menu
  visible: true,
  isSingleton: true,  // Important: mark as singleton
);

// Use with singleton route builder
final config = EntityConfiguration<Settings>(
  metadata: settingsMetadata,
  routing: EntityRoutingDescriptor(
    path: NavigationPathBuilder.singleton('settings', prefix: '/config'),
    builder: StandardRouteBuilder<Settings>(),
  ),
  form: EntityFormDescriptor<Settings>(
    getForm: (entityId) => SettingsForm(),
    transformer: SettingsTransformer(),
    verifyUpdate: ActionBehavior.yes,  // Always verify settings changes
  ),
  // ... other configuration
);

Hidden Configuration Entity

final systemConfigMetadata = EntityMetadata(
  identifier: 'system_config',
  name: 'System Configuration',
  pluralName: 'System Configurations',
  description: 'Internal system configuration (admin only)',
  icon: Icons.admin_panel_settings,
  themeColor: Colors.red,
  category: EntityCategory.general,
  priority: 1000,
  visible: false,  // Hidden from navigation
  isSingleton: true,
);

Best Practices

Naming Conventions

  1. Identifiers

    • Use plural form: 'users', 'equipment'
    • Lowercase with underscores: 'reference_standards'
    • Match database table names when possible
  2. Display Names

    • Use proper capitalization: 'Reference Standard'
    • Keep concise (1-3 words)
    • Match user terminology, not technical jargon
  3. Descriptions

    • One sentence overview of entity purpose
    • Focus on "what" not "how"
    • Avoid technical implementation details

Organization

  1. Categories

    • Use sparingly - only major functional areas
    • Keep category count under 5 for good UX
    • Match user mental models, not code structure
  2. Menu Categories

    • Create only when you have 5+ entities in a category
    • Use user-facing terminology
    • Avoid single-item categories
  3. Priority

    • Reserve 1-10 for most important entities
    • Use 50-100 for standard entities
    • Use 900+ for administrative/rarely used entities

Help Content

  1. Structure

    • Start with purpose/overview
    • Explain key concepts and fields
    • Provide step-by-step common tasks
    • Include troubleshooting tips
  2. Maintenance

    • Use remote CMS for easy updates
    • Version control help content
    • Keep synchronized with features
    • Regular review and updates
  3. Localization

    • Provide help in user's language
    • Use same i18n system as metadata
    • Consider cultural context in examples

Testing Metadata

test('equipment metadata is correctly configured', () {
  final metadata = EquipmentConfig.instance.metadata;

  expect(metadata.identifier, 'equipment');
  expect(metadata.name, 'Equipment');
  expect(metadata.pluralName, 'Equipment');
  expect(metadata.icon, Icons.precision_manufacturing);
  expect(metadata.category, EntityCategory.elog);
  expect(metadata.priority, 10);
  expect(metadata.visible, true);
  expect(metadata.isSingleton, false);
});

test('localization resolvers work correctly', () {
  // Set locale to Spanish
  LocaleSettings.setLocale(AppLocale.es);

  final metadata = EntityMetadata(
    identifier: 'equipment',
    name: 'Equipment',  // Fallback
    pluralName: 'Equipment',  // Fallback
    nameResolver: () => t.entities.equipment.singular,
    pluralNameResolver: () => t.entities.equipment.plural,
  );

  expect(metadata.name, 'Equipo');  // Spanish translation

  // Switch to English
  LocaleSettings.setLocale(AppLocale.en);
  expect(metadata.name, 'Equipment');  // English translation
});

Migration from Old Structure

If you're migrating from the old structure where routing and verification were in metadata:

Before (Old):

final metadata = EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  route: EntityRouteBuilder.collection('equipment'),  // ❌ Removed
  verifyCreate: ActionBehavior.yes,  // ❌ Removed
  verifyUpdate: ActionBehavior.yes,  // ❌ Removed
);

After (New):

// Metadata: purely descriptive
final metadata = EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  category: EntityCategory.general,
  icon: Icons.precision_manufacturing,
);

// Configuration: structural and behavioral settings
final config = EntityConfiguration<Equipment>(
  metadata: metadata,

  // Routing: consolidated in EntityRoutingDescriptor
  routing: EntityRoutingDescriptor(
    path: NavigationPathBuilder.collection('equipment', prefix: '/entities'),
    builder: StandardRouteBuilder<Equipment>(),
    mode: EntityNavigationMode.navigate,
  ),

  api: EquipmentApi(),
  layouts: equipmentLayouts,

  // Verification: moved to EntityFormDescriptor
  form: EntityFormDescriptor<Equipment>(
    getForm: (entityId) => EquipmentForm(entityId: entityId),
    transformer: EquipmentTransformer(),
    verifyCreate: ActionBehavior.yes,  // ✓ Moved here
    verifyUpdate: ActionBehavior.yes,  // ✓ Moved here
  ),

  actions: equipmentActions,
);

Next: Entity Configuration - Complete entity integration with routing and behavioral policies

On this page