dart_admin

pub package Dart SDK License: MIT

A powerful, extensible, Django-inspired Admin Dashboard and backoffice generation suite for Dart backend frameworks (Shelf, Dart Frog, Serverpod, and raw dart:io).

dart_admin automatically inspects your data models, generates a modern server-rendered responsive administration interface, manages authentication, records audit trails, and handles database operations via SQLite ORM with zero frontend build steps required.

Warning

UNDER ACTIVE DEVELOPMENT β€” DO NOT USE IN PRODUCTION

dart_admin is currently in active development (v0.1.x experimental preview). APIs, database schemas, and configuration interfaces are subject to breaking changes between releases. It is intended for prototyping, evaluation, and internal tooling. Use in production environments is not recommended at this stage.


πŸ“‘ Table of Contents


✨ Key Features

  • πŸš€ Framework Agnostic: First-class adapters for Shelf, Dart Frog, and Serverpod / dart:io.
  • πŸ—„οΈ SQLite ORM & Auto-Migrations: SqliteOrmAdapter auto-generates tables, syncs new schema columns dynamically with ALTER TABLE, and executes parameterized queries.
  • 🎨 Modern Built-in UI: Sleek, responsive dark & light mode dashboard with glassmorphic accents, metric cards, and SVG icons.
  • ⚑ Zero Frontend Build Step: Zero npm, webpack, or Vite needed. Pre-compiled CSS & JS are served directly from Dart.
  • πŸ” Search & Filters: Real-time substring search across fields, enum dropdown filters, boolean toggles, and column-based sorting.
  • πŸ“‘ Pagination & Batch Actions: Configurable page sizes, multi-select checkboxes, and custom bulk actions.
  • πŸ”’ Persistent SQLite Auth: Salted SHA-256 password hashing, HMAC-signed session cookies, and fine-grained wildcard permissions.
  • πŸ“œ Audit Logging: Built-in SqliteAuditLogger tracking who created, edited, deleted, or executed bulk actions on records.
  • 🌐 JSON API Support: Built-in REST API endpoints (/api/{resource}) alongside server-rendered HTML.

πŸ“¦ Installation

Add dart_admin and SQLite packages to your pubspec.yaml:

dependencies:
  dart_admin: ^0.1.0
  shelf: ^1.4.1
  sqflite_common: ^2.5.4
  sqflite_common_ffi: ^2.3.4

πŸš€ Quick Start (Shelf + SQLite)

import 'dart:io';
import 'package:dart_admin/dart_admin.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:sqflite_common_ffi/sqflite_ffi.dart';

// 1. Define your data model
class Product {
  final int id;
  final String name;
  final num price;
  final bool inStock;

  Product({required this.id, required this.name, required this.price, this.inStock = true});

  Map<String, dynamic> toMap() => {'id': id, 'name': name, 'price': price, 'inStock': inStock};
  factory Product.fromMap(Map<String, dynamic> m) => Product(
    id: m['id'] is int ? m['id'] : int.parse(m['id'].toString()),
    name: m['name'] as String,
    price: m['price'] is num ? m['price'] : num.parse(m['price'].toString()),
    inStock: m['inStock'] == 1 || m['inStock'] == true,
  );
}

void main() async {
  // 2. Initialize SQLite Database Engine
  sqfliteFfiInit();
  final db = await databaseFactoryFfi.openDatabase('app_admin.db');

  // 3. Setup Persistent Auth & Audit Logging
  final authProvider = SqliteAuthProvider(
    db: db,
    defaultAdminUsername: 'admin',
    defaultAdminPassword: 'password123',
  );
  await authProvider.initialize();

  final auditLogger = SqliteAuditLogger(db: db);
  await auditLogger.initialize();

  // 4. Create AdminSite
  final admin = AdminSite(
    title: 'Store Admin',
    basePath: '/admin',
    authProvider: authProvider,
    auditLogger: auditLogger,
  );

  // 5. Register Model with SqliteOrmAdapter
  final productFields = const [
    AdminField(name: 'id', type: AdminFieldType.integer, readOnly: true),
    AdminField(name: 'name', type: AdminFieldType.text, required: true),
    AdminField(name: 'price', type: AdminFieldType.number, required: true),
    AdminField(name: 'inStock', type: AdminFieldType.boolean),
  ];

  final productAdapter = SqliteOrmAdapter<Product>(
    db: db,
    tableName: 'products',
    fields: productFields,
    getId: (p) => p.id,
    toMap: (p) => p.toMap(),
    fromMap: (m) => Product.fromMap(m),
  );
  await productAdapter.createTableIfNotExists();

  admin.register(AdminResource<Product>(
    name: 'Product',
    slug: 'products',
    adapter: productAdapter,
    getId: (p) => p.id,
    toMap: (p) => p.toMap(),
    fromMap: (m) => Product.fromMap(m),
    searchFields: ['name'],
    listFilters: ['inStock'],
    listDisplay: ['id', 'name', 'price', 'inStock'],
    fields: productFields,
  ));

  // 6. Mount Shelf Pipeline
  final handler = const Pipeline()
      .addMiddleware(logRequests())
      .addHandler((Request req) {
        if (req.url.path.startsWith('admin') || req.url.path.isEmpty) {
          return admin.toShelfHandler()(req);
        }
        return Response.notFound('Not Found');
      });

  final server = await shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);
  print('Admin panel running at: http://localhost:${server.port}/admin');
}

πŸ—„οΈ SQLite ORM & Automatic Schema Migrations

SqliteOrmAdapter<T> provides seamless ORM data mapping with type conversions, search indexing, parameterized filters, and automatic schema synchronization.

Defining a Model

final userFields = const [
  AdminField(name: 'id', type: AdminFieldType.integer, readOnly: true),
  AdminField(name: 'username', type: AdminFieldType.text, required: true),
  AdminField(name: 'email', type: AdminFieldType.email, required: true),
  AdminField(
    name: 'role',
    type: AdminFieldType.enumeration,
    choices: [
      AdminFieldChoice(value: 'admin', label: 'Administrator'),
      AdminFieldChoice(value: 'editor', label: 'Content Editor'),
      AdminFieldChoice(value: 'viewer', label: 'Viewer'),
    ],
  ),
  AdminField(name: 'isActive', type: AdminFieldType.boolean),
  AdminField(name: 'createdAt', type: AdminFieldType.dateTime, readOnly: true),
];

final userAdapter = SqliteOrmAdapter<User>(
  db: db,
  tableName: 'app_users',
  fields: userFields,
  getId: (u) => u.id,
  toMap: (u) => u.toMap(),
  fromMap: (m) => User.fromMap(m),
);

Automatic Schema Synchronization

When you add a new field to your model (e.g. discount):

  1. Add the property to your Dart class and serialization functions (toMap / fromMap).
  2. Add AdminField(name: 'discount', type: AdminFieldType.number) to your fields list.
  3. On application boot, adapter.createTableIfNotExists() runs syncTableSchema(), which queries PRAGMA table_info(...) and automatically runs:
    ALTER TABLE "app_products" ADD COLUMN "discount" REAL;
    
    Zero data loss and no manual SQL scripts required.

βš™οΈ Field Types & Form Validation

AdminFieldType provides rich HTML5 input widgets, automatic data coercion, and validation rules:

AdminFieldType SQLite Column HTML Widget Description & Options
text TEXT <input type="text"> Single line text. Supports required, validator, formatter
textarea TEXT <textarea> Multiline text for descriptions, notes, or JSON
integer INTEGER <input type="number" step="1"> Whole integer numbers
number REAL <input type="number" step="any"> Floating-point numbers and currency values
boolean INTEGER (0/1) <input type="checkbox"> Toggle switch checkbox
email TEXT <input type="email"> Built-in email syntax validation
password TEXT <input type="password"> Obscured password input
enumeration TEXT <select> Dropdown choices via choices: [AdminFieldChoice(...)]
date TEXT (ISO-8601) <input type="date"> DateTime formatted as YYYY-MM-DD
dateTime TEXT (ISO-8601) <input type="datetime-local"> DateTime with timestamp
url TEXT <input type="url"> Valid URL string with link rendering

πŸ”’ Authentication & Security Architecture

dart_admin provides a clean separation between Admin Users (backoffice staff) and Application Users (end-users of your mobile/web apps):

1. Admin Users (Backoffice Access)

Admin users are persisted in _admin_users by SqliteAuthProvider:

  • Salted SHA-256 Hashing: Generates a cryptographically random 16-byte salt per user.
  • Auto-Seeding: Creates the initial administrator on first startup if empty.
  • Signed Session Cookies: SessionManager signs sessions with HMAC-SHA256 tokens (dart_admin_session).
final authProvider = SqliteAuthProvider(
  db: db,
  defaultAdminUsername: 'admin',
  defaultAdminPassword: 'password123',
);
await authProvider.initialize();

// Programmatically create extra staff accounts:
await authProvider.createUser(
  username: 'sarah_editor',
  password: 'securePassword456!',
  roles: {'editor'},
  permissions: {'products.*', 'orders.view'},
);

2. Application Users (Mobile / Web App Authentication)

To authenticate your mobile app or frontend users using your backend database:

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:dart_admin/dart_admin.dart';
import 'package:shelf/shelf.dart';

// Session manager for App Users
final appSessionManager = SessionManager(
  secretKey: 'your-app-secret-key-32-chars-long',
  sessionDuration: const Duration(days: 30),
);

// Login Endpoint: POST /api/v1/auth/login
Future<Response> handleAppLogin(Request request) async {
  final body = await request.readAsString();
  final payload = jsonDecode(body) as Map<String, dynamic>;
  final email = payload['email']?.toString().toLowerCase().trim();
  final password = payload['password']?.toString();

  final result = await userAdapter.list(filters: {'email': email}, pageSize: 1);
  if (result.items.isEmpty) {
    return Response(401, body: '{"error":"Invalid credentials"}');
  }

  final user = result.items.first;
  final isValid = sha256.convert(utf8.encode(password! + user.salt)).toString() == user.passwordHash;
  if (!isValid) {
    return Response(401, body: '{"error":"Invalid credentials"}');
  }

  final token = appSessionManager.createToken(
    AdminUser(id: user.id, username: user.username, email: user.email, roles: {user.role}),
  );

  return Response.ok(
    jsonEncode({'status': 'success', 'token': token, 'user': user.toMap()}),
    headers: {'content-type': 'application/json'},
  );
}

Wildcard Permissions

AdminUser supports granular wildcard permission checks:

  • *.*: Superuser with access to all actions across all resources.
  • products.*: Full access to all operations on products.
  • products.view: Read-only access to view products.
  • products.delete: Permission to delete products.
AdminResource<User>(
  name: 'User',
  adapter: userAdapter,
  canView: (user) => user.hasPermission('users.view'),
  canAdd: (user) => user.hasPermission('users.add'),
  canChange: (user) => user.hasPermission('users.change'),
  canDelete: (user) => user.hasPermission('users.delete'),
);

πŸ“œ Persistent Audit Logging

SqliteAuditLogger automatically records audit trails for every create, update, delete, and bulk action into _admin_audit_logs:

final auditLogger = SqliteAuditLogger(db: db);
await auditLogger.initialize();

final admin = AdminSite(
  title: 'My Admin',
  authProvider: authProvider,
  auditLogger: auditLogger,
);

Query audit logs programmatically:

final logs = await auditLogger.getLogs(limit: 50);
for (final log in logs) {
  print('${log.timestamp} - ${log.username} executed ${log.action} on ${log.resourceSlug}');
}

⚑ Custom Bulk & Row Actions

Define custom bulk actions with confirmation modals, execution feedback, and audit logging:

admin.register(AdminResource<Product>(
  name: 'Product',
  adapter: productAdapter,
  getId: (p) => p.id,
  toMap: (p) => p.toMap(),
  fromMap: (m) => Product.fromMap(m),
  fields: productFields,
  actions: [
    AdminAction<Product>(
      name: 'apply_discount_20',
      label: 'Apply 20% Discount',
      handler: (List<Product> selectedProducts, Map<String, dynamic> context) async {
        for (final product in selectedProducts) {
          final discounted = (product.price ?? 0) * 0.8;
          await productAdapter.update(product.id, {'price': discounted});
        }
        return ActionResult.ok('Applied 20% discount to ${selectedProducts.length} product(s).');
      },
    ),
  ],
));

πŸ”Œ Framework Integrations

1. Shelf

import 'package:shelf/shelf.dart';
import 'package:dart_admin/dart_admin.dart';

final adminSite = AdminSite(...);

Handler get appHandler {
  final adminHandler = adminSite.toShelfHandler();

  return (Request request) {
    if (request.url.path.startsWith('admin')) {
      return adminHandler(request);
    }
    return Response.ok('Welcome to my API');
  };
}

2. Dart Frog

In routes/admin/[...slug].dart:

import 'package:dart_frog/dart_frog.dart';
import 'package:dart_admin/dart_admin.dart';
import '../../admin_setup.dart';

AdminSite? _cachedAdminSite;

Future<AdminSite> _getSite() async {
  return _cachedAdminSite ??= await setupDartFrogAdmin();
}

