Admin UI
Configuration

Admin UI Configuration

The Admin UI is configured through the ActiveAdminConfiguration object passed to addActiveAdmin().

Configuration Overview

PropertyTypeDefaultDescription
pathstring/active-adminBase path for admin UI
titlestring | FunctionActiveQL UIPage title
entitiesobject{}Per-entity configuration
pageSizenumber20Items per page
viewPathstringadmin/viewsCustom view directory
menuobjectauto-generatedMenu structure
selectVsSearchThresholdnumber10When to show search vs dropdown
localestringenLocale for formatting
uploadTempPathstringdata/temp/Temporary upload directory
loginFunction-Login handler
principalForTokenFunction-Token validation
initFunction-Post-init hook

Basic Example

import { addActiveAdmin } from 'activeql-active-admin';
 
const config = {
  path: '/admin',
  title: 'My Application Admin',
  pageSize: 25,
  locale: 'de',
  entities: {
    User: {
      // Entity configuration
    },
    Product: {
      // Entity configuration
    }
  }
};
 
await addActiveAdmin(config);

path

path?: string
TypeDefaultDescription
string/active-adminBase URL path for admin UI

The admin UI will be accessible at this path. All entity routes will be nested under it.

Example

{
  path: '/admin'
}

The admin UI will be available at http://localhost:3000/admin.

title

title?: string | ((activeAdmin: ActiveAdmin) => string)
TypeDefaultDescription
string | FunctionActiveQL UIPage title displayed in header

Can be a static string or a function returning a string.

Example

{
  title: 'My Application Admin'
}
 
// Or dynamic
{
  title: (activeAdmin) => `Admin - ${process.env.APP_NAME}`
}

entities

entities?: { [name: string]: EntityUIConfiguration }
TypeDefaultDescription
object{}Per-entity UI configuration

See Entity configuration for details.

Example

{
  entities: {
    User: {
      collectionTitle: () => 'Users',
      itemTitle: (ui, item) => item.email,
      fields: {
        email: { label: 'Email Address' }
      }
    }
  }
}

pageSize

pageSize?: number
TypeDefaultDescription
number20Number of items per page in index views

Example

{
  pageSize: 50
}

viewPath

viewPath?: string
TypeDefaultDescription
stringadmin/viewsDirectory for custom view templates

You can provide custom Eta templates to override the default views.

Example

{
  viewPath: 'src/admin/views'
}

menu

menu?: { [name: string]: MenuType | MenuTypeFn }
TypeDefaultDescription
objectauto-generatedMenu structure

See Menu configuration for details.

Example

{
  menu: {
    'Main': ['User', 'Product'],
    'Settings': [
      { label: 'System Settings', path: '/settings' }
    ]
  }
}

selectVsSearchThreshold

selectVsSearchThreshold?: number
TypeDefaultDescription
number10Item count threshold for dropdown vs search

When an association has fewer items than this threshold, a dropdown is shown. Otherwise, a search field is used.

Example

{
  selectVsSearchThreshold: 25
}

locale

locale?: string
TypeDefaultDescription
stringenLocale for number and date formatting

Example

{
  locale: 'de'
}

uploadTempPath

uploadTempPath?: string
TypeDefaultDescription
stringdata/temp/Directory for temporary file uploads

Example

{
  uploadTempPath: '/tmp/uploads/'
}

login

login?: (runtime: ActiveQLServer, props: { email: string, password: string }) => Promise<{ token?: string }>
TypeDefaultDescription
Function-Custom login handler

See Authentication for details.

Example

{
  login: async (runtime, { email, password }) => {
    const user = await authenticateUser(email, password);
    if (user) {
      return { token: generateJWT(user) };
    }
    return {};
  }
}

principalForToken

principalForToken?: (token: string, runtime: ActiveQLServer) => Promise<Principal | undefined>
TypeDefaultDescription
Function-Token validation handler

See Authentication for details.

Example

{
  principalForToken: async (token, runtime) => {
    try {
      const decoded = verifyJWT(token);
      return new Principal(decoded.userId, decoded.roles);
    } catch {
      return undefined;
    }
  }
}

init

init?: (activeAdmin: ActiveAdmin) => void
TypeDefaultDescription
Function-Called after initialization

Use this hook for post-initialization setup.

Example

{
  init: (activeAdmin) => {
    console.log('Admin UI initialized at', activeAdmin.path);
  }
}

saveFiles

saveFiles?: (entity: Entity, item: any, files: any) => Promise<void>
TypeDefaultDescription
FunctionLocalFileHandlerCustom file save handler

Override the default file persistence logic.

getFileUrl

getFileUrl?: (entity: Entity, url: string) => string
TypeDefaultDescription
FunctionLocalFileHandlerCustom file URL generation

Override how file download URLs are generated.

Complete Example

import { addActiveAdmin } from 'activeql-active-admin';
 
const config = {
  path: '/admin',
  title: 'E-Commerce Admin',
  pageSize: 25,
  locale: 'en',
  selectVsSearchThreshold: 15,
  uploadTempPath: '/tmp/uploads/',
 
  entities: {
    User: {
      collectionTitle: () => 'Users',
      itemTitle: (ui, item) => `${item.firstName} ${item.lastName}`,
      fields: {
        email: { label: 'Email' },
        firstName: { label: 'First Name' },
        lastName: { label: 'Last Name' }
      },
      index: { fields: ['email', 'firstName', 'lastName'] }
    },
    Product: {
      collectionTitle: () => 'Products',
      itemTitle: (ui, item) => item.name,
      fields: {
        name: { label: 'Product Name' },
        price: { label: 'Price' }
      }
    }
  },
 
  menu: {
    'Main': ['User', 'Product'],
    'Reports': [
      { label: 'Sales Report', path: '/reports/sales' }
    ]
  },
 
  login: async (runtime, { email, password }) => {
    const user = await runtime.entity('User').findOneByAttribute({ email });
    if (user && await verifyPassword(password, user.password)) {
      return { token: generateJWT(user) };
    }
    return {};
  },
 
  principalForToken: async (token, runtime) => {
    const decoded = verifyJWT(token);
    return new Principal(decoded.userId, decoded.roles);
  },
 
  init: (activeAdmin) => {
    console.log('Admin initialized');
  }
};
 
await addActiveAdmin(config);