Admin Interface
The admin interface is forge's auto-generated admin panel. Register your models and you get a full web UI for managing your data. No code required.
Why use it?β
Because building admin interfaces is boring:
- Zero code - Just register your models
- Full CRUD - Create, read, update, deleteβall there
- Search and filters - Find what you need fast
- Bulk actions - Update or delete multiple records at once
- Export - Download your data as CSV or JSON
Quick startβ
1. Define Your Schemaβ
First, ensure your models implement the schema interface:
type UserSchema struct{}
func (s *UserSchema) Fields() []schema.Field {
return []schema.Field{
{Name: "ID", Type: schema.TypeInt64, PrimaryKey: true},
{Name: "Username", Type: schema.TypeString, Required: true},
{Name: "Email", Type: schema.TypeEmail, Required: true},
{Name: "IsActive", Type: schema.TypeBool, Default: true},
}
}
func (s *UserSchema) Meta() schema.Meta {
return schema.Meta{
TableName: "users",
VerboseName: "User",
VerboseNamePlural: "Users",
}
}
2. Register Models with Adminβ
import (
admincore "github.com/forgego/forge/admin/core"
"github.com/forgego/forge/admin/http"
"github.com/forgego/forge/schema"
query "github.com/forgego/forge/orm"
)
func main() {
// Setup database and managers
database := db.NewDBFromConfig(cfg)
userManager := query.NewManager[User](database)
// Register admin
userSchema := &UserSchema{}
userAdmin, err := admincore.Register[User](
schema.NewSchema(userSchema),
userManager,
&admincore.Config[User]{
ListDisplay: []string{"username", "email", "is_active"},
SearchFields: []string{"username", "email"},
},
)
if err != nil {
log.Fatal(err)
}
// Register for HTTP handlers
http.RegisterAdminForHTTP(userAdmin)
// Setup router
router := httplib.NewRouter()
adminRouter := http.NewRouter(admin.GetGlobalRegistry())
adminRouter.RegisterRoutes(router, "/admin")
// Start server
log.Fatal(http.ListenAndServe(":8080", router))
}
3. Access Adminβ
Start your server and visit http://localhost:8000/admin/
You'll see:
- A list of all registered models
- Links to view, add, and manage each model
- Search and filtering capabilities
- Full CRUD operations
Admin Configuration Optionsβ
List Displayβ
Control which fields appear in the list view:
config := &admincore.Config[User]{
ListDisplay: []string{"username", "email", "is_active", "created_at"},
}
List Display Linksβ
Make fields clickable to navigate to detail view:
config := &admincore.Config[User]{
ListDisplay: []string{"username", "email"},
ListDisplayLinks: []string{"username"}, // username is clickable
}
Search Fieldsβ
Enable search on specific fields:
config := &admincore.Config[User]{
SearchFields: []string{"username", "email"},
}
List Filterβ
Add filters to the sidebar:
config := &admincore.Config[Post]{
ListFilter: []string{"published", "created_at", "author"},
}
You can also use custom filter objects:
import adminfilters "github.com/forgego/forge/admin/filters"
config := &admincore.Config[Post]{
ListFilter: []admincore.Filter[Post]{
adminfilters.NewBooleanFilter[Post]("published"),
adminfilters.NewDateFilter[Post]("created_at"),
adminfilters.NewRelatedFieldListFilter[Post, User](
authorField,
userManager,
),
},
}
Date Hierarchyβ
Add date-based navigation:
config := &admincore.Config[Post]{
DateHierarchy: "created_at",
}
Orderingβ
Set default ordering:
config := &admincore.Config[Post]{
Ordering: []string{"-created_at", "title"}, // Descending by created_at, then ascending by title
}
Read Only Fieldsβ
Make fields read-only in forms:
config := &admincore.Config[User]{
ReadOnlyFields: []string{"created_at", "last_login"},
}
Fieldsetsβ
Organize form fields into groups:
config := &admincore.Config[User]{
Fieldsets: []admincore.Fieldset[User]{
{
Title: "Personal Information",
Fields: []string{"username", "email"},
},
{
Title: "Permissions",
Fields: []string{"is_active", "is_staff", "is_superuser"},
Collapsible: true, // Optional: make collapsible
},
{
Title: "Important Dates",
Fields: []string{"created_at", "last_login"},
},
},
}
List Per Pageβ
Control pagination:
config := &admincore.Config[Post]{
ListPerPage: 25, // Items per page
ListMaxShowAll: 100, // Max items for "show all"
}
Advanced Customizationβ
Custom Querysetβ
Override the base queryset:
config := &admincore.Config[Post]{
GetQueryset: func(ctx context.Context, admin *admincore.Admin[Post], qs query.QuerySet[Post]) (query.QuerySet[Post], error) {
// Only show published posts to non-staff users
if user := GetUserFromContext(ctx); user != nil && !user.IsStaff {
return qs.Filter(publishedField.Eq(true)), nil
}
return qs, nil
},
}
Custom Save Logicβ
Customize how models are saved:
config := &admincore.Config[Post]{
SaveModel: func(ctx context.Context, admin *admincore.Admin[Post], instance *Post, formData admincore.FormData, isNew bool) error {
// Custom validation
if instance.Title == "" {
return errors.New("title is required")
}
// Set timestamps
if isNew {
instance.CreatedAt = time.Now()
}
instance.UpdatedAt = time.Now()
// Call default save
if isNew {
return admin.Manager().Create(ctx, instance)
}
return admin.Manager().Update(ctx, instance)
},
}
Custom Delete Logicβ
Customize delete behavior:
config := &admincore.Config[Post]{
DeleteModel: func(ctx context.Context, admin *admincore.Admin[Post], instance *Post) error {
// Soft delete instead of hard delete
instance.Deleted = true
return admin.Manager().Update(ctx, instance)
},
}
Custom Actionsβ
Add custom bulk actions:
import (
"context"
admincore "github.com/forgego/forge/admin/core"
)
config := &admincore.Config[Post]{
Actions: []admincore.Action[Post]{
admincore.NewAction[Post](
"publish",
"Publish selected posts",
func(ctx context.Context, posts []*Post) error {
for _, post := range posts {
post.Published = true
if err := postManager.Update(ctx, post); err != nil {
return err
}
}
return nil
},
),
admincore.NewAction[Post](
"unpublish",
"Unpublish selected posts",
func(ctx context.Context, posts []*Post) error {
for _, post := range posts {
post.Published = false
if err := postManager.Update(ctx, post); err != nil {
return err
}
}
return nil
},
),
},
}
Inlinesβ
Add related model editing:
config := &admincore.Config[Post]{
Inlines: []admincore.Inline[Post, Comment]{
admincore.TabularInline[Post, Comment](
commentManager,
"post_id", // Foreign key field name
[]string{"author", "content", "created_at"}, // Fields to display
),
// Or use StackedInline for a different layout
admincore.StackedInline[Post, Comment](
commentManager,
"post_id",
[]string{"author", "content", "created_at"},
),
},
}
Featuresβ
List Viewβ
The list view provides:
- Pagination - Automatic pagination with configurable page size
- Search - Full-text search on specified fields
- Sorting - Click column headers to sort
- Filtering - Filter by field values
- Inline Editing - Double-click cells to edit inline (coming soon)
- Bulk Actions - Select multiple items for bulk operations
Detail Viewβ
View all fields of a model instance:
- Read-only display of all fields
- Related objects display
- History/audit trail (coming soon)
Create/Edit Formsβ
Auto-generated forms with:
- Proper input types for different field types
- Client and server-side validation
- Rich text editing for textarea fields
- Date pickers for date/time fields
- Enhanced dropdowns for select fields
- File upload support (coming soon)
Deleteβ
Delete with confirmation:
- Browser confirmation dialog
- Smooth row removal with HTMX
- Cascade delete handling
Permissionsβ
Control access to admin with permission checking:
import (
"github.com/forgego/forge/identity"
)
// Setup permission checker
permissionChecker := identity.NewPermissionChecker(userRepo)
config := &admincore.Config[User]{
PermissionChecker: permissionChecker,
// Custom permission checks
HasAddPermission: func(ctx context.Context, user interface{}) bool {
return permissionChecker.HasPermission(ctx, user, "auth.add_user")
},
HasChangePermission: func(ctx context.Context, user interface{}, obj *User) bool {
// Users can only edit themselves unless they're staff
if staff, ok := user.(*User); ok {
if !staff.IsStaff && staff.ID != obj.ID {
return false
}
}
return permissionChecker.HasPermission(ctx, user, "auth.change_user")
},
HasDeletePermission: func(ctx context.Context, user interface{}, obj *User) bool {
// Only staff can delete users
if staff, ok := user.(*User); ok {
if !staff.IsStaff {
return false
}
}
return permissionChecker.HasPermission(ctx, user, "auth.delete_user")
},
HasViewPermission: func(ctx context.Context, user interface{}, obj *User) bool {
return permissionChecker.HasPermission(ctx, user, "auth.view_user")
},
}
Custom Templatesβ
You can override admin templates by placing them in your project's templates/admin/ directory:
templates/
βββ admin/
βββ base.html
βββ index.html
βββ list.html
βββ form.html
βββ detail.html
Stylingβ
The admin uses Bootstrap 5 and can be customized with CSS:
/* Custom admin styles */
.admin-header {
background-color: #your-brand-color;
}
Best Practicesβ
- Use List Display - Show only relevant fields in list view
- Enable Search - Add search fields for frequently searched columns
- Use Filters - Add filters for fields users commonly filter by
- Organize Fieldsets - Group related fields together
- Set Read-Only - Make auto-generated fields read-only
Examplesβ
Blog Adminβ
admin.Register(&admin.Config[models.Post]{
ListDisplay: []admin.Field{
models.PostFieldsInstance.Title,
models.PostFieldsInstance.Author,
models.PostFieldsInstance.Published,
models.PostFieldsInstance.CreatedAt,
},
SearchFields: []admin.Field{
models.PostFieldsInstance.Title,
models.PostFieldsInstance.Content,
},
ListFilter: []admin.Field{
models.PostFieldsInstance.Published,
models.PostFieldsInstance.Author,
models.PostFieldsInstance.CreatedAt,
},
DateHierarchy: "created_at",
})
User Adminβ
admin.Register(&admin.Config[models.User]{
ListDisplay: []admin.Field{
models.UserFieldsInstance.Username,
models.UserFieldsInstance.Email,
models.UserFieldsInstance.IsActive,
models.UserFieldsInstance.IsStaff,
models.UserFieldsInstance.DateJoined,
},
SearchFields: []admin.Field{
models.UserFieldsInstance.Username,
models.UserFieldsInstance.Email,
},
ListFilter: []admin.Field{
models.UserFieldsInstance.IsActive,
models.UserFieldsInstance.IsStaff,
models.UserFieldsInstance.DateJoined,
},
Fieldsets: []admin.Fieldset[models.User]{
admin.NewFieldset[models.User]("Personal Information", "username", "email"),
admin.NewFieldset[models.User]("Permissions", "is_active", "is_staff", "is_superuser"),
},
ReadOnlyFields: []any{"date_joined", "last_login"},
})
Export Functionalityβ
Export your data in various formats:
// Export is automatically available in the list view
// Users can export filtered results as CSV or JSON
The export feature:
- Exports current filtered results
- Supports CSV and JSON formats
- Respects permissions (only exports what user can view)
- Handles large datasets efficiently
Custom Widgetsβ
Customize form widgets for specific fields:
import (
adminwidgets "github.com/forgego/forge/admin/widgets"
)
config := &admincore.Config[Post]{
FormFieldOverrides: map[string]admincore.Widget{
"content": adminwidgets.NewRichTextWidget(),
"published_at": adminwidgets.NewDateTimePicker(),
"tags": adminwidgets.NewSelectSearchWidget(),
},
}
Available widgets:
TextInput- Standard text inputTextarea- Multi-line text inputSelect- Dropdown selectSelectSearch- Searchable select (for foreign keys)Checkbox- Boolean checkboxRadioButtons- Radio button groupDatePicker- Date pickerTimePicker- Time pickerDateTimePicker- Combined date/time pickerRichTextEditor- WYSIWYG editorFileUpload- File upload widgetImageUpload- Image upload with preview
Type Safetyβ
The admin system is fully type-safe using Go generics:
// Type-safe field expressions
usernameField := admincore.StringField[User](
"username",
func(u *User) string { return u.Username },
func(u *User, v string) { u.Username = v },
)
// Type-safe filters
activeFilter := adminfilters.NewBooleanFilter[User](usernameField)
// Type-safe actions
action := admincore.NewAction[User](
"activate",
"Activate users",
func(ctx context.Context, users []*User) error {
// users is []*User, fully typed
for _, user := range users {
user.IsActive = true
}
return nil
},
)
Performance Optimizationβ
Select Relatedβ
Optimize queries with select_related:
config := &admincore.Config[Post]{
ListSelectRelated: []string{"author"}, // Join author in list view
}
Prefetch Relatedβ
Optimize many-to-many and reverse foreign keys:
config := &admincore.Config[Post]{
ListPrefetchRelated: []string{"comments", "tags"}, // Prefetch related objects
}
Complete Exampleβ
Here's a complete example with all features:
package blog
import (
"context"
admincore "github.com/forgego/forge/admin/core"
"github.com/forgego/forge/admin/http"
"github.com/forgego/forge/schema"
query "github.com/forgego/forge/orm"
)
func InitPostAdmin(postManager *query.Manager[Post], commentManager *query.Manager[Comment]) (*admincore.Admin[Post], error) {
postSchema := &PostSchema{}
admin, err := admincore.Register[Post](
schema.NewSchema(postSchema),
postManager,
&admincore.Config[Post]{
// List configuration
ListDisplay: []string{"title", "author", "published", "created_at"},
ListDisplayLinks: []string{"title"},
ListPerPage: 25,
Ordering: []string{"-created_at"},
// Search and filters
SearchFields: []string{"title", "content"},
ListFilter: []string{"published", "author", "created_at"},
DateHierarchy: "created_at",
// Form configuration
Fieldsets: []admincore.Fieldset[Post]{
{Title: "Post", Fields: []string{"title", "content", "author"}},
{Title: "Publishing", Fields: []string{"published"}},
},
ReadOnlyFields: []string{"created_at", "updated_at"},
// Inlines
Inlines: []admincore.Inline[Post, Comment]{
admincore.TabularInline[Post, Comment](
commentManager,
"post_id",
[]string{"author", "content", "created_at"},
),
},
// Actions
Actions: []admincore.Action[Post]{
admincore.NewAction[Post](
"publish",
"Publish selected",
func(ctx context.Context, posts []*Post) error {
for _, post := range posts {
post.Published = true
postManager.Update(ctx, post)
}
return nil
},
),
},
// Performance
ListSelectRelated: []string{"author"},
ListPrefetchRelated: []string{"comments"},
},
)
if err != nil {
return nil, err
}
http.RegisterAdminForHTTP(admin)
return admin, nil
}
Next Stepsβ
- Admin Tutorial - Step-by-step tutorial
- REST API Guide - Build APIs for your frontend
- Security Guide - Secure your admin interface
- Advanced Topics - Extend the admin with plugins