Future<Response> onRequest(RequestContext context, String slug) async {
  final site = await _getSite();
  final request = context.request;
  final body = await request.body();

  final adapter = site.toDartFrogAdapter();
  final adminResponse = await adapter.handle(
    method: request.method.value,
    uri: request.uri,
    headers: request.headers,
    body: body,
  );

  return Response(
    statusCode: adminResponse.statusCode,
    headers: adminResponse.headers,
    body: adminResponse.body,
  );
}

3. Serverpod & dart:io

In Serverpod Web Route or raw dart:io HttpServer:

import 'dart:io';
import 'package:dart_admin/dart_admin.dart';

final adminSite = AdminSite(...);

void handleServerpodWebRoute(HttpRequest request) async {
  await adminSite.handleHttpRequest(request);
}

🌐 Built-in REST JSON API

Every registered resource automatically exposes full REST API endpoints authenticated via the same session mechanism:

  • GET /admin/api/{resource}: Paginated JSON list with filters (?page=1&pageSize=20&search=keyword&field=value).
  • GET /admin/api/{resource}/{id}: Single record JSON.
  • POST /admin/api/{resource}: Create new record via JSON.
  • PUT /admin/api/{resource}/{id}: Update existing record via JSON.
  • DELETE /admin/api/{resource}/{id}: Delete record.

🎨 UI Engine & Customization

The default server-rendered UI features:

  • πŸŒ— Dark & Light Mode: Built-in toggle with persistent localStorage preference.
  • πŸ“± Mobile Responsive: Collapsible sidebar, horizontal scrollable data tables, and touch-friendly controls.
  • 🍞 Flash Notifications: Toast banners for successful operations, action results, and validation errors.
  • 🧩 Custom Renderers: Implement AdminUIRenderer to supply your own custom HTML templates or themes.

⚠️ Known Limitations & Gaps

While dart_admin provides a complete out-of-the-box solution, the following architectural gaps and limitations exist in the current version:

  1. Database Backend Support:
    • Currently, persistent ORM storage is natively implemented for SQLite (sqflite_common_ffi).
    • PostgreSQL and MySQL require creating a custom AdminAdapter<T> implementation.
  2. File & Media Storage Uploads:
    • Images and media are currently configured as AdminFieldType.url or handled manually via custom base64/static endpoints.
    • Built-in multi-part direct file uploads to local disk or AWS S3 / Cloudinary are not yet integrated into the default form widget.
  3. Complex Relational UI (Inline Foreign Keys):
    • Relational fields must be referenced via IDs or integer foreign keys. Dynamic autocomplete lookups for foreign relations (ManyToOne / ManyToMany tabular inlines) are not yet built-in.
  4. SQLite DROP COLUMN Restrictions:
    • syncTableSchema() automatically performs ALTER TABLE ADD COLUMN. However, column deletion or type renaming in SQLite requires table rebuilds and is not handled automatically to prevent accidental data destruction.
  5. Internationalization (i18n):
    • Dashboard UI labels (buttons, headers, navigation) are currently in English.

πŸ—ΊοΈ Roadmap (Things Yet To Be Done)

The following capabilities are actively planned for upcoming versions:

  • 🐘 PostgreSQL & MySQL ORM Adapters: Native adapters for Postgres and MySQL with connection pooling.
  • πŸ“€ Storage & File Upload Adapters: Dedicated AdminFieldType.file and AdminFieldType.image with S3, Google Cloud Storage, and local disk handlers.
  • πŸ”— Relational Select & Inline Inlines: Autocomplete dropdowns for foreign keys and nested inline editing (Django-style TabularInline).
  • πŸ“Š Dashboard Chart Widgets & Metrics: Configurable time-series line charts, bar graphs, and counter widgets on the homepage.
  • πŸ” Two-Factor Authentication (2FA / MFA): Time-based One-Time Password (TOTP) support for admin staff.
  • πŸ“₯ Data Import / Export: Built-in CSV and Excel exporter and batch data importer.
  • 🌍 i18n & Localization: Configurable dictionary support for multi-language admin interfaces.
  • 🧩 Lifecycle Hooks: beforeSave, afterSave, beforeDelete hooks on AdminResource.

πŸ§ͺ Testing & Quality Assurance

dart_admin includes automated unit and integration tests across core models, auth, routers, query builders, SQLite CRUD lifecycles, and all framework adapters:

# Run static analysis
dart analyze

# Run all test suites
dart test

πŸ“„ License

MIT License. Copyright (c) 2026. See LICENSE for full details.

Libraries

dart_admin
A Django-style extensible Admin Dashboard for Dart backend frameworks.