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.formandEntityConfiguration.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, anddescriptiongetters 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 entitiesOrganization 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 entitiesEntityCategory.sso- Authentication and authorization (users, roles, companies)EntityCategory.elog- Electronic logbook entities
Categories control:
- Navigation drawer sections
- Permission grouping
- Dashboard organization
- Command palette grouping
Menu Categories
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 TrailsWhen 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 migrationSorting Rules:
- Entities sorted by
priority(ascending) - Entities with same priority sorted alphabetically by
name - 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
-
Identifiers
- Use plural form:
'users','equipment' - Lowercase with underscores:
'reference_standards' - Match database table names when possible
- Use plural form:
-
Display Names
- Use proper capitalization:
'Reference Standard' - Keep concise (1-3 words)
- Match user terminology, not technical jargon
- Use proper capitalization:
-
Descriptions
- One sentence overview of entity purpose
- Focus on "what" not "how"
- Avoid technical implementation details
Organization
-
Categories
- Use sparingly - only major functional areas
- Keep category count under 5 for good UX
- Match user mental models, not code structure
-
Menu Categories
- Create only when you have 5+ entities in a category
- Use user-facing terminology
- Avoid single-item categories
-
Priority
- Reserve 1-10 for most important entities
- Use 50-100 for standard entities
- Use 900+ for administrative/rarely used entities
Help Content
-
Structure
- Start with purpose/overview
- Explain key concepts and fields
- Provide step-by-step common tasks
- Include troubleshooting tips
-
Maintenance
- Use remote CMS for easy updates
- Version control help content
- Keep synchronized with features
- Regular review and updates
-
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