dart_admin 0.1.0
dart_admin: ^0.1.0 copied to clipboard
A Django-inspired extensible Admin Dashboard for Dart backend frameworks (Shelf, Dart Frog, Serverpod).
dart_admin #
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
- π¦ Installation
- π Quick Start (Shelf + SQLite)
- ποΈ SQLite ORM & Automatic Schema Migrations
- βοΈ Field Types & Form Validation
- π Authentication & Security Architecture
- π Persistent Audit Logging
- β‘ Custom Bulk & Row Actions
- π Framework Integrations
- π Built-in REST JSON API
- π¨ UI Engine & Customization
- β οΈ Known Limitations & Gaps
- πΊοΈ Roadmap (Things Yet To Be Done)
- π§ͺ Testing & Quality Assurance
- π License
β¨ Key Features #
- π Framework Agnostic: First-class adapters for Shelf, Dart Frog, and Serverpod /
dart:io. - ποΈ SQLite ORM & Auto-Migrations:
SqliteOrmAdapterauto-generates tables, syncs new schema columns dynamically withALTER 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
SqliteAuditLoggertracking 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):
- Add the property to your Dart class and serialization functions (
toMap/fromMap). - Add
AdminField(name: 'discount', type: AdminFieldType.number)to yourfieldslist. - On application boot,
adapter.createTableIfNotExists()runssyncTableSchema(), which queriesPRAGMA table_info(...)and automatically runs:
Zero data loss and no manual SQL scripts required.ALTER TABLE "app_products" ADD COLUMN "discount" REAL;
βοΈ 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:
SessionManagersigns 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 onproducts.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
localStoragepreference. - π± 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
AdminUIRendererto 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:
- 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.
- Currently, persistent ORM storage is natively implemented for SQLite (
- File & Media Storage Uploads:
- Images and media are currently configured as
AdminFieldType.urlor 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.
- Images and media are currently configured as
- Complex Relational UI (Inline Foreign Keys):
- Relational fields must be referenced via IDs or integer foreign keys. Dynamic autocomplete lookups for foreign relations (
ManyToOne/ManyToManytabular inlines) are not yet built-in.
- Relational fields must be referenced via IDs or integer foreign keys. Dynamic autocomplete lookups for foreign relations (
- SQLite DROP COLUMN Restrictions:
syncTableSchema()automatically performsALTER TABLE ADD COLUMN. However, column deletion or type renaming in SQLite requires table rebuilds and is not handled automatically to prevent accidental data destruction.
- 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.fileandAdminFieldType.imagewith 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,beforeDeletehooks onAdminResource.
π§ͺ 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.