Appearance
Nova Resource Conventions
Class Structure
- Resources should be final by default:
php
/** @extends \App\Nova\Resources\Resource<\App\Modules\YourModule\Models\YourModel> */
final class YourResource extends Resource
{
}- Always use PHPDoc with template annotation to specify the model type:
php
/** @extends \App\Nova\Resources\Resource<\App\Modules\Course\Models\Course> */Organization
Resources are organized by module/domain directory:
app/Course/Nova/- Course-related resourcesapp/Member/Nova/- Member-related resourcesapp/Payment/Nova/- Payment-related resources- etc.
Common Patterns
Resources extend the base
\App\Nova\Resources\ResourceclassResources are placed in a namespace matching their module
Resources are named after their model without the "Model" suffix
Every field must have a help text using translation keys:
phpText::make('Title', 'title') ->help(__('nova-your-resource.title')), BelongsTo::make('Kitchen', 'kitchen') ->help(__('nova-your-resource.kitchen')),
Resource Components
Each resource typically includes:
- Fields definition:
php
public function fields(NovaRequest $request): array
{
return [
// Fields here
];
}- Custom field types from
App\Nova\Fields:
php
use App\Nova\Fields\DateTime;use App\Nova\Fields\Money;
public function fields(NovaRequest $request): array
{
return [
DateTime::make('Created At'),
Money::make('Amount'),
];
}- Filters in a dedicated directory per module:
php
public function filters(NovaRequest $request): array
{
return [
new Filters\YourFilter,
];
}- Actions in a dedicated directory per module:
php
public function actions(NovaRequest $request): array
{
return [
new Actions\YourAction,
];
}Best Practices
- Keep resources focused on their primary responsibility
- Use custom fields for consistent display of specific data types
- Group related functionality in dedicated directories (Actions, Filters, etc.)
- Follow Laravel Nova's conventions for method names and structure
- Use type hints and docblocks consistently
- Place business logic in the model or dedicated services, not in the resource
- Always provide help text for every field using translation keys (to make editing help text friendly for non-technical colleagues)
Examples
Basic Resource
php
namespace App\Course\Nova;
use App\Nova\Resources\Resource;use Laravel\Nova\Http\Requests\NovaRequest;
/** @extends \App\Nova\Resources\Resource<\App\Modules\Course\Models\Course> */
final class Course extends Resource
{
public static $model = \App\Modules\Course\Models\Course::class;
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable()->help(__('nova-courses.help.fields.id')),
Text::make('Title')->help(__('nova-courses.help.fields.title')),
DateTime::make('Created At')->help(__('nova-courses.help.fields.created_at')),
];
}
}Resource with Custom Components
php
namespace App\Nova\Payment;
use App\Nova\Resources\Resource;use Laravel\Nova\Http\Requests\NovaRequest;
/** @extends \App\Nova\Resources\Resource<\App\Modules\Payment\Models\Transaction> */
final class Transaction extends Resource
{
public static $model = \App\Modules\Payment\Models\Transaction::class;
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable()->help(__('nova-transaction.id')),
Money::make('Amount', 'amount')->help(__('nova-transaction.amount')),
BelongsTo::make('Order', 'order')->help(__('nova-transaction.order')),
];
}
public function filters(NovaRequest $request): array
{
return [
new \App\Modules\Payment\Nova\Filters\TransactionState(),
new \App\Modules\Payment\Nova\Filters\TransactionCurrencyFilter(),
];
}
public function actions(NovaRequest $request): array
{
return [
new \App\Modules\Payment\Nova\Actions\MarkOrderAsPaidAction(),
new \App\Modules\Payment\Nova\Actions\OpenTransactionOnGatewayDashboardAction(),
];
}
}