Admin UI
Menu configuration

Menu Configuration

The Admin UI menu can be configured to organize entities into groups and add custom navigation items.

Overview

By default, the menu is auto-generated with all entities. You can customize it to:

  • Group entities into categories
  • Add custom navigation links
  • Add custom action menu items
  • Control the order of items

Configuration

menu?: {
  [groupName: string]: (string | MenuItemConfig)[]
}
TypeDefaultDescription
objectauto-generatedMenu structure with groups

Basic Example

{
  menu: {
    'Users': ['User', 'Role', 'Permission'],
    'Content': ['Article', 'Category', 'Tag'],
    'E-Commerce': ['Product', 'Order', 'Customer']
  }
}

Menu Items

Entity Reference

Reference an entity by name:

{
  menu: {
    'Content': ['Article', 'Category']
  }
}

Custom Link

Add a navigation link:

{
  menu: {
    'Content': [
      'Article',
      {
        label: 'Media Library',
        path: '/admin/media'
      }
    ]
  }
}

External Link

Open in new window:

{
  menu: {
    'Help': [
      {
        label: 'Documentation',
        path: 'https://docs.example.com',
        newWindow: true
      }
    ]
  }
}

Custom Action

Execute custom logic:

{
  menu: {
    'Actions': [
      {
        label: 'Clear Cache',
        confirm: 'Clear all cached data?',
        action: async ({ principal, runtime }) => {
          await clearCache();
          return { message: 'Cache cleared successfully' };
        }
      }
    ]
  }
}

Menu Item Configuration

type MenuItemConfig = {
  label: string;
  path?: string;
  newWindow?: boolean;
  confirm?: string;
  action?: (params: ActionParams) => Promise<ActionResult>;
}
 
type ActionParams = {
  principal: Principal;
  runtime: ActiveQLServer;
  activeAdmin: ActiveAdmin;
}
 
type ActionResult = {
  message?: string;
  redirect?: string;
}

Action Results

Show Message

{
  action: async () => {
    await doSomething();
    return { message: 'Operation completed' };
  }
}

Redirect

{
  action: async () => {
    const report = await generateReport();
    return { redirect: `/download/${report.id}` };
  }
}

Complete Example

{
  menu: {
    'Main': [
      'User',
      'Product',
      'Order'
    ],
 
    'Content': [
      'Article',
      'Category',
      'Tag',
      {
        label: 'Media Library',
        path: '/admin/media'
      }
    ],
 
    'Settings': [
      'SystemSetting',
      'EmailTemplate',
      {
        label: 'Edit Configuration',
        path: '/admin/config'
      }
    ],
 
    'Reports': [
      {
        label: 'Sales Report',
        action: async ({ runtime }) => {
          const report = await generateSalesReport(runtime);
          return { redirect: `/download/report/${report.id}` };
        }
      },
      {
        label: 'User Activity',
        action: async ({ runtime }) => {
          const report = await generateActivityReport(runtime);
          return { redirect: `/download/report/${report.id}` };
        }
      }
    ],
 
    'Actions': [
      {
        label: 'Clear Cache',
        confirm: 'This will clear all cached data. Continue?',
        action: async () => {
          await clearAllCache();
          return { message: 'Cache cleared successfully' };
        }
      },
      {
        label: 'Rebuild Search Index',
        confirm: 'Rebuild the search index? This may take a few minutes.',
        action: async ({ runtime }) => {
          await rebuildSearchIndex(runtime);
          return { message: 'Search index rebuilt' };
        }
      },
      {
        label: 'Send Newsletter',
        confirm: 'Send newsletter to all subscribers?',
        action: async ({ runtime }) => {
          const count = await sendNewsletter(runtime);
          return { message: `Newsletter sent to ${count} subscribers` };
        }
      }
    ],
 
    'Help': [
      {
        label: 'Documentation',
        path: 'https://docs.example.com',
        newWindow: true
      },
      {
        label: 'API Reference',
        path: 'https://api.example.com/docs',
        newWindow: true
      },
      {
        label: 'Support',
        path: 'mailto:support@example.com'
      }
    ]
  }
}

Dynamic Menu

You can generate menu items dynamically:

{
  menu: {
    'Content': await getContentEntities(),
    'Dynamic': generateDynamicMenuItems()
  }
}
 
function generateDynamicMenuItems() {
  const items = ['Article', 'Page'];
 
  if (process.env.FEATURE_BLOG === 'true') {
    items.push('BlogPost');
  }
 
  return items;
}

Hiding Entities from Menu

Entities not included in the menu configuration are hidden from navigation but still accessible via URL:

{
  menu: {
    'Main': ['User', 'Product']
    // Order and Customer are not in menu but still accessible
  }
}

To completely hide an entity, you would need to configure permissions.

Built-in Menus

ActiveQL Admin UI provides several built-in menu groups that can be customized or disabled:

Developer Menu

The Developer menu contains useful development tools:

{
  menu: {
    Developer: {
      seed: {
        label: 'Seed data',
        confirm: 'Reset and seed database?',
        action: async ({ activeAdmin, principal }) => {
          await activeAdmin.runtime.seed(true);
          return { message: 'Database seeded successfully' };
        }
      },
      graphql: {
        label: 'GraphQL Studio',
        path: '/graphql',
        newWindow: true
      },
      swagger: {
        label: 'Swagger / REST API',
        path: '/docs',
        newWindow: true
      },
      plantuml: {
        label: 'Entity Diagram',
        path: '/active-admin/plantuml',
        newWindow: true
      },
      repository: {
        label: 'Repository',
        // Auto-detected from package.json
        newWindow: true
      },
      configuration: {
        label: 'Show Configuration',
        path: '/active-admin/configuration',
        newWindow: true
      }
    }
  }
}

Disable specific items by setting them to false:

{
  menu: {
    Developer: {
      seed: false,  // Hide seed action
      swagger: false  // Hide swagger link
    }
  }
}

QueryMutation Menu

Automatically generates menu items for all GraphQL queries and mutations without arguments:

{
  menu: {
    QueryMutation: {
      // Auto-populated with:
      // - All queries with no arguments
      // - All mutations with no arguments (with confirmation)
      // Organized with dividers between groups
    }
  }
}

This menu is dynamically generated from your GraphQL schema. Example generated items:

  • ping - Query
  • --- Queries --- (divider)
  • systemStatus - Query
  • --- Mutations --- (divider)
  • resetCache - Mutation (with confirmation)
  • rebuildIndex - Mutation (with confirmation)

Disable QueryMutation menu:

{
  menu: {
    QueryMutation: false
  }
}

Entities Menu

The Entities menu is auto-generated based on your domain entities. By default, it lists all entities:

{
  menu: {
    Entities: ['User', 'Product', 'Order', 'Category']
  }
}

Dynamic generation based on realms:

{
  menu: {
    Entities: async (activeAdmin) => {
      const entities = activeAdmin.runtime.entities;
      // Group by realm or custom logic
      return {
        'Core': ['User', 'Role'],
        'Commerce': ['Product', 'Order'],
        'Content': ['Article', 'Page']
      };
    }
  }
}

Organize by realm (if entities have realm configuration):

// Entities are automatically grouped by their realm property
{
  menu: {
    Entities: (activeAdmin) => {
      const grouped = {};
      const entities = activeAdmin.runtime.entities;
 
      Object.keys(entities).forEach(name => {
        const realm = entities[name].realm || 'Default';
        if (!grouped[realm]) grouped[realm] = [];
        grouped[realm].push(name);
      });
 
      return grouped;
    }
  }
}

Custom Extra Menus

Add custom menu groups with the Extra property:

{
  menu: {
    Extra: {
      'Analytics': [
        {
          label: 'User Analytics',
          action: async ({ runtime }) => {
            const stats = await runtime.entity('User').stats();
            return {
              message: `Total users: ${stats.count}`
            };
          }
        },
        {
          label: 'Sales Dashboard',
          path: '/analytics/sales',
          newWindow: true
        }
      ],
      'Tools': [
        {
          label: 'Export Data',
          action: async ({ runtime }) => {
            const file = await exportAllData(runtime);
            return { redirect: `/download/${file}` };
          }
        }
      ]
    }
  }
}

Complete Example with Built-in Menus

export const activeAdminConfig: ActiveAdminConfig = {
  title: 'My Admin UI',
 
  menu: {
    // Customize Developer menu
    Developer: {
      seed: {
        label: 'Reset & Seed Database',
        confirm: 'This will delete all data and reset the database. Continue?',
        action: async ({ activeAdmin, principal }) => {
          // Custom seed logic
          if (!activeAdmin.runtime.principalHasRole('admin', { principal })) {
            return { message: 'Only admins can seed data' };
          }
          await activeAdmin.runtime.seed(true);
          return { message: 'Database reset and seeded' };
        }
      },
      graphql: true,
      swagger: true,
      plantuml: false,  // Disable diagram
      repository: true,
      configuration: true
    },
 
    // Keep auto-generated QueryMutation menu
    QueryMutation: true,
 
    // Customize Entities menu
    Entities: {
      'User Management': ['User', 'Role', 'Permission'],
      'E-Commerce': ['Product', 'Order', 'Customer', 'Payment'],
      'Content': ['Article', 'Category', 'Tag', 'Media']
    },
 
    // Add custom menus
    Extra: {
      'Reports': [
        {
          label: 'Monthly Sales',
          action: async ({ runtime }) => {
            const report = await generateMonthlySales(runtime);
            return { redirect: `/reports/${report.id}` };
          }
        }
      ]
    }
  }
};

Menu Configuration Type

type ActiveAdminMenuConfig = {
  Developer?: DeveloperMenu | false;
  QueryMutation?: MenuType | MenuTypeFn | false;
  Entities?: EntityMenu | EntityMenuFn | false;
  Extra?: {
    [groupName: string]: MenuType | MenuTypeFn;
  };
};
 
type DeveloperMenu = {
  seed?: MenuAction | false;
  graphql?: MenuAction | false;
  swagger?: MenuAction | false;
  plantuml?: MenuAction | false;
  repository?: MenuAction | false;
  configuration?: MenuAction | false;
};
 
type MenuAction = {
  label?: string;
  path?: string;
  newWindow?: boolean;
  confirm?: boolean | string;
  action?: (params: MenuActionParams) => Promise<MenuActionResult>;
};

Menu Icons

The Admin UI uses Tabler icons. You can reference them in custom templates if you create custom menu partials.