| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Β | Β | |||
Β | Β | |||
Β | Β | |||
Β | Β | |||
Β | Β | |||
Β | Β | |||
Β | Β | |||
Β | Β | |||
Β | Β | |||
Finally, an admin panel that Serverpod deserves!
You've built an amazing Serverpod app with powerful endpoints, robust models, and a beautiful frontend. But when it comes to managing your data, you're stuck writing custom endpoints, building one-off admin pages, or worseβdirectly accessing the database.
That's where Serverpod Admin comes in. This is the missing piece that transforms your Serverpod backend into a fully manageable system.
Secure login screen for admin users. Only users with the serverpod.admin scope can access the dashboard.
Browse and manage all your data with a beautiful, intuitive interface.
New record with a clean, user-friendly interface.

Powerful search and filtering capabilities to find exactly what you need.
Edit record with a clean, user-friendly interface.
Delete record with a clean, user-friendly interface.

View detailed information about any record.
Beautiful empty states when no records are found.
Monitor scheduled, ready, paused, failed, finished, and historical Serverpod jobs from the same admin dashboard.
No more writing boilerplate CRUD endpoints. Register your models once, and instantly get a complete admin interface with:
Built with flexibility in mind. The serverpod_admin_server exposes a clean API that any frontend can consume. Start with Flutter today, switch to Jaspr tomorrow, or build your own custom admin UIβthe choice is yours!
Stop spending days building admin interfaces. Get back to building features that matter. With Serverpod Admin, you can have a production-ready admin panel in minutes, not weeks.
Serverpod Admin has two ways to use the UI:
Both paths use the same server package, authentication, model registry, import and export support, profile/password flows, and jobs monitoring endpoints.
From your Serverpod server package directory:
dart pub add serverpod_admin_serverServerpod Admin uses serverpod_auth_idp. Configure auth, register the models you want to manage, and enable jobs if you want the Serverpod jobs dashboard.
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_admin_server/serverpod_admin_server.dart' as admin;
import 'src/generated/endpoints.dart';
import 'src/generated/protocol.dart';
void run(List<String> args) async {
final pod = Serverpod(args, Protocol(), Endpoints());
pod.initializeAuthServices(
tokenManagerBuilders: [
JwtConfigFromPasswords(),
],
identityProviderBuilders: [
EmailIdpConfigFromPasswords(
sendRegistrationVerificationCode: _sendRegistrationCode,
sendPasswordResetVerificationCode: _sendPasswordResetCode,
),
],
);
admin.jobs = true;
admin.configureAdminModule((registry) {
registry.register<Post>();
registry.register<Person>();
registry.register<Comment>();
registry.register<Setting>();
});
admin.serveAdminDashboard(pod); // /admin
await pod.start();
}admin.jobs = true shows Serverpod's persisted future-call jobs in the admin dashboard, including scheduled, ready, paused, failed, finished, and historical jobs.
Enum columns are edited with a dropdown. On the Dart VM (dart run) their values are discovered automatically. A compiled server (dart compile exe, dart build cli, most Docker images) cannot discover the values of name-serialized enums, so declare the enums your models use:
admin.configureAdminModule((registry) {
registry.register<Order>();
registry.registerEnum(OrderStatus.values);
});Pass the complete values list; it is harmless on the VM and takes precedence over automatic discovery.
To manage who can open the dashboard, register Serverpod's auth users:
admin.configureAdminModule((registry) {
registry.registerAuthUsers();
});The serverpod_auth_core_user table then lists every account. Pick serverpod.admin in scopeNames to make someone an administrator (unpick it to remove access; it applies when their access token next refreshes), and tick blocked to stop someone signing in. Accounts are not created or deleted there: they come from sign-up or AdminUser.create.
Models that extend the auth user show its access in their own rows, so you manage a user where you manage their profile. A model extends the auth user when it holds the user's id in a unique column: a relation to module:serverpod_auth_core:AuthUser, or a UuidValue field named authUserId:
class: UserProfile
table: user_profile
fields:
authUserId: UuidValue
fullName: String
indexes:
user_profile_auth_user_idx:
fields: authUserId
unique: trueregistry.register<UserProfile>() then adds authUser.scopeNames and authUser.blocked to each row. They are read from and saved to serverpod_auth_core_user, never copied into your table. Rows whose auth user was deleted still save, with no access to grant. Name the column with register<T>(authUserColumn: 'ownerId') if it is not detected. An admin cannot remove their own admin access or block themselves.
Offer your own permission scopes in the same picker:
registry.registerAuthUsers(scopes: [const Scope('dispatcher')]);Any list or set column can be edited as a multi-select the same way:
registry.register<Team>(choices: {'tags': ['beta', 'internal']});From the same Serverpod server package directory:
dart run serverpod_admin_server:serverpod_admin installThe installer downloads the prebuilt Flutter web dashboard from the latest GitHub release and places it in web/admin. admin.serveAdminDashboard(pod) serves that folder at /admin.
To install to another folder:
dart run serverpod_admin_server:serverpod_admin install --target web/customadminpathTo serve another route:
admin.serveAdminDashboard(
pod,
path: '/customadminpath',
);Admin users must have the serverpod.admin scope. Use this as a development or bootstrap helper, then remove it or guard it once your admin user exists.
import 'dart:io';
import 'package:serverpod_admin_server/serverpod_admin_server.dart';
Future<void> createAdminUser() async {
final email = Platform.environment['SERVERPOD_ADMIN_EMAIL'];
final password = Platform.environment['SERVERPOD_ADMIN_PASSWORD'];
if (email == null || password == null) {
throw StateError(
'Set SERVERPOD_ADMIN_EMAIL and SERVERPOD_ADMIN_PASSWORD first.',
);
}
await AdminUser.create(
email: email,
password: password,
);
}http://localhost:8082/admin
The dashboard shows a login screen for unauthenticated users and only allows users with the serverpod.admin scope.
The prebuilt app talks to your Serverpod API. In local development it maps localhost:8082/admin to localhost:8080/ for API calls by default.
Use this path when you want to build your own Flutter admin app and customize the UI. You still use serverpod_admin_server on the backend, but you do not need to run the prebuilt dashboard installer.
From your Flutter app:
flutter pub add serverpod_admin_dashboardYour Flutter app also needs the generated client for your Serverpod project, including the serverpod_admin module client.
import 'package:flutter/material.dart';
import 'package:serverpod_admin_dashboard/serverpod_admin_dashboard.dart';
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';
import 'src/generated/client.dart';
late final Client client;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
client = Client('http://localhost:8080/')
..connectivityMonitor = FlutterConnectivityMonitor()
..authSessionManager = FlutterAuthSessionManager();
await client.auth.initialize();
runApp(
AdminDashboard(
client: client,
title: 'My Admin',
sidebarItemCustomizations: const {
serverpodJobsResourceKey: SidebarItemCustomization(
label: 'Jobs',
icon: Icons.work_history,
),
},
),
);
}The custom dashboard path lets you keep the default admin behavior while overriding the parts you care about:
The authentication behavior is the same as the non-custom path: unauthenticated users see login, and only users with serverpod.admin can enter.
Serverpod Admin offers flexible customization options, from simple sidebar tweaks to complete UI replacement.
Customize individual sidebar items with custom labels and icons:
AdminDashboard(
client: client,
sidebarItemCustomizations: {
'posts': SidebarItemCustomization(
label: 'Posts',
icon: Icons.post_add,
),
'persons': SidebarItemCustomization(
label: 'Person',
icon: Icons.person,
),
'comments': SidebarItemCustomization(
label: 'Comment',
icon: Icons.comment,
),
'settings': SidebarItemCustomization(
label: 'Setting',
icon: Icons.settings,
),
},
)This allows you to:
For complete control over the admin interface, you can replace any component with your own custom widgets:
AdminDashboard(
client: client,
// Custom sidebar - completely replace the default sidebar
customSidebarBuilder: (context, controller) {
return CustomSidebar(controller: controller);
},
// Custom body/records pane - replace the default table view
customBodyBuilder: (context, controller, operations) {
return CustomBody(
controller: controller,
operations: operations,
);
},
// Custom record details view
customDetailsBuilder: (context, controller, operations, resource, record) {
return CustomDetails(
controller: controller,
operations: operations,
resource: resource,
record: record,
);
},
// Custom edit dialog
customEditDialogBuilder: (context, controller, operations, resource,
currentValues, onSubmit) {
return CustomEditDialog(
resource: resource,
currentValues: currentValues,
onSubmit: onSubmit,
);
},
// Custom delete confirmation dialog
customDeleteDialogBuilder: (context, controller, operations, resource,
record, onConfirm) {
return CustomDeleteDialog(
resource: resource,
record: record,
onConfirm: onConfirm,
);
},
// Custom create dialog
customCreateDialogBuilder: (context, controller, operations, resource,
onSubmit) {
return CustomCreateDialog(
resource: resource,
onSubmit: onSubmit,
);
},
// Custom footer (displayed above the default footer)
customFooterBuilder: (context, controller) {
return CustomFooter(controller: controller);
},
// Custom themes
lightTheme: myLightTheme,
darkTheme: myDarkTheme,
initialThemeMode: ThemeMode.dark,
)| Builder | Purpose | Parameters |
|---|---|---|
| customSidebarBuilder | Replace the entire sidebar | (context, controller) |
| customBodyBuilder | Replace the records table view | (context, controller, operations) |
| customDetailsBuilder | Replace the record details view | (context, controller, operations, resource, record) |
| customEditDialogBuilder | Replace the edit dialog | (context, controller, operations, resource, currentValues, onSubmit) |
| customDeleteDialogBuilder | Replace the delete confirmation dialog | (context, controller, operations, resource, record, onConfirm) |
| customCreateDialogBuilder | Replace the create dialog | (context, controller, operations, resource, onSubmit) |
| customFooterBuilder | Add custom footer above default footer | (context, controller) |
When creating custom builders, you have access to:
AdminDashboardController β Provides access to:
HomeOperations β Provides CRUD operations:
AdminResource β Information about the resource:
Widget CustomSidebar(AdminDashboardController controller) {
return Drawer(
child: ListView(
children: [
const DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text('My Admin Panel'),
),
...controller.resources.map((resource) {
final customization = controller.sidebarItemCustomizations?[resource.key];
return ListTile(
leading: Icon(customization?.icon ?? Icons.table_chart),
title: Text(customization?.label ?? resource.tableName),
selected: controller.selectedResource?.key == resource.key,
onTap: () => controller.selectResource(resource),
);
}),
],
),
);
}Widget CustomEditDialog({
required AdminResource resource,
required Map<String, String> currentValues,
required Future<bool> Function(Map<String, String> payload) onSubmit,
}) {
final formKey = GlobalKey<FormState>();
final controllers = currentValues.map(
(key, value) => MapEntry(key, TextEditingController(text: value)),
);
return AlertDialog(
title: Text('Edit ${resource.tableName}'),
content: Form(
key: formKey,
child: SingleChildScrollView(
child: Column(
children: resource.columns.map((column) {
return TextFormField(
controller: controllers[column.name],
decoration: InputDecoration(labelText: column.name),
enabled: !column.isId, // Disable editing ID fields
);
}).toList(),
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () async {
if (formKey.currentState!.validate()) {
final payload = controllers.map(
(key, controller) => MapEntry(key, controller.text),
);
final success = await onSubmit(payload);
if (success && context.mounted) {
Navigator.pop(context);
}
}
},
child: const Text('Save'),
),
],
);
}Serverpod Admin implements strict role-based access control:
Important: Without the serverpod.admin scope, users will see an error message and cannot access the admin panel, even if they successfully authenticate with email/password.
Serverpod Admin fills the gap that every Serverpod developer has felt. No more custom admin code. No more database dives. Just a beautiful, powerful admin panel that works out of the box.
Welcome to the future of Serverpod administration. π
| Back | FazBrowse Home | New Git URL |