Start Here
Configuration
Every config file these packages publish, what each key decides, and the defaults you get for saying nothing.
On this page
Wire works out of the box after installation. Publish config files only when you need to change defaults for notifications, date formats, uploads, table behavior, sortable behavior, or audit logging.
Publish Config Files
php artisan vendor:publish --tag=wire-core::configphp artisan vendor:publish --tag=wire-forms::configphp artisan vendor:publish --tag=wire-table::configphp artisan vendor:publish --tag=wire-sortable::configphp artisan vendor:publish --tag=wire-panels::configphp artisan vendor:publish --tag=wire-admin::configphp artisan vendor:publish --tag=wire-boost::config # One per installed modulephp artisan vendor:publish --tag=wire-module-users::configphp artisan vendor:publish --tag=wire-module-auth::configphp artisan vendor:publish --tag=wire-module-settings::configphp artisan vendor:publish --tag=wire-module-notifications::configphp artisan vendor:publish --tag=wire-module-audit::configphp artisan vendor:publish --tag=wire-module-media::configphp artisan vendor:publish --tag=wire-module-tenants::config
You only need the tags for packages you installed. Every module's own installer
(php artisan wire-module-users:install and friends) publishes its config for
you, so the lines above are for an application setting one up by hand.
Environment Variables
| Variable | Default | Used by |
|---|---|---|
WIRE_NAVIGATE |
true |
Links between this application's pages follow wire:navigate |
WIRE_NOTIFICATIONS_DRIVER |
session |
Core notifications |
WIRE_AUDIT_ENABLED |
true |
Core audit log |
WIRE_AUDIT_USER_MODEL |
App\Models\User |
Core audit log |
WIRE_FORMS_UPLOAD_DISK |
public |
Forms file upload |
WIRE_MOBILE_SHEET |
true |
Core mobile bottom-sheets |
WIRE_MOBILE_BREAKPOINT |
sm |
Core mobile sheet breakpoint |
WIRE_MOBILE_NATIVE |
false |
Browser-native selects and date/time inputs below the mobile breakpoint |
WIRE_MOBILE_TOUCH |
false |
Touch-built controls below the mobile breakpoint: wheels for dates and times, a full-height list for selects |
WIRE_TOURS_DRIVER |
session |
Where a signed-in user's tour progress and finished tours are kept |
WIRE_TOURS_GUEST_DRIVER |
session |
The same, for a guest |
WIRE_TOURS_POSTPONE |
3 |
How many times a tour's welcome block may be answered with "Later" before it stops asking |
WIRE_AUTH_CODE_LOGIN |
false |
Signing in with a mailed code, no password |
WIRE_AUTH_CODE_SECOND_FACTOR |
false |
A mailed code after a correct password |
WIRE_AUTH_CODE_VERIFY_EMAIL |
false |
Confirming an address by code |
WIRE_AUTH_CODE_RESET_PASSWORD |
false |
A reset mail that carries a code, not a link |
WIRE_USERS_PASSKEYS |
auto |
The passkey card on the profile page |
JavaScript Assets
Nothing needs configuring and nothing needs publishing: every package copies its
own pre-built bundles into public/vendor/<package> and serves them as static
files, cache-busted by file modification time. The one thing your app decides is
where they are emitted — put
@wireStackScripts
once in the layout <head> and every installed package's Alpine controllers are in
the initial document, which is what keeps them working across wire:navigate
(including the cached Back/Forward path). Pass a package name —
@wireStackScripts('wire-table') — to emit only one package's bundles.
php artisan vendor:publish --tag=laravel-assets --force does the same copy ahead
of time, which moves it off the first request after a deploy — useful, never
required. There is no config key either way.
Full explanation in Getting Started → JavaScript Assets.
Core
The wire-core config controls shared UI behavior.
return [ 'navigate' => env('WIRE_NAVIGATE', true), 'notifications' => [ 'default' => env('WIRE_NOTIFICATIONS_DRIVER', 'session'), ], 'icons' => [ 'default_set' => 'default', 'sets' => [ 'default' => \NyonCode\WireCore\Foundation\Icons\DefaultIconSet::class, // 'lucide' => App\Wire\Icons\LucideIconSet::class, // => "lucide:home" ], 'paths' => [ // resource_path('icons'), // logo.svg => "logo" // 'brand' => resource_path('icons/brand'), // mark.svg => "brand-mark" ], 'warn_missing' => env('WIRE_ICONS_WARN_MISSING', false), ], 'colors' => [ // Roles, not colours: every surface follows what they point at. // 'success' => 'emerald', 'danger' => 'red', 'warning' => 'amber', 'info' => 'cyan', ], // 'normal' or 'compact' — see Theming → Density. 'density' => env('WIRE_DENSITY', 'normal'), // 'rounded' or 'sharp' — see Theming → Shape. 'shape' => env('WIRE_SHAPE', 'rounded'), 'plugins' => [ // App\Wire\Plugins\ExamplePlugin::class, ], 'modals' => [ 'default_width' => 'md', 'slide_over_width' => 'md', 'close_on_click_away' => true, 'close_on_escape' => true, ],];
Navigation
navigate decides whether a link from one page of your application to another
is followed with Livewire's wire:navigate — the next page's body is fetched
and swapped in, so the layout, the sidebar's scroll and the assets already
loaded stay — or as an ordinary full page load. It is on by default.
It is one switch for every link the framework renders: the sidebar and the top
navigation, breadcrumbs, the record sub-navigation, actions with a url()
(buttons, menu rows, header actions, infolist actions), a table's
recordUrl(), url columns and ButtonColumn links, the list widget, the
notification bell, board cards, the user menu, and the redirect after a save,
a delete, a search result or a tour step.
Each link is decided in this order:
- Is it a page of this application? A path, or an absolute URL on the
application's own scheme, host and port. Another site, a
mailto:ortel:link and a bare#fragmentare never navigated, whatever the switch says — Livewire cannot swap in a page from another origin. - Does it open in a new tab? Then there is nothing to navigate.
- Does the link have its own say?
->navigate(false)keeps one link a full page load in a navigating application — a file download, or a page with a script of its own that must run on load.->navigate()makes one link navigate in an application that turned the switch off. - Otherwise the switch.
// config/wire-core.php'navigate' => env('WIRE_NAVIGATE', true),
Action::make('export') ->url(fn (Order $record) => route('orders.export', $record)) ->navigate(false), TextColumn::make('customer.name') ->actionUrl(fn (Order $record) => route('customers.show', $record->customer)) ->navigate(false), $table->recordUrl(fn (Order $record) => route('orders.show', $record), navigate: false);
Your own Blade asks the same question with @wireNavigate:
<a href="{{ $url }}" @wireNavigate($url)>Orders</a><a href="{{ $url }}" @wireNavigate($url, false)>Download</a>
It writes wire:navigate or nothing. <x-wire::menu-item> takes a
:navigate prop for the same choice.
Two places stay a full page load on purpose: the tenant switcher and the team switcher, because the whole layout belongs to the tenant or team being left.
Notifications
Built-in notification drivers are:
| Value | Driver |
|---|---|
session |
Stores notifications in session flash data |
livewire |
Dispatches Livewire browser events |
flasher |
Uses Flasher when your app has it installed |
null |
Disables delivery |
WIRE_NOTIFICATIONS_DRIVER=livewire
See Core Notifications for usage examples.
Icons
| Key | Purpose |
|---|---|
default_set |
Which sets key is the unprefixed base set (default 'default' = Heroicons). |
sets |
Registered icon sets. The default-set key is unprefixed; every other key is a required prefix, so its icons are addressed as prefix:name (e.g. lucide:home). Registering a non-default set without a string prefix throws. |
paths |
Folders of .svg files auto-registered as bare-named icons. A string key adds a dash-joined name prefix ('brand' => … → brand-mark). |
warn_missing |
Log a warning (and render the fallback) when an unknown icon name is used — handy for catching typos in development. |
'icons' => [ 'default_set' => 'default', 'sets' => [ 'default' => DefaultIconSet::class, // "pencil" (Heroicons, 20×20 fill) 'lucide' => LucideIconSet::class, // "lucide:home" (24×24 stroke) ], 'paths' => [ 'brand' => resource_path('icons/brand'), ], 'warn_missing' => env('WIRE_ICONS_WARN_MISSING', false),],
Sets are used together with deterministic, collision-free resolution. See
Core → Foundation → Icons for the full API, the
prefix:name model, custom sets, and accessibility.
Plugins
Register application or package plugins in the plugins array:
'plugins' => [ App\Wire\Plugins\TenantPlugin::class,],
Plugins that implement HasConfiguration can also read merged options from wire-core.plugins.config.{pluginId}:
'plugins' => [ App\Wire\Plugins\ExportPlugin::class, 'config' => [ 'export' => [ 'format' => 'xlsx', ], ],],
See Core Plugins for plugin classes, lifecycle, dependencies, macros, hooks, type registries, query pipes, and plugin configuration.
Modals
Modal width values are Tailwind-style size tokens such as sm, md, lg, xl, 2xl, or full.
'modals' => [ 'default_width' => 'lg', 'slide_over_width' => 'xl', 'close_on_click_away' => false, 'close_on_escape' => true,],
See Core Modals for modal actions and slide-overs.
Mobile
Floating panels (dropdowns, action-group menus, select/date/tag pickers, table filter & column-toggle panels) and the mobile modal variants present as a bottom sheet below a breakpoint. These are the global defaults — every component overrides them per instance.
'mobile' => [ // Present floating panels as a bottom sheet on mobile. false = classic // trigger-anchored floating panel everywhere. 'sheet' => env('WIRE_MOBILE_SHEET', true), // Breakpoint below which panels become a sheet: // 'sm' (< 640px, phones — default) // 'md' (< 768px, incl. small tablets) // 'lg' (< 1024px, incl. tablet portrait) 'breakpoint' => env('WIRE_MOBILE_BREAKPOINT', 'sm'), // Below the breakpoint, render the browser's own <select> / date / time // input instead of the custom control, wherever one exists. 'native' => env('WIRE_MOBILE_NATIVE', false), // Below the breakpoint, render a control made for a thumb: a wheel for // dates and times, a full-height list sheet for selects. Outranks 'native'. 'touch' => env('WIRE_MOBILE_TOUCH', false), ],
Per-component overrides (win over the global defaults):
// Sheet on/offSelect::make('role')->options([...])->sheetOnMobile(false); // force floatingSelect::make('country')->searchable()->sheetOnMobile(); // force sheet even when searchable$table->sheetOnMobile(false); // filter + column-toggle panels // Breakpoint (sm | md | lg)Select::make('role')->mobileBreakpoint('lg'); // sheet up to 1024px$table->mobileBreakpoint('md');ActionGroup::make([...])->mobileBreakpoint('md');Action::make('edit')->form([...])->slideOverOnMobile()->mobileBreakpoint('md'); // Browser-native control below the breakpoint, the custom one above itDateTimePicker::make('starts_at')->nativeOnMobile(); // the phone's own date wheel SelectFilter::make('status')->nativeOnMobile(false); // keep the combobox under 'native' => trueSelect::make('customer_id')->touchOnMobile(); // full-height touch list, search included
<x-wire::dropdown :sheet-on-mobile="false" :breakpoint="'md'">…</x-wire::dropdown>
Priority: per-component (->sheetOnMobile() / ->mobileBreakpoint()) > searchable-auto-floating > global
config. Searchable selects default to floating so the search box stays usable. Sheets add safe-area
padding, a drag-to-dismiss grabber and a focus trap automatically.
touch and native both swap the control itself rather than its panel, and
one precedence holds for every surface: ->native() (the browser's element
everywhere) > ->touchOnMobile() / touch > ->nativeOnMobile() / native.
touch renders a control built for a thumb — a wheel for a date or a time, a
bottom-sheet list with 48px rows, 16px text and a pinned search for a select —
and, unlike the browser's element, keeps every feature the field has: remote
search, creating an option, disabled days. See
touch list on phones and
touch wheel on phones.
native swaps the control itself rather than its panel. Every select and picker
that has a browser counterpart — Select, BelongsToSelect, DateTimePicker,
TimePicker, SelectFilter, TernaryFilter — renders both, and CSS at the same
breakpoint shows the browser's element below it and the custom control above it,
so no sheet is drawn for that field. A control that would lose something on the
way stays custom on every screen — a remote-search or create/edit-option select;
everything else goes native. A clock step becomes a native <select> of the
slots (beside a native date on a datetime), because iOS ignores a time input's
step, and the pickers' bounds are validated on the server, because a phone's
wheel ignores them. An explicit ->native(false) also opts a
field out of the global switch; ->nativeOnMobile() brings it back.
Tours
Where a tour keeps what it remembers per person: the tours
they finished or skipped, the step they reached in one they left halfway, and
the ones they put off. It is the preference store with its own default,
session rather than null, because a tour on a store that forgets would
interrupt the same person on every page load.
'tours' => [ 'postpone' => env('WIRE_TOURS_POSTPONE', 3), // "Later"s before a tour stops asking // 'preferences' => [ 'default' => env('WIRE_TOURS_DRIVER', 'session'), // signed-in users 'guest' => env('WIRE_TOURS_GUEST_DRIVER', 'session'), // guests 'drivers' => [ 'null' => NullPreferenceDriver::class, 'session' => SessionPreferenceDriver::class, 'database' => DatabasePreferenceDriver::class, // "once, ever" — needs the migration ], ],],
postpone is how many times a tour's welcome block
may be answered with "Later" before it records itself as seen and stops asking.
Each "Later" puts the tour down for that session; zero removes the button, and a
tour may override the number with ->postpone(int $times).
A tour's panel docks to the bottom of the screen below the mobile.breakpoint
above. See Tour → Remembering.
Forms
The wire-forms config controls date and time defaults, how money and phone numbers are
written, uploads, and the rich editor toolbar.
return [ 'date_format' => 'd.m.Y', 'time_format' => 'H:i', 'datetime_format' => 'd.m.Y H:i', 'first_day_of_week' => 1, 'money' => [ 'currency' => 'CZK', 'decimal_separator' => ',', 'thousands_separator' => ' ', ], 'phone' => [ 'countries' => [], 'default_country' => null, ], 'file_upload' => [ 'disk' => env('WIRE_FORMS_UPLOAD_DISK', 'public'), 'directory' => 'uploads', ], 'rich_editor' => [ 'toolbar' => [ 'bold', 'italic', 'underline', 'strike', 'h2', 'h3', 'bulletList', 'orderedList', 'link', 'blockquote', 'codeBlock', 'undo', 'redo', ], ],];
money is what MoneyInput writes an amount with when a field does
not say otherwise, and phone is PhoneInput's offer — an empty
countries list offers the whole dialling-code table, and the list is also the validation.
Use WIRE_FORMS_UPLOAD_DISK to move uploads to a different filesystem disk:
WIRE_FORMS_UPLOAD_DISK=s3
See Field Reference for field-specific options.
Table
The wire-table config controls default table behavior and inline text input behavior.
return [ 'defaults' => [ 'per_page' => 10, 'per_page_options' => [10, 25, 50, 100], 'searchable' => true, 'sortable' => true, 'hoverable' => true, 'striped' => false, ], 'text_input' => [ 'save_on_blur' => true, 'save_on_enter' => true, 'live_validation' => false, 'live_debounce' => 500, ], 'notification_driver' => null,];
notification_driver may be left as null; the table then uses the driver wire-core.notifications.default names. Set it to a driver class only when tables need a different one.
See Table Overview, Columns, and Exports.
Sortable
The wire-sortable config controls row ordering.
return [ 'order_column' => 'sort_order', 'sortablejs_cdn' => null, 'animation' => 150, 'user_model' => 'App\\Models\\User', 'user_key_type' => 'id', // 'uuid' / 'ulid' for non-integer user keys];
sortablejs_cdn defaults to null because SortableJS is compiled into the package's
own bundle — reordering needs no CDN request, so it works offline and under a strict
CSP. Set it only if your own code needs a global window.Sortable: the tag is then
loaded in addition to the bundle, never instead of it, and the drag controller uses
the bundled copy either way.
Set user_key_type to uuid or ulid (before running the column-order migration) when your user model uses a non-integer primary key.
Routes And Panels
Every package's routes are a group — panel (the resource pages), zones,
tenant-entry, and what the installed modules bring (tenants, auth-codes) —
and no provider registers any. The application places each one: with
Route::wire('panel') in routes/web.php, inside a group of its own, or with an
entry in wire-core.routes, which the framework's one route file registers.
// config/wire-core.php'routes' => [ 'defaults' => [], // under every entry 'groups' => [ 'admin' => ['uses' => 'panel', 'prefix' => 'admin', 'middleware' => ['web', 'auth', 'can:admin']], 'business' => ['uses' => 'panel', 'prefix' => 'business', 'only' => ['orders']], ], ],
An entry takes Laravel's group attributes (prefix, domain, middleware,
without_middleware, as, where, namespace, scope_bindings), can, the
group's own options, and routes for single routes. An entry named otherwise
than its group is that group's zone. Nothing is registered by default, the panel
starts from ['web', 'auth'] so an entry naming no middleware is guarded, and
groups from config are matched before routes/web.php.
wire-panels keeps only where two addresses lead — routes.zone_entry.primary
and .view for the address above the zones, routes.tenant_entry.view for a
tenant zone's bare address:
// config/wire-panels.php'routes' => [ 'zone_entry' => ['primary' => null, 'view' => null], 'tenant_entry' => ['view' => null],],
See Routing § Describing the groups in config for the whole table, and Zones.
Boost
The wire-boost config controls the AI tooling MCP server. The two code-executing tools are disabled by default.
return [ 'server' => [ 'name' => 'WireStack Boost', 'version' => '1.0.0', ], 'tools' => [ 'database_query' => env('WIRE_BOOST_DATABASE_QUERY', false), 'tinker' => env('WIRE_BOOST_TINKER', false), 'browser_logs' => env('WIRE_BOOST_BROWSER_LOGS', true), ], 'scan' => [ 'paths' => [app_path()], // where list-wire-components searches ], 'docs' => [ 'paths' => [], // extra Markdown directories for search-wire-docs ], 'browser_logs' => [ 'path' => storage_path('wire-boost/browser.log'), 'max_entries' => 50, ],];
Enable database-query and tinker only when you trust the agent connecting to the server. See
MCP Server & Tools.
Audit
Audit log settings live in config/wire-core.php:
'audit' => [ 'enabled' => env('WIRE_AUDIT_ENABLED', true), 'model' => \NyonCode\WireCore\Audit\AuditEntry::class, 'user_model' => env('WIRE_AUDIT_USER_MODEL', 'App\\Models\\User'), 'events' => null, 'exclude_columns' => [ 'password', 'remember_token', ], 'retention_days' => null,],
Set events to an array when you want to log only selected event types:
'events' => ['created', 'updated', 'deleted'],
See Audit Log for setup, model usage, and pruning.
Admin
The shell publishes one block, and it is the brand — everything else about it is markup you write (see The Admin Shell):
// config/wire-admin.php'brand' => [ 'name' => null, // falls back to config('app.name') 'logo' => null, // the wide menu — a path under public/, or a URL 'logo_dark' => null, // the dark-theme variant of it 'mark' => null, // the 64-pixel rail — falls back to the app's initial 'height' => 28, // the logo's rendered height, in pixels 'url' => null, // where the brand links to — defaults to the panel root],
Why there are two logo forms, and why the choice is made before the page paints: Branding And Theme.
The menu has one switch of its own, the filter over it — what it does and why it is not a second search: The Sidebar.
// config/wire-admin.php'layout' => [ 'navigation' => 'sidebar', // 'sidebar' | 'top' — see The Layout § The Menu As A Bar],'navigation' => [ 'filter' => 'auto', // 'auto' | 'always' | 'never' 'filter_threshold' => 12, // 'auto' shows it from this many rows up],
Modules
Each ready-made module publishes a config file of its own, and the keys are documented on the module's page rather than here — a module is a whole area, and its options only make sense beside the screens they change:
| File | What it configures | Page |
|---|---|---|
wire-module-users.php |
The user model, the resource, roles and teams, the profile screen | Users |
wire-module-auth.php |
Which screens the auth module registers, the routes it claims, and the four one-time-code flows | Auth |
wire-module-settings.php |
The settings table, its cache, and the groups the screen shows | Settings |
wire-module-notifications.php |
The bell, its panel, and the stored-notification table | Notifications |
wire-module-audit.php |
The audit screen over the trail wire-core records |
Audit |
wire-module-media.php |
Disks, conversions, accepted types, and the picker | Media |
wire-module-tenants.php |
The company model, who may register one, invitations, reserved slugs | Companies |
The Rest Of wire-core
Four keys in config/wire-core.php are declarations rather than settings, and
each one is covered where the thing it declares is explained:
| Key | What it holds | Page |
|---|---|---|
resources |
The resource classes an application registers | Resources → Registration |
dashboards |
The dashboard classes, the same way | Dashboards |
discover |
Directories to find resources, dashboards and wire-panels pages in — 'resources' => [namespace => directory], off until named |
Resources |
tenancy |
enabled and the column a tenant scope is applied on |
Authorization |
config/wire-table.php has one more: preferences picks the driver a table's
per-user column order, visibility and page size are stored in (null, session
or database, with guest naming the driver for a visitor who is not signed
in). See Advanced Features.