A modern, declarative admin panel for Laravel with advanced media management and composable field architecture.
- Advanced Media System - State-path based collections, deferred actions, multiple upload strategies, image conversions, drag-n-drop reordering
- Fieldset Component - Repeatable field groups stored in JSON with nested media support, drag-n-drop sorting, and automatic cleanup
- Hierarchical Relations - BelongsToSelect with automatic parent-child tree building and visual indentation
- Modal CRUD - Create and edit records in modals without page reload
- Inline Editing - Edit table cells directly with text, number, and toggle support
- Rich Components - 23 field types including RichEditor (Jodit), CodeEditor, hierarchical selects
- Granular ACL - Role-based permissions with groups, bulk operations, caching, and default roles
- PHP 8.2 or higher
- Laravel 12.0 or higher
Install via Composer and run the setup command:
composer require monstrex/ave
php artisan ave:installThis will:
- Publish configuration and assets
- Run migrations
- Set up the admin panel at
/admin
Options:
php artisan ave:install --force # Overwrite existing files
php artisan ave:install --no-migrate # Skip migrationsTo completely remove Ave Admin Panel from your application:
php artisan ave:uninstallThis will:
- Drop all Ave database tables
- Delete published config and assets
- Remove admin users (optional)
- Clear Ave cache entries
Options:
php artisan ave:uninstall --dry-run # Preview what would be deleted
php artisan ave:uninstall --force # Skip confirmation
php artisan ave:uninstall --keep-users # Don't delete admin users
php artisan ave:uninstall --keep-config # Keep published config
php artisan ave:uninstall --keep-assets # Keep published assetsNote: After uninstalling, remove the package from composer:
composer remove monstrex/aveCreate a resource in app/Ave/Resources:
<?php
namespace App\Ave\Resources;
use Monstrex\Ave\Admin\BaseResource;
use Monstrex\Ave\Core\Components\Fields\TextInput;
use Monstrex\Ave\Core\Components\Fields\Media;
use Monstrex\Ave\Core\Components\Columns\Column;
use Monstrex\Ave\Core\Components\Columns\ImageColumn;
use App\Models\Post;
class PostResource extends BaseResource
{
public static string $model = Post::class;
protected static ?string $slug = 'posts';
public function fields(): array
{
return [
TextInput::make('title')->required(),
Media::make('cover')->single()->acceptImages(),
];
}
public function columns(): array
{
return [
Column::make('title')->sortable()->searchable(),
ImageColumn::make('cover')->fromMedia('cover', 'thumb'),
];
}
}Visit /admin/posts to see your resource.
Text Inputs:
TextInput- Single-line with type variants (email, url, tel, number), prefix/suffix, validationTextarea- Multi-line textNumber- Numeric input with min/max/stepPasswordInput- Password fieldHidden- Hidden field
Rich Editors:
RichEditor- WYSIWYG editor powered by Jodit with toolbar presets (minimal/basic/full), feature toggles, and customizable heightCodeEditor- Syntax-highlighted code editing with modes (html, css, js, php, json, xml, sql, markdown, yaml) and themes (monokai, github, twilight)
Selects:
Select- Dropdown with options arrayBelongsToSelect- Relation selector with hierarchical support (automatic parent-child tree building), query modifiers, nullableBelongsToManySelect- Multiple selection for many-to-many relations with automatic pivot syncCheckboxGroup- Group of checkboxesRadioGroup- Radio button group
Toggles:
Checkbox- Single checkboxToggle- Switch toggle
Specialized:
DateTimePicker- Date and time selectionColorPicker- Color pickerTags- Tag inputSlug- Auto-generate slug from another field
Files & Media:
File- Document uploads with MIME type filtering, filename strategies (original, transliterate, unique), path strategies (flat, dated, custom)Media- Advanced media library with:- Single/multiple files (with maxFiles limit)
- Drag & drop upload and reordering
- Image conversions (thumbnail, medium, large)
- Metadata (title, alt, caption, position, custom props)
- Collections for grouping
- Preset system: SingleImagePreset, GalleryPreset, DocumentsPreset, IconsPreset
- State-path based collection naming for nested fields
- Deferred actions for newly created models
- Automatic cleanup when deleting Fieldset items
Containers:
Fieldset- Repeatable field groups stored in JSON with:- Add/remove/reorder items (drag & drop)
- Collapsible/collapsed state
- Stable IDs for media fields
- Support for nested Media with deterministic collection names
- minItems/maxItems limits
- Custom add/delete button labels
- Head title/preview from field values
- Automatic media cleanup on item deletion
Layout:
Row- Bootstrap grid rowCol- Bootstrap grid column (1-12)Panel- Panel with headerTabs/Tab- Tabbed interfaceDiv- Container with conditional visibilityGroup- Element grouping
Column- Universal column with sorting, searching, formatting, alignment, width control, text wrapping, inline editing, styling (fontSize, bold, italic, color), and links (linkToEdit, linkUrl, linkAction)TextColumn- Text display (extends Column)BooleanColumn- Boolean indicator with custom labels, icons, colors, and inline toggle editingImageColumn- Image display with sizes, shapes (square, circle), lightbox, single/multiple modes, stack/grid layouts, and media library integration with conversion supportBadgeColumn- Badge/tag displayComputedColumn- Calculated valuesTemplateColumn- Custom Blade template
Row Actions:
EditAction- Navigate to edit pageEditInModalAction- Edit in modal without page reloadDeleteAction- Delete with confirmationRestoreAction- Restore soft-deleted records
Global Actions:
CreateInModalAction- Create in modal without page reload
Form Actions:
SaveFormAction- Save and returnSaveAndContinueFormAction- Save and continue editingCancelFormAction- Cancel and return
The Media field provides enterprise-level file management:
Media::make('gallery')
->multiple(true, maxFiles: 20)
->acceptImages()
->maxFileSize(5120) // KB
->columns(8) // Grid layout
->props('title', 'alt', 'caption', 'position')
->conversions([
'thumb' => ['width' => 150, 'height' => 150],
'medium' => ['width' => 800],
'large' => ['width' => 1920],
])
->pathStrategy('dated')
->pathPrefix('products')
// Or use a preset
Media::make('banner')->preset(SingleImagePreset::class)How it works:
- Files are stored in
ave_mediatable with metadata - State-path determines collection name (e.g.,
gallery→ collection) - Supports nested fields through meta keys
- Deferred actions handle new model creation
- Automatic cleanup when parent is deleted
Create repeatable structures with media support:
Fieldset::make('team_members')
->schema([
TextInput::make('name')->required(),
TextInput::make('position'),
Media::make('photo')->preset(SingleImagePreset::class),
Textarea::make('bio'),
])
->sortable()
->collapsible()
->minItems(1)
->maxItems(10)
->headTitle('name') // Use 'name' field as item title
->addButtonLabel('Add Team Member')Features:
- Stable item IDs for media correlation
- Drag & drop reordering
- Automatic media cleanup on item deletion
- Nested media uses deterministic collection names
- Template fields for dynamic addition
Build tree structures in dropdowns:
BelongsToSelect::make('parent_id')
->relationship('parent', 'title')
->hierarchical() // Requires parent_id and order columns
->nullable()
->where(fn($q) => $q->where('status', 'active'))Displays as:
Root Item
├─ Child Item
│ └─ Grandchild Item
└─ Another Child
Edit table cells directly:
Column::make('price')
->inline('text', ['field' => 'price'])
->inlineRules('required|numeric|min:0')
BooleanColumn::make('is_active')
->inlineToggle() // Click to toggleDefine granular permissions:
// In a seeder or service provider
$accessManager->registerPermissions('posts', [
'viewAny' => ['name' => 'View Posts List'],
'view' => ['name' => 'View Post Details'],
'create' => ['name' => 'Create Posts'],
'update' => ['name' => 'Edit Posts'],
'delete' => ['name' => 'Delete Posts'],
]);Check permissions:
$accessManager->allows($user, 'posts', 'create'); // booleanFeatures:
- Role-based with groups for organization
- Super role bypasses all checks
- Default roles for new users
- Bulk permission checks (optimized)
- Caching with configurable TTL
Intercept CRUD operations:
class PostResource extends BaseResource
{
protected function beforeCreate(array $data): array
{
$data['author_id'] = auth()->id();
return $data;
}
protected function afterCreate($model): void
{
// Trigger notifications, etc.
}
protected function beforeUpdate($model, array $data): array
{
$data['updated_by'] = auth()->id();
return $data;
}
}Available hooks: beforeCreate, afterCreate, beforeUpdate, afterUpdate, afterDelete
Customize in config/ave.php:
return [
'route_prefix' => 'admin',
'user_model' => \App\Models\User::class,
'users_table' => 'users',
'acl' => [
'enabled' => true,
'super_role' => 'admin',
'cache_ttl' => 300,
],
'pagination' => [
'default_per_page' => 25,
'per_page_options' => [10, 25, 50, 100],
],
];Publish assets for customization:
php artisan vendor:publish --tag=ave-views # Blade templates
php artisan vendor:publish --tag=ave-lang # Translations (en, ru)
php artisan vendor:publish --tag=ave-config # ConfigurationRun tests:
cd vendor/monstrex/ave
php ../../../vendor/bin/phpunitBuild assets:
npm install
npm run build # or npm run devMIT License. See LICENSE for details.
Developed by Monstrex.
Report issues at GitHub issue tracker.