Inertia Start

Releases

v1.6.0

v1.6.0

This release adds email notifications to administrators when users are created or deleted, erases profile photos together with the rest of a deleted user's personal data, and enables HTTPS for Octane by default.

Added

  • Added admin email notifications for user creation and deletion, sent by the new App\Notifications\AdminUsersChanged notification to the comma-separated addresses in I_S_ADMIN_NOTIFICATIONS_RECIPIENTS. Notifications are disabled when no recipients are set.
  • Added I_S_ADMIN_NOTIFICATIONS_DELIVERY to choose how notifications are delivered:
    • immediate: sent synchronously during the request.
    • queued (default): sent by a queue worker.
    • digest: changes are grouped and sent in a single "User activity digest" email every day at 18:00 by the new scheduled App\Jobs\SendAdminDigest job. Pending changes are stored in the new admin_notification_digests table.
  • Added I_S_ADMIN_NOTIFICATIONS_QUEUE to send queued notifications and the digest job on a specific queue. Leave it empty to use the default queue.
  • Added the admin_notifications section to config/inertia-start.php, and the App\Services\AdminNotificationService that builds and dispatches the notifications.

Changed

  • OCTANE_HTTPS now defaults to true in config/octane.php and .env.example.
  • The profile photo of a deleted user is now removed by User::nullify(), together with the rest of their personal data, instead of by UserObserver::deleted(). The file is deleted only once the database transaction commits, since file deletion cannot be rolled back.
  • HasAvatar::deleteAvatar() now clears the whole avatar media collection, removing any leftover avatars and resetting the loaded media relation. It no longer calls save() on the model, avoiding a useless extra query.
  • Inertia Table header render functions now receive the TanStack header context (column, header, table) as props, matching how cell render functions already work.
  • To address vulnerabilities that have been discovered in certain npm dependencies, ESLint now uses typescript-eslint and vue-eslint-parser directly, with type-aware linting through projectService, instead of @vue/eslint-config-typescript. <script> blocks in Vue components must use lang="ts".

Fixed

  • The "magic link sent" toast no longer reappears after logging in with a magic link.

Removed

  • Removed the unused resources/views/mail/admin/user_added.blade.php mail template, replaced by resources/views/mail/admin/users_changed.blade.php.
  • Removed the unused is_admin property from the frontend User type in resources/js/types/auth.ts.

Dependencies

  • Updated minor and patch Composer and npm dependencies.

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. Run php artisan migrate to create the admin_notification_digests table.
  4. Rebuild frontend assets with npm run build or your normal frontend workflow.
  5. To receive admin notifications, set I_S_ADMIN_NOTIFICATIONS_RECIPIENTS to a comma-separated list of email addresses, and optionally I_S_ADMIN_NOTIFICATIONS_DELIVERY and I_S_ADMIN_NOTIFICATIONS_QUEUE. The default queued delivery requires a running queue worker, and digest delivery requires the scheduler and a queue worker.
  6. If you serve the application with Octane without HTTPS and OCTANE_HTTPS is not set in your .env file, set OCTANE_HTTPS=false.
  7. If your code relied on deleteAvatar() saving the model, call save() explicitly after it.
  8. If your code used is_admin from the frontend User type, or the mail.admin.user_added view, add them back in your application or migrate to the new notification.

v1.5.3

v1.5.3

Fixed

  • Login tracking now reads its database connection from LOGINS_DB_CONNECTION instead of SESSION_CONNECTION. When left unset, login records use the application's default database connection.
  • Billing tests now define their own locale fixtures and localized routes instead of depending on the ignored config/env/locales.json, preventing false failures in downstream applications with customized or disabled localization.

Dependencies

  • Updated minor and patch Composer and npm dependencies.

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. If config/logins.php is already published, replace 'database_connection' => env('SESSION_CONNECTION') with 'database_connection' => env('LOGINS_DB_CONNECTION').
  4. If login records should use a non-default relational database connection, set LOGINS_DB_CONNECTION to that connection's configured name.

v1.5.2

v1.5.2

Changed

  • The navigation links no longer set a prefetch prop on navigation links, leaving prefetching strategy up to the developer.

Removed

  • Removed the enable_link_prefetching option from config/inertia-start.php and the I_S_ENABLE_LINK_PREFETCHING environment variable from .env.example.
  • Removed the linkPrefetchingEnabled prop shared by App\Http\Middleware\HandleInertiaRequests, along with its entry in the SharedPageProps type.

Dependencies

  • Updated minor and patch Composer and npm dependencies.

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. Rebuild frontend assets with npm run build or your normal frontend workflow.
  4. Remove I_S_ENABLE_LINK_PREFETCHING from your .env file. It is no longer read.

v1.5.1

v1.5.1

This release improves initial page performance while preserving existing application behavior.

Improvements

  • Deferred CPU-intensive Thumbmark fingerprinting until after the page has loaded and the browser is idle.
  • Avoided unnecessary fingerprinting for authenticated visitors and guests with an existing context.
  • Removed standalone CSS chunks generated for alert and Inertia Table loading transitions, reducing render-blocking requests.
  • Preserved all existing alert and table animations using inline CSS variables and Tailwind utilities.

Upgrade Guide

Rebuild frontend assets with npm run build or your normal frontend workflow.

v1.5.0

v1.5.0

This release brings Laravel Octane to the starter kit, moves the product and subscription catalog out of configuration files and into the database, lets configured descriptions be translated per locale, and improves frontend loading performance, accessibility, and page metadata.

Highlights

  • Laravel Octane is installed, with FrankenPHP as the default server, and the application is prepared for long-lived workers.
  • In order to support Octane, products and subscriptions config are now stored in the database instead of config/env/products.php and config/env/subscriptions.php.
  • The products and plans saved in the billing editors are now the checkout allowlist, replacing the I_S_*_ALLOWED_PRICES and I_S_*_ALLOWED_DISCOUNTS environment variables.
  • Non-critical dependencies and layouts are lazy-loaded, and the primary font is preloaded.
  • Suspicious activity notifications are sent again, and three listeners no longer run twice per event.
  • Public pages carry meta descriptions, and navigation labels are translated instead of hard-coded in English.

Added

Laravel Octane

  • Added laravel/octane and published config/octane.php, with frankenphp as the default server. Added OCTANE_SERVER and OCTANE_HTTPS to .env.example.
  • Added App\Octane\Listeners\WarmRedisConnections, registered on WorkerStarting. It pings the Redis connections used by the session driver and the default cache store when a worker boots, so the first request does not pay the connection latency.
  • Enabled Spatie Permission's register_octane_reset_listener, so the permission registrar is reset between requests instead of holding a stale cache in the worker.
  • Added DevCommands::artisan('serve', 'server') to AppServiceProvider. Octane registers octane:start --watch as the server process of php artisan dev at vendor priority, which would download a FrankenPHP binary into the project root on first run; registering from userland outranks it and keeps local development on the built-in server.
  • Added frankenphp and /public/frankenphp-worker.php to .gitignore.
  • Added feature coverage in tests/Feature/DevCommandsTest.php and unit coverage in tests/Unit/Listeners/WarmRedisConnectionsTest.php.
  • Added the billing_product_configurations and billing_subscription_configurations tables, the App\Models\BillingProductConfiguration and App\Models\BillingSubscriptionConfiguration models, and the App\Concerns\StoresBillingConfiguration trait they share.

Localized product and subscription descriptions

  • A configured product, subscription group, or plan description may now be an array keyed by locale. Only the entry matching the current locale is used.
  • A plain string keeps working. When it is a Laravel translation key it still resolves in every locale; otherwise it is literal copy that only applies to the application's fallback locale.
  • Added App\Rules\LocalizedText, used to validate a description that is either a string or an array keyed by an available locale, with a 5,000 character limit per value.

Link prefetching

  • Added the enable_link_prefetching option to config/inertia-start.php, built from the new I_S_ENABLE_LINK_PREFETCHING environment variable and enabled by default. It is shared with the frontend as the linkPrefetchingEnabled prop.

Page titles and metadata

  • Added messages.meta.home, messages.meta.login, messages.meta.register, messages.meta.password_lost, messages.meta.terms_of_service, and messages.meta.privacy_policy in English, Spanish, and French.
  • The welcome, login, registration, registration completion, forgotten password, terms of service, privacy policy, and buy pages now set a meta description. The registration and forgotten password pages also gained their missing title.
  • MetaData now gives every tag it renders a head-key, so a page that sets its own description or Open Graph values replaces the shared ones instead of adding a second tag.
  • Added browser coverage asserting that each public page exposes a meta description.

Accessibility

  • Added the messages.nav_menu, messages.nav_to, messages.nav_to_item, messages.sub_nav, messages.current_nav, and messages.nav_none_selected translations in English, Spanish, and French, replacing hard-coded English labels in AppHeader and Breadcrumbs.
  • The mobile navigation trigger gained a screen-reader label, the breadcrumb group select is now wrapped in a BreadcrumbItem so it sits in the breadcrumb list, and the language switcher trigger gained an accessible name.

Other

  • Added pooled and direct host settings to the PostgreSQL connection in config/database.php, built from the new DB_POOLED and DB_DIRECT_HOST variables, for databases reached through a connection pooler.
  • Added tests/Feature/EventListenerRegistrationTest.php, asserting that each application listener is registered exactly once for the event it handles.
  • Added tests/Unit/frontendBundle.test.mjs and tests/Unit/persistentLayout.test.mjs, covering the lazy-loaded dependencies, the font preload, the date locale handling, and the persistent page layout.
  • Added browser coverage asserting that the language switcher updates the page without reloading the document.

Changed

Billing

  • BillingService now reads products and subscription groups from the database instead of config('inertia-start.products') and config('inertia-start.subscriptions').
  • The checkout allowlist is now derived from the saved configuration: the price identifiers and discounts of the configured products and plans are the only ones accepted at checkout. There is no longer a * allowlist, so a price or discount that is not configured is rejected with a 404.
  • The billing editors no longer write PHP files. Saving now stores rows and takes effect immediately, so the "configuration is cached" warning and the configurationIsCached page prop were removed, and billing.config_editor.saved no longer takes a :file placeholder.
  • The product editor now fails validation when a selected price cannot be resolved from the provider catalog, when the selected prices do not share one active currency, or when a selected discount cannot be found. It previously saved the product without its price snapshot.
  • Discount snapshots now respect the provider's own enabled_for_checkout flag, and the editors only offer discounts that are enabled for checkout.

Frontend performance

  • Every layout except AppLayout is now loaded with defineAsyncComponent, so a visit only downloads the shell it renders.
  • useContext() now imports @thumbmarkjs/thumbmarkjs dynamically after the component mounts, and stops before registering its listeners when the component was unmounted meanwhile.
  • configureEcho() was removed from the browser entry point, keeping the unused realtime client out of the initial bundle.
  • The Inter variable font file is now preloaded from app.blade.php.
  • The manual vue-vendor chunk group was removed from vite.config.ts, leaving dependency chunking to Rolldown.
  • English dates now use date-fns' built-in locale instead of downloading the en-US chunk, and switching back to English clears a previously selected locale.

Localization

  • The switcher reads its languages from the live page props, so it keeps pointing at the current page's URLs when it sits in a persistent layout.
  • resolveCurrentLocale() is now backed by a reactive reference, so computed route helpers re-resolve after a locale change. Its window.history.state fallback was removed, since the synchronized locale and the locale meta tag already cover client and server rendering.

Events

  • Application listeners now rely on Laravel's automatic listener discovery. The manual Event::subscribe() and Event::listen() calls were removed from AppServiceProvider, along with the subscribe() methods of the event subscribers.
  • MediaEventSubscriber::conversionCompleted() was renamed to handleConversionCompleted(), and SecurityEventSubscriber::notifyThrottle() to handleTooManyAttempts(), so discovery picks them up.

Other

  • The "or" separators on the login, registration, registration completion, and password confirmation pages now use the page background on small screens and the card background from the lg breakpoint, instead of always using the card background.
  • --muted-foreground is now #5d5d5d in the gray, neutral, slate, stone, and zinc secondary themes, improving contrast against light backgrounds.
  • The base test case now calls withoutVite(), so backend tests no longer depend on a stale or missing frontend build manifest.
  • Upgraded the bundled Inertia Table to 1.0.1, which adds accessible names to the per-page select and the page number input in the pagination.
  • Synced with laravel/vue-starter-kit up to commit d282e81: auth inputs and the passkey name field use the v-focus directive instead of the autofocus attribute, PasswordInput gained an autofocus prop, and the directive is now typed through Vue's GlobalDirectives interface.

Fixed

  • Fixed suspicious activity notifications never being sent. Its handler is now discovered automatically and covered by a test that dispatches the event.
  • Fixed three listeners running twice for every event. SessionEventSubscriber::handleSuccessfulLogin(), RecordBillingCheckout, and ProvisionGuestCheckoutCustomer were registered manually in AppServiceProvider on top of being discovered automatically, so each login, Stripe webhook, Paddle webhook, and completed Paddle transaction invoked them twice.
  • Fixed the focus directive being missing during server-side rendering.
  • Fixed the language switcher using stale page properties when rendered inside a persistent layout, which could send the visitor to the previous page's localized URL.
  • Fixed shared meta tags being duplicated when a page set its own description or Open Graph values.

Removed

  • Removed the I_S_STRIPE_ALLOWED_PRICES, I_S_STRIPE_ALLOWED_DISCOUNTS, I_S_PADDLE_ALLOWED_PRICES, and I_S_PADDLE_ALLOWED_DISCOUNTS environment variables and their config keys. The saved product and subscription selections are now the allowlist.
  • Removed the inertia-start.products and inertia-start.subscriptions config keys. config/env/products.php and config/env/subscriptions.php are no longer read.
  • Removed App\Listeners\PaddleEventListener, an unused example listener.
  • Removed the unused Helpers::displaySettings(), Helpers::dumpMemory(), Helpers::increasePhpLimits(), and Helpers::generateArrayHash() helpers.
  • Removed tests/Unit/navigation.test.mjs, whose assertions are now covered by the persistent layout and bundle tests.

Dependencies

  • Added laravel/octane 2.20.
  • Updated minor and patch Composer and npm dependencies.

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. Run php artisan migrate to create the billing_product_configurations and billing_subscription_configurations tables.
  4. Rebuild frontend assets with npm run build or your normal frontend workflow.
  5. Move your billing configuration into the database. config/env/products.php and config/env/subscriptions.php are no longer read, and both new tables start empty, so every /buy/{product} and /subscribe/{group}/{plan} page returns 404 until you re-create your catalog from /admin/billing/products and /admin/billing/subscriptions. Keep the old files at hand while you do it, then delete them.
  6. Remove I_S_STRIPE_ALLOWED_PRICES, I_S_STRIPE_ALLOWED_DISCOUNTS, I_S_PADDLE_ALLOWED_PRICES, and I_S_PADDLE_ALLOWED_DISCOUNTS from your .env file. Saved products and plans are now the allowlist, and a wildcard allowlist no longer exists: if your own code starts a checkout with a price or discount that is not part of a configured product or plan, configure it in the editors first, or that checkout will return 404.
  7. If your code reads config('inertia-start.products') or config('inertia-start.subscriptions'), read BillingProductConfiguration::configuration() or BillingSubscriptionConfiguration::configuration() instead. Both return the same array shape, ordered, with the row's slug merged in.
  8. If you customized resources/js/pages/admin/billing/ProductsConfig.vue or SubscriptionsConfig.vue, merge the new locales prop and the per-locale description fields, and drop the configurationIsCached prop. If you customized lang/*/billing.php, note that config_editor.saved no longer takes a :file placeholder and that config_editor.config_cached and config_editor.file_path were replaced by config_editor.database_storage.
  9. To translate a description, save one per locale from the editors, or store an array keyed by locale. A plain string that is not a translation key now only shows in the fallback locale, and other locales fall back to the generic buy page description.
  10. Installing Octane does not change how your application is served. php artisan dev deliberately stays on php artisan serve, and config/octane.php is already published. To serve with Octane, add OCTANE_SERVER and OCTANE_HTTPS to your .env file, install the server binary for your chosen driver, and start it with php artisan octane:start. Review any singleton, static property, or long-lived state in your own code before doing so, since workers keep the application booted between requests.
  11. If you registered your own listeners in a service provider, check them against the new EventListenerRegistrationTest: Laravel discovers any listener method named handle or starting with handle that type-hints its event, so a manual registration on top of that runs the listener twice.
  12. If you customized MediaEventSubscriber or SecurityEventSubscriber, rename their handlers to handleConversionCompleted() and handleTooManyAttempts(), or register them manually, otherwise they stop being called.
  13. If you used Helpers::displaySettings(), Helpers::dumpMemory(), Helpers::increasePhpLimits(), or Helpers::generateArrayHash(), copy the implementation you need into your own code.
  14. Link prefetching stays enabled. Set I_S_ENABLE_LINK_PREFETCHING=false to turn it off, and if you customized the navigation components, the breadcrumbs, or the user menu, replace their hard-coded prefetch attribute with :prefetch="$page.props.linkPrefetchingEnabled".
  15. If you customized PageLayout.vue, merge the asynchronous layout imports, and if you customized resources/js/ssr.ts, register FocusPlugin there too.
  16. If you customized resources/views/app.blade.php, add the font preload link, pointing it at your own font file if you replaced Inter.
  17. If you customized MetaData.vue, add a head-key to each tag, and set a description on your own pages so they do not inherit another page's.
  18. If you overrode --muted-foreground in a secondary theme, or relied on the previous value, review the new contrast.
  19. If you use PostgreSQL behind a connection pooler, set DB_POOLED=true and point DB_DIRECT_HOST at the direct host used for operations the pooler cannot serve.

v1.4.0

v1.4.0

This release adds complete OAuth authentication through Laravel Socialite, remembers the visitor's last login method, introduces reusable breadcrumb helpers, and strengthens the test and CI workflows.

Highlights

  • Users can log in and sign up with OAuth providers such as Google or GitHub, enabled with the new I_S_AUTH_PROVIDERS environment variable.
  • Provider logins link to existing accounts by verified email or create verified passwordless accounts, while keeping two-factor challenges and terms acceptance in place.
  • The login page shows a "Last used" badge on the method the visitor last logged in with.
  • Pages can replace or extend their layout's breadcrumb stack with static or reactive breadcrumb helpers.

Added

OAuth providers

  • Added the auth_providers option to config/inertia-start.php, built from the new I_S_AUTH_PROVIDERS environment variable. The value is a comma-separated list of Socialite driver names. Providers that are not listed return 404 on every OAuth route.
  • Added a google entry to config/services.php, reading GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET, with {APP_URL}/auth/provider/google/callback as the redirect URL. Added I_S_AUTH_PROVIDERS, GOOGLE_CLIENT_ID, and GOOGLE_CLIENT_SECRET to .env.example.
  • Added App\Http\Controllers\Auth\OAuthController and its routes:
    • GET auth/provider/{provider}/redirect (oauth.redirect) and GET auth/provider/{provider}/callback (oauth.callback) to log in with a provider.
    • GET and POST auth/provider/{provider}/challenge (oauth.challenge, oauth.challenge.store) to complete a two-factor challenge.
    • POST signup/provider/{provider}/redirect (oauth.registration.redirect) to start from the registration page, and GET and POST signup/provider/{provider} (oauth.registration, oauth.registration.store) to accept the terms before the account is created. These routes use the guest and feature:accounts middleware.
  • Added the oauth_accounts table, the App\Models\OAuthAccount model, and User::oauthAccounts(). A provider user ID belongs to a single user, and a user can link one account per provider.
  • When a provider redirects back to the application:
    • An account already linked to the provider user ID is logged in.
    • Otherwise, the provider email is matched against existing users, and the provider account is linked on the first login.
    • When no user matches, a passwordless account is created with a verified email and the name returned by the provider. The provider is recorded as the context's registration method.
    • Providers exposing an email_verified flag, such as Google, must report the email as verified before it is used to match or create an account.
    • Users with two-factor authentication enabled must enter an authentication app code on the new auth/OAuthChallenge page.
    • When I_S_ACCOUNT_MUST_ACCEPT_TERMS is enabled, new users are sent to the registration page, which shows the provider email as read-only and requires accepting the terms before the account is created.
  • Added provider buttons to the login, switch account, and registration pages.
  • Added the AuthProvider TypeScript interface and the auth.oauth.*, messages.last_used, messages.last_used_tooltip, messages.continue_with_provider, and messages.oauth_providers.github translations in English, Spanish, and French.

Last used login method

  • Added a last_login_method cookie, kept for one year and configured under last_login_method_cookie in config/inertia-start.php. It is set after logging in with a password, a magic link, a passkey, or an OAuth provider, including the automatic login at the end of registration.
  • Added AuthService::rememberLoginMethod() and AuthService::lastUsedLoginMethod(). The latter only returns a built-in method (password, magic_link, passkey) or an enabled provider, and is passed to the login and switch account pages as the lastUsedLoginMethod prop.
  • Added App\Http\Responses\PasskeyLoginResponse, bound to the passkeys package's PasskeyLoginResponse contract. It responds like the package's default response and also records the passkey login method.
  • Added the LastUsedBadge component, displayed with an explanatory tooltip on the matching login button. PasskeyVerify gained a lastUsed prop.

Breadcrumbs

  • Added setBreadcrumbItems() to replace a page's breadcrumb stack and addBreadcrumbItems() to extend the defaults supplied by its layout.
  • Breadcrumb helpers accept static items or reactive getters, and layout breadcrumb props accept either a replacement array or a resolver that receives the default stack.
  • AppLayout, AdminLayout, and AccountLayout now resolve breadcrumb props consistently; account pages can extend or replace the built-in account breadcrumb.

Tests

  • Added feature coverage in tests/Feature/Auth/OAuthTest.php.
  • Added frontend unit coverage for replacing, extending, resolving, and reactively updating breadcrumbs.
  • AuthenticationTest and RegistrationTest now assert the last used login method cookie after password, magic link, passkey, and post-registration logins.

Changed

  • Split the Composer test workflow into test:backend and test:browser. composer run test runs both, while composer run ci:check also runs ESLint, Prettier, vue-tsc, and the frontend unit tests.
  • Browser tests now use and clear a dedicated Vite hot file, preventing a stale development server marker from leaking into the test environment.
  • CI now installs Node development dependencies explicitly and runs application setup non-interactively.
  • Removed generated IDE Helper stubs from Composer, PHPStan, models, and CI. Larastan now infers model properties from migrations, while Laravel-aware editor support provides completion and navigation without generated project files.
  • Updated the bundled Inertia Start AI skill to describe the implemented OAuth flow and its security requirements.
  • TooltipContent now uses the secondary color for the background instead of the foreground color, and its arrow has a smaller radius. This applies to every tooltip in the application.
  • Added crawler directives for /auth/provider/ and /signup/provider/ to ask crawlers not to visit OAuth endpoints.
  • Synced with laravel/vue-starter-kit up to commit 4bd3e1d, and documented why Inertia Start intentionally does not adopt every upstream feature.

Fixed

  • Made avatar upload and account export tests explicitly enable the avatar feature, preventing environment-dependent failures.

Dependencies

  • Added laravel/socialite 5.31.
  • Removed barryvdh/laravel-ide-helper to avoid duplicate definitions. This package was previously valuable for completion and navigation, but modern Laravel-aware editor tooling and Larastan now provide those capabilities.
  • Updated Laravel Framework to 13.32, Laravel Boost to 2.9, Pest to 5.2, Playwright to 1.63, and other minor and patch Composer and npm dependencies.

Upgrade Guide

  1. Run composer install.

  2. Run npm install.

  3. Run php artisan migrate to create the oauth_accounts table.

  4. Rebuild frontend assets with npm run build or your normal frontend workflow.

  5. OAuth stays disabled while I_S_AUTH_PROVIDERS is empty. To enable Google, create an OAuth client in the Google Cloud console with {APP_URL}/auth/provider/google/callback as an authorized redirect URI, then add its credentials to your .env file:

    I_S_AUTH_PROVIDERS=google
    GOOGLE_CLIENT_ID=your-client-id
    GOOGLE_CLIENT_SECRET=your-client-secret
    
  6. To enable another Socialite provider, add its credentials to config/services.php with {APP_URL}/auth/provider/{provider}/callback as the redirect URL, add its driver name to I_S_AUTH_PROVIDERS, and add messages.oauth_providers.{provider} to each locale's messages.php. Emails from providers that do not expose an email_verified flag are trusted as returned, so only enable providers that return verified email addresses.

  7. If you customized resources/js/pages/auth/Login.vue, resources/js/pages/auth/RequestRegistration.vue, or resources/js/components/OAuthProviderButton.vue, merge the provider buttons and last used badges. OAuthProviderButton now expects an AuthProvider object in its provider prop instead of a string.

  8. If you bound your own implementation of Laravel\Passkeys\Contracts\PasskeyLoginResponse, merge it with App\Http\Responses\PasskeyLoginResponse and call AuthService::rememberLoginMethod(AuthService::LOGIN_METHOD_PASSKEY), so passkey logins keep being remembered.

  9. If you customized resources/js/components/ui/tooltip/TooltipContent.vue, or relied on the previous tooltip colors, review the new styles.

  10. If you customized AppLayout, AdminLayout, or AccountLayout, merge the new BreadcrumbItemsProp and resolveBreadcrumbItems() support. Existing static breadcrumb arrays remain supported.

  11. If you customized the Composer test scripts or GitHub Actions workflow, merge the split backend/browser commands, frontend unit test execution, explicit Node dependency installation, and non-interactive setup.

  12. If your editor relied on generated _ide_helper.php, _ide_helper_models.php, or .phpstorm.meta.php files, enable PhpStorm's bundled Laravel support, Laravel's official VS Code extension, or Laravel's official language server instead.

v1.3.0

v1.3.0

This release lets notification types be grouped on the account notification preferences page, and keeps each type's configuration in a single definition object.

Added

  • Added App\Services\NotificationTypeDefinition, a readonly object holding a notification type's default state and its optional group. NotificationType::definition() returns it for each case, and defaultEnabled() now reads from it.
  • Added NotificationType::group(), returning the slug of the group a notification type belongs to, or null when it stands on its own.
  • Added grouped rendering to /account/notifications. Notification types that declare a group are listed together under a parent checkbox reflecting the state of its children: checked when all of them are enabled, unchecked when none are, and indeterminate in between. Toggling the parent enables or disables every notification in the group.
  • Group titles and descriptions are read from pages.notifications.groups.{group}.title and pages.notifications.groups.{group}.description.

Changed

  • Each entry of the notification preferences page notifications prop now carries a group value: null, or an object with the group's slug, title, and description.

Dependencies

  • Updated minor and patch versions of Composer and npm dependencies.

Upgrade Guide

  1. Rebuild frontend assets with npm run build or your normal frontend workflow.
  2. If you added your own cases to App\Enums\NotificationType, move each case from the defaultEnabled() match into the definition() match and return new NotificationTypeDefinition(defaultEnabled: ...). defaultEnabled() no longer matches on cases itself, so a case left only there raises an unhandled match error.
  3. To group notification types, pass group: 'your_group' to their NotificationTypeDefinition and add pages.notifications.groups.your_group.title and .description to each locale's pages.php. Missing keys are rendered as the raw translation key.
  4. If you customized resources/js/pages/account/notifications/Notifications.vue, merge the grouped rendering, or at least the new group key on the NotificationType interface.

v1.2.2

v1.2.2

This release adds dark mode support to Markdown emails.

Added

  • Added dark mode styles to the Markdown mail layout, applied under prefers-color-scheme: dark.
  • Added public/inertia-start-logo-dark.png, a dark-mode variant of the application logo.
  • Added dark mode rules for clients that do not honor prefers-color-scheme.

Dependencies

  • Updated minor and patch versions of Composer and npm dependencies.

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. Add public/inertia-start-logo-dark.png. If you replaced the application logo with your own, add a dark variant under that name as well: the logo component embeds the file on every Markdown email, so a missing file breaks sending.
  4. Rebuild frontend assets with npm run build or your normal frontend workflow.
  5. If you customized resources/views/vendor/mail/html/layout.blade.php, logo.blade.php, or themes/default.css, merge the dark mode styles, the second logo image, and the email-body class on <body>. Without the class, Gmail's forced dark mode rules do not apply.
  6. Review your own email templates and custom mail CSS against a dark background, and check any hard-coded colors in them.

v1.2.1

v1.2.1

This patch prevents billing components from making browser-only API requests during server-side rendering.

Fixed

  • Fixed the Inertia SSR process terminating with XMLHttpRequest is not defined when Stripe or Paddle pricing cards loaded provider prices or discounts, or when Paddle billing components resolved an authenticated customer. These requests now start after the components mount in the browser.
  • Fixed the default Stripe and Paddle pricing-card checkout URLs accessing window.location during server-side rendering.

Dependencies

  • Upgraded stripe/stripe-php from v20 to v21 now that Cashier 16.8 supports it
  • Updated minor versions of Composer and npm dependencies

Upgrade Guide

Rebuild frontend assets with npm run build or your normal frontend workflow.

v1.2.0

v1.2.0

Highlights

  • Visitor contexts now preserve the first landing URL, external referrer, and UTM campaign parameters that brought the visitor to the application.
  • The first successful Stripe or Paddle product or subscription purchase now sets converted_at on the user's contexts.
  • Admin user pages now show first-party lifecycle, acquisition, and conversion tracking, and the users list can display and sort by conversion date.
  • Stripe plan switching works again with Dahlia flexible billing and pending_if_incomplete updates.
  • Markdown emails now embed a PNG application logo instead of relying on an inline SVG.

Added

Acquisition and conversion attribution

  • Added utm_source, utm_medium, utm_campaign, utm_term, utm_content, landing_url, and referrer columns to the contexts table. The source and campaign columns are indexed.
  • Added first-touch attribution recording to the web and API context middleware. The first landing's acquisition record is preserved instead of being overwritten or combined with data from a later visit; internal referrers are ignored, URLs are limited to 2,000 characters, and UTM values are limited to 255 characters.
  • The public POST /api/context endpoint can now receive an optional url and referrer in its JSON body, allowing an approved first-party website to attach its own landing page and acquisition source to the shared visitor context. Malformed or oversized URLs are discarded.
  • Successful Stripe Checkout sessions and Paddle transactions now record their checkout or billing timestamp as the context's first converted_at value for both configured products and subscriptions. Later purchases do not overwrite the original conversion time or update context activity.
  • Added feature coverage for first-touch attribution, cross-site attribution, invalid attribution input, product and subscription conversions through both payment providers, admin tracking details, and conversion-date sorting.

Admin analytics

  • Added an analytics and tracking section to each admin user page. It shows the context ID, registration method, first and last activity, registration and conversion dates, UTM source, medium, campaign, term and content, landing page, and external referrer. The existing Umami session lookup remains available in the same section when Umami is configured.
  • Added a sortable Customer since column to the admin users list, backed by the context's converted_at value. Contexts without a recorded conversion display an empty value.

Changed

Billing

  • PaddleCheckoutButton now emits a success event with the configured success URL when Paddle reports checkout.completed.
  • The account billing page now closes the subscription dialogs after a successful plan switch, cancellation, or inline Paddle checkout.

Fixed

  • Fixed Stripe plan changes failing after the Dahlia upgrade. Pending subscription updates no longer send cancel_at or cancel_at_period_end, which Stripe does not support with payment_behavior=pending_if_incomplete; cancellation cleanup remains in the dedicated resume flow.
  • Fixed Stripe subscription lifecycle webhooks returning 500 errors because the custom Cashier customer model inferred a nonexistent stripe_user_id column. Subscription creation, updates, and cancellations now use the existing user_id foreign key and remain synchronized locally.
  • Fixed subscription dialogs remaining open after successful billing actions.
  • Fixed application logos being missing or inconsistently rendered in Markdown emails by replacing the inline SVG with public/inertia-start-logo.png and embedding it as an inline PNG attachment. The shared Markdown mail and notification templates now pass the underlying mail message through to the logo component.

Removed

  • Fresh installations no longer create the unused lifetime_value and cumulative_spend context columns, and the corresponding model casts and frontend type fields were removed. Existing databases keep these harmless columns unless an application removes them separately.

Dependencies

  • Composer and npm dependency lockfiles were updated.

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. Run php artisan migrate to add the attribution columns to contexts.
  4. Rebuild frontend assets with npm run build or your normal frontend workflow.
  5. If another first-party website calls POST /api/context, send its current absolute URL as url and, when available, the previous page as referrer to record acquisition data. Existing calls that only send X-Guest continue to work.
  6. If you customized the Markdown mail templates or logo component, merge the updated mailMessage prop forwarding and add public/inertia-start-logo.png so the logo can be embedded in outgoing messages.