Skip to content

Framework Defaults

The starter configures several opinionated behaviors across its service providers and bootstrap that differ from stock Laravel. These are active, running defaults that affect how you write code in this application. Understanding them helps avoid surprises.

Configured in EloquentServiceProvider. These affect every model in the application.

Model::unguard();

Laravel default: Models are guarded, you must declare $fillable or $guarded to allow mass assignment.

Starter behavior: All mass assignment is allowed on all models. $fillable and $guarded are ignored.

Rationale: User input MUST pass through Laravel’s form request validation before reaching models. The validator only returns known validated fields, solving the problem $fillable was designed for, without the maintenance burden of keeping arrays in sync with your schema.

Model::preventLazyLoading(! App::isProduction());

Laravel default: Lazy loading is allowed everywhere, silently introducing N+1 query problems.

Starter behavior: A two-tier approach:

EnvironmentBehavior
Local with debug modeThrows an exception, you see the N+1 immediately
Non-production without debugThrows an exception, caught during testing and CI
ProductionSilently reports to your exception handler, never breaks for users

If a lazy loading violation reaches production, it’s reported to Sentry/logs rather than showing an error page.

Model::preventAccessingMissingAttributes(! App::isProduction());

Laravel default: Accessing a non-existent attribute silently returns null.

Starter behavior: Throws in non-production, falls back to null in production.

This catches typos during development (e.g., $user->frist_name instead of $user->first_name) that would otherwise silently produce null.

Relation::morphMap(array_merge(
// Model::MORPH_TYPE => Model::class,
));

Laravel default: Polymorphic relationships store fully-qualified class names in *_type database columns.

Starter behavior: The morph map is scaffolded but empty. When you add polymorphic relationships, register aliases here so the database stores stable strings (e.g., user) instead of class paths like App\Domains\User\Models\User.

This prevents renaming/moving a model from breaking existing polymorphic references, and keeps implementation details out of the database. The recommended pattern is to define a MORPH_TYPE_MAP constant on each model’s base class and merge it here.


Configured in AppServiceProvider and EagerLoadEloquentUserProvider.

app/Providers/EagerLoadEloquentUserProvider.php
$this->withQuery(function (Builder $query) {
$query->with(['roles.role_type', 'roles.permissions']);
});

A custom auth user provider (EagerLoadEloquentUserProvider) replaces Laravel’s default EloquentUserProvider. It eager-loads the roles, roles.role_type, and roles.permissions relationships on every authenticated request, preventing N+1 queries when checking authorization.

If you need additional relationships available on the authenticated user, add them to the withQuery callback in this class.

app/Providers/AppServiceProvider.php
Gate::before(static function (User $user): ?true {
return $user->hasPermissionTo(SystemPermission::ManageAll) ? true : null;
});

Users with the ManageAll permission bypass all Gate and Policy checks. Every $this->authorize(), @can, and Gate::allows() call returns true for them. This is the mechanism behind super admin access.


Configured in AppServiceProvider and bootstrap/app.php.

if (! App::environment(['ci', 'testing'])) {
URL::forceScheme('https');
}

All generated URLs use HTTPS. CI and testing environments are excluded so tests don’t require SSL certificates.

DB::prohibitDestructiveCommands(App::isProduction());

Commands like migrate:fresh, migrate:refresh, db:wipe, and db:seed --force are blocked in production. This prevents accidentally destroying a production database.

if (App::environment(['ci', 'testing'])) {
Http::preventStrayRequests();
}

In CI and testing environments, any outbound HTTP request made via Laravel’s Http client that hasn’t been explicitly faked will throw an exception. This ensures your test suite doesn’t accidentally make real HTTP calls to external services, verifying that all network dependencies are properly mocked. If you see a stray request error, you need to add Http::fake() for that endpoint.

if (App::environment(['local', 'ci', 'testing'])) {
RequestException::dontTruncate();
}

In local, CI, and testing environments, HTTP client exceptions include the full response body instead of truncating it. This makes debugging failed API calls significantly easier.

Paginator::useBootstrapFive();

Laravel’s paginator renders Bootstrap 5 markup instead of the default Tailwind CSS. This applies to non-Filament views that use {{ $items->links() }}.


Configured in FilamentServiceProvider. These affect the entire admin panel.

All DateTimePicker, TextColumn, and TextEntry components that display datetime values automatically use the authenticated user’s timezone. This means timestamps are displayed in each user’s local time throughout the admin panel. No manual ->timezone() calls are needed.

SettingValue
Default pagination25 per page
Pagination options10, 25, 50, 100
Deferred filtersDisabled (filters apply immediately)
DateTime formatFrom config('platform.datetime_display_format')

All Select and SelectFilter components render as Filament’s custom searchable select instead of the browser’s native <select> element. This provides a consistent, searchable dropdown experience across the admin panel.

All Filament export actions default to CSV format and write to the s3 disk.

An impersonation warning banner is injected at the top of the page body when a user is being impersonated, ensuring administrators always know when they’re acting as another user.


Configured in bootstrap/app.php.

API routes return errors as structured RFC 9457 Problem Details JSON instead of Laravel’s default error format. This is handled by ProblemDetailsRenderer and applies to all api/* routes and requests that send Accept: application/json.

When a database connection times out (common with scale-to-zero RDS in non-production), the application renders a custom “database paused” error page instead of a generic 500. In non-production environments, these timeout errors are also suppressed from error reporting to reduce noise during RDS wake-up periods.

->withEvents(discover: [
__DIR__ . '/../app/Domains/*/Listeners',
])

Laravel’s event auto-discovery is scoped to app/Domains/*/Listeners. Listener classes in these directories are automatically registered without needing an EventServiceProvider. The framework infers the event from the handle() method’s type-hint.