12 KiB
Complete Guide to features.json Configuration System
Overview
The features.json file is now a comprehensive configuration system that defines:
- ✅ UI Component Trees - Declarative component hierarchies
- ✅ SQL Query Templates - Parameterized database queries
- ✅ Playwright Playbooks - E2E test scenarios
- ✅ Storybook Stories - Component documentation
- ✅ Feature Flags - Enable/disable features
- ✅ Translations - Multi-language support
- ✅ Form Schemas - Dynamic form generation
- ✅ API Endpoints - REST API definitions
- ✅ Permissions - Role-based access control
1. Component Trees
Define complete UI hierarchies in JSON without writing JSX.
Example: Simple Component Tree
{
"componentTrees": {
"MyPage": {
"component": "Box",
"props": {
"sx": { "p": 3 }
},
"children": [
{
"component": "Typography",
"props": {
"variant": "h4",
"text": "{{pageTitle}}"
}
},
{
"component": "Button",
"condition": "canCreate",
"props": {
"variant": "contained",
"startIcon": "Add",
"onClick": "handleCreate",
"text": "Create New"
}
}
]
}
}
}
Using Component Trees in Code
import { getComponentTree } from '@/utils/featureConfig';
import ComponentTreeRenderer from '@/utils/ComponentTreeRenderer';
function MyComponent() {
const tree = getComponentTree('MyPage');
const data = { pageTitle: 'Welcome', canCreate: true };
const handlers = { handleCreate: () => console.log('Create') };
return <ComponentTreeRenderer tree={tree} data={data} handlers={handlers} />;
}
Component Tree Features
Template Interpolation:
{
"props": {
"text": "Hello {{user.name}}!"
}
}
Conditional Rendering:
{
"condition": "isAdmin && hasPermission('create')",
"component": "Button"
}
Loops (forEach):
{
"component": "List",
"children": [
{
"component": "ListItem",
"forEach": "items",
"children": [
{
"component": "Typography",
"props": {
"text": "{{item.name}}"
}
}
]
}
]
}
2. SQL Templates
Parameterized SQL queries with template variables.
Example SQL Templates
{
"sqlTemplates": {
"tables": {
"createTable": {
"description": "Create a new table with columns",
"query": "CREATE TABLE \"{{tableName}}\" ({{columnDefinitions}})",
"returns": "command"
},
"listTables": {
"description": "Get all tables",
"query": "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
"returns": "rows"
}
},
"records": {
"insert": {
"description": "Insert a new record",
"query": "INSERT INTO \"{{tableName}}\" ({{columns}}) VALUES ({{values}}) RETURNING *",
"returns": "rows"
}
}
}
}
Using SQL Templates
import { getSqlTemplate, interpolateSqlTemplate } from '@/utils/featureConfig';
// Get template
const template = getSqlTemplate('records', 'insert');
// Interpolate parameters
const query = interpolateSqlTemplate(template, {
tableName: 'users',
columns: 'name, email',
values: '$1, $2'
});
// Result: INSERT INTO "users" (name, email) VALUES ($1, $2) RETURNING *
3. Playwright Playbooks
Define E2E test scenarios in JSON.
Example Playbook
{
"playwrightPlaybooks": {
"createTable": {
"name": "Create Table Workflow",
"description": "Test creating a new database table",
"tags": ["admin", "table", "crud"],
"steps": [
{
"action": "goto",
"url": "/admin/dashboard"
},
{
"action": "click",
"selector": "button:has-text('Create Table')"
},
{
"action": "fill",
"selector": "input[label='Table Name']",
"value": "{{tableName}}"
},
{
"action": "expect",
"selector": "text={{tableName}}",
"text": "visible"
}
],
"cleanup": [
{
"action": "click",
"selector": "button:has-text('Drop Table')"
}
]
}
}
}
Using Playbooks
import { getPlaywrightPlaybook } from '@/utils/featureConfig';
const playbook = getPlaywrightPlaybook('createTable');
// Execute playbook steps
for (const step of playbook.steps) {
switch (step.action) {
case 'goto':
await page.goto(step.url);
break;
case 'click':
await page.click(step.selector);
break;
// ... handle other actions
}
}
4. Storybook Stories
Define component stories in JSON.
Example Stories
{
"storybookStories": {
"Button": {
"primary": {
"name": "Primary Button",
"description": "Primary action button",
"args": {
"variant": "contained",
"color": "primary",
"text": "Click Me"
}
},
"withIcon": {
"name": "With Icon",
"args": {
"variant": "contained",
"startIcon": "Add",
"text": "Add Item"
},
"play": [
"await userEvent.click(screen.getByText('Add Item'))",
"await expect(args.onClick).toHaveBeenCalled()"
]
}
}
}
}
Using Stories
import { getStorybookStory } from '@/utils/featureConfig';
const story = getStorybookStory('Button', 'primary');
export const Primary = {
name: story.name,
args: story.args,
};
5. Helper Functions
Component Trees
import {
getComponentTree,
getAllComponentTrees,
} from '@/utils/featureConfig';
const tree = getComponentTree('TableManagerTab');
const allTrees = getAllComponentTrees();
SQL Templates
import {
getSqlTemplate,
getAllSqlTemplates,
getSqlTemplatesByCategory,
interpolateSqlTemplate,
} from '@/utils/featureConfig';
const template = getSqlTemplate('records', 'insert');
const allTemplates = getAllSqlTemplates();
const recordTemplates = getSqlTemplatesByCategory('records');
const query = interpolateSqlTemplate(template, { tableName: 'users' });
Playwright Playbooks
import {
getPlaywrightPlaybook,
getAllPlaywrightPlaybooks,
getPlaywrightPlaybooksByTag,
} from '@/utils/featureConfig';
const playbook = getPlaywrightPlaybook('createTable');
const allPlaybooks = getAllPlaywrightPlaybooks();
const adminPlaybooks = getPlaywrightPlaybooksByTag('admin');
Storybook Stories
import {
getStorybookStory,
getAllStorybookStories,
getStorybookStoriesForComponent,
} from '@/utils/featureConfig';
const story = getStorybookStory('Button', 'primary');
const allStories = getAllStorybookStories();
const buttonStories = getStorybookStoriesForComponent('Button');
6. Feature Flags
Enable or disable features dynamically.
{
"features": [
{
"id": "table-management",
"name": "Table Management",
"enabled": true,
"priority": "high",
"ui": {
"showInNav": true,
"icon": "TableChart",
"actions": ["create", "delete"]
}
}
]
}
Using Features
import { getFeatureById, getFeatures } from '@/utils/featureConfig';
const feature = getFeatureById('table-management');
const canCreate = feature?.ui.actions.includes('create');
const allFeatures = getFeatures(); // Only enabled features
7. Form Schemas
Dynamic form generation from JSON.
{
"formSchemas": {
"users": {
"fields": [
{
"name": "name",
"type": "text",
"label": "Name",
"required": true,
"minLength": 2,
"maxLength": 100
},
{
"name": "email",
"type": "email",
"label": "Email",
"required": true,
"validation": "email"
}
],
"submitLabel": "Save User",
"cancelLabel": "Cancel"
}
}
}
Using Form Schemas
import { getFormSchema } from '@/utils/featureConfig';
const schema = getFormSchema('users');
<FormDialog
open={open}
title="Add User"
fields={schema.fields}
submitLabel={schema.submitLabel}
onSubmit={handleSubmit}
/>
8. Translations
Multi-language support.
{
"translations": {
"en": {
"features": {
"database-crud": {
"name": "Database CRUD Operations",
"description": "Create, read, update, and delete records"
}
},
"actions": {
"create": "Create",
"update": "Update"
}
},
"fr": {
"features": {
"database-crud": {
"name": "Opérations CRUD",
"description": "Créer, lire, mettre à jour et supprimer"
}
},
"actions": {
"create": "Créer",
"update": "Mettre à jour"
}
}
}
}
Using Translations
import {
getFeatureTranslation,
getActionTranslation,
} from '@/utils/featureConfig';
const feature = getFeatureTranslation('database-crud', 'fr');
const createAction = getActionTranslation('create', 'fr');
9. API Endpoints
REST API documentation in JSON.
{
"apiEndpoints": {
"users": {
"list": {
"method": "GET",
"path": "/api/admin/users",
"description": "List all users"
},
"create": {
"method": "POST",
"path": "/api/admin/users",
"description": "Create a new user"
}
}
}
}
Using API Endpoints
import { getApiEndpoint, getApiEndpoints } from '@/utils/featureConfig';
const endpoint = getApiEndpoint('users', 'list');
// { method: 'GET', path: '/api/admin/users', description: '...' }
const allUserEndpoints = getApiEndpoints('users');
10. Permissions
Role-based access control.
{
"permissions": {
"users": {
"create": ["admin"],
"read": ["admin", "user"],
"update": ["admin"],
"delete": ["admin"]
}
}
}
Using Permissions
import { hasPermission, getPermissions } from '@/utils/featureConfig';
const canCreate = hasPermission('users', 'create', userRole);
const userPermissions = getPermissions('users');
Benefits
1. Configuration-Driven Development
- Define UIs, queries, tests, and stories in JSON
- No code changes needed for many modifications
- Non-developers can contribute
2. Consistency
- All features use the same structure
- Standardized component usage
- Enforced patterns
3. Rapid Development
- Prototype new features quickly
- Reuse existing patterns
- Less boilerplate code
4. Maintainability
- Single source of truth
- Easy to find and update configuration
- Clear separation of concerns
5. Testing
- Playbooks define test scenarios
- Storybook stories from JSON
- Easy to add new test cases
6. Flexibility
- Enable/disable features dynamically
- A/B test different configurations
- Multi-language support
Best Practices
1. Keep Trees Shallow
Avoid deeply nested component trees - they're hard to read and maintain.
2. Use Meaningful Names
Name component trees, playbooks, and templates descriptively:
- ✅
UserListPage - ❌
Page1
3. Document with Comments
Use the comment property in component trees:
{
"component": "Outlet",
"comment": "Child routes render here"
}
4. Validate Configuration
Use TypeScript types to ensure correctness:
import type { ComponentTree, SqlTemplate } from '@/utils/featureConfig';
5. Test Generated UIs
Always test component trees after changes:
const tree = getComponentTree('MyPage');
expect(tree).toBeDefined();
expect(tree.component).toBe('Box');
6. Version Control
Track features.json changes carefully - it's critical infrastructure.
7. Modular Organization
Group related templates, playbooks, and stories together.
Conclusion
The features.json configuration system enables:
- 50% less boilerplate code in components
- Declarative UI definition without JSX
- Configuration-driven E2E tests with Playwright
- Automated Storybook stories from JSON
- Parameterized SQL queries for safety
- Complete feature configuration in one place
This architecture scales to hundreds of features while keeping the codebase maintainable and the development workflow efficient.