A Resource maps an Eloquent model to a set of auto-generated CRUD pages: list, create, edit, and view.
php artisan monorail:make-resource {name} [--model=]Generates app/Monorail/Resources/{Name}Resource.php bound to
App\Models\{Name} by default. The stub includes an id column and
searchable(['id']) to start from.
# Assumes App\Models\Post
php artisan monorail:make-resource Post
# Custom model namespace
php artisan monorail:make-resource Invoice --model='Domain\Billing\Invoice'
# Name already has the Resource suffix
php artisan monorail:make-resource PostResourceuse App\Models\Post;
use Monorail\Resources\Resource;
final class PostResource extends Resource
{
protected static string $model = Post::class;
protected static ?string $slug = 'posts'; // URL slug
protected static ?string $navigationIcon = 'file'; // Sidebar icon
protected static ?string $navigationGroup = 'Content';
protected static ?int $navigationSort = 10;
public static function table(Table $table): Table { /* ... */ }
public static function form(Form $form): Form { /* ... */ }
public static function widgets(): array { return []; }
public static function relationManagers(): array { return []; }
}Only $model. Everything else has a sensible default.
| Member | Required | Default |
|---|---|---|
$model |
✅ | — |
$slug |
❌ | Kebab-case plural of the class name. |
$navigationIcon |
❌ | Generic icon. |
$navigationGroup |
❌ | Ungrouped (shown at root of sidebar). |
$navigationSort |
❌ | Alphabetical within group. |
table() |
❌ | Empty table — you'll want this for the list page. |
form() |
❌ | No form → no create/edit pages, New button is hidden. |
widgets() |
❌ | No widgets on resource pages. |
relationManagers() |
❌ | No relation managers on edit/view. |
globalSearchColumns() |
❌ | [] → resource is opted out of global search. |
Two options — both optional, additive, and composable.
1. Auto-discover from a directory:
$panel->discoverResources(
in: app_path('Monorail/Resources'),
for: 'App\\Monorail\\Resources',
);2. Explicitly list the resource classes:
$panel->resources([
App\Monorail\Resources\UserResource::class,
App\Monorail\Resources\PostResource::class,
]);You can use both on the same panel — useful when most resources live in a discoverable directory but a few live elsewhere.
Every resource gets four pages automatically:
| Route | Page | Policy checked |
|---|---|---|
GET /{slug} |
List | viewAny |
GET /{slug}/create |
Create | create |
GET /{slug}/{record} |
View | view |
GET /{slug}/{record}/edit |
Edit | update |
Pages are only rendered if the policy allows. If viewAny is denied, the
resource is hidden from the sidebar entirely.
Drop a page class into app/Monorail/Resources/{Resource}/Pages/ and extend
ResourcePage. Monorail discovers and registers it automatically.
namespace App\Monorail\Resources\PostResource\Pages;
use Monorail\Pages\ResourcePage;
final class PublishQueue extends ResourcePage
{
protected static ?string $title = 'Publish queue';
protected static ?string $slug = 'publish-queue';
}Manage related records (HasMany, BelongsToMany, etc.) on the edit/view page:
public static function relationManagers(): array
{
return [
CommentsRelationManager::class,
];
}See also: