⌘K

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::config
php artisan vendor:publish --tag=wire-forms::config
php artisan vendor:publish --tag=wire-table::config
php artisan vendor:publish --tag=wire-sortable::config
php artisan vendor:publish --tag=wire-panels::config
php artisan vendor:publish --tag=wire-admin::config
php artisan vendor:publish --tag=wire-boost::config
 
# One per installed module
php artisan vendor:publish --tag=wire-module-users::config
php artisan vendor:publish --tag=wire-module-auth::config
php artisan vendor:publish --tag=wire-module-settings::config
php artisan vendor:publish --tag=wire-module-notifications::config
php artisan vendor:publish --tag=wire-module-audit::config
php artisan vendor:publish --tag=wire-module-media::config
php 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,
],
];

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:

  1. 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: or tel: link and a bare #fragment are never navigated, whatever the switch says — Livewire cannot swap in a page from another origin.
  2. Does it open in a new tab? Then there is nothing to navigate.
  3. 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.
  4. 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/off
Select::make('role')->options([...])->sheetOnMobile(false); // force floating
Select::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 it
DateTimePicker::make('starts_at')->nativeOnMobile(); // the phone's own date wheel
SelectFilter::make('status')->nativeOnMobile(false); // keep the combobox under 'native' => true
Select::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.

See Sortable Installation.

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.