inertia start

Releases

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.

v1.1.1

v1.1.1

Changed

  • Sync with laravel/laravel v13.10.1
  • Sync with laravel/vue-starter-kit up to commit 11a7368
  • Composer and npm dependencies were updated

Upgrade Guide

  1. Run composer install.
  2. Run npm install.
  3. Rebuild frontend assets with npm run build or your normal frontend workflow.

v1.1.0

v1.1.0

This release opens visitor contexts to your other first-party websites. A new public endpoint lets your other sites resolve the same anonymous context as the application.

Highlights

  • New public POST /api/context endpoint returning the visitor's context ID.
  • New I_S_CONTEXT_ALLOWED_ORIGINS environment variable listing the other first-party origins allowed to use it.
  • Browser requests coming from an origin that is not allowed are rejected.

Added

  • Added App\Http\Controllers\Api\ContextController, registered as POST /api/context. It runs the API SetContext middleware, is rate-limited to 60 requests per minute, and returns the resolved context as {"contextId": ...} with a no-store Cache-Control header. A request that resolves no context returns a 422.
  • Added the context_allowed_origins option to config/inertia-start.php, built from the new I_S_CONTEXT_ALLOWED_ORIGINS environment variable. The value is a comma-separated list of origins; entries are trimmed, empty ones are dropped, and duplicates are removed. The application's own origin is always accepted and does not need to be listed.
  • Added I_S_CONTEXT_ALLOWED_ORIGINS to .env.example.
  • Added feature coverage for the new endpoint in tests/Feature/GuestContextTest.php: resolving a context from an allowed origin, the browser preflight request, the missing fingerprint and unknown origin rejections, and an existing fingerprint being reused instead of creating a second context.

Changed

  • App\Http\Middleware\Api\SetContext now checks the Origin header of requests carrying an X-Guest fingerprint. A request whose origin is neither the application's own origin nor one of context_allowed_origins is rejected with a 403. Requests sent without an Origin header, such as server-to-server calls, are unaffected.
  • The middleware now ignores an empty X-Guest header. Previously the header only had to be present, so an empty value produced a context keyed on the client IP address alone.

Upgrade Guide

  1. No migration and no configuration change are required. Without I_S_CONTEXT_ALLOWED_ORIGINS, the new endpoint only answers browser requests coming from the application's own origin.

  2. To share contexts with another first-party website, add its origin to I_S_CONTEXT_ALLOWED_ORIGINS in your .env file:

    I_S_CONTEXT_ALLOWED_ORIGINS=https://www.example.com,https://docs.example.com
    

    Origins are matched exactly: scheme and host, plus the port when one is used, with no path and no trailing slash.

  3. That website then sends its Thumbmark fingerprint to POST /api/context in the X-Guest header and reuses the returned context ID, for instance to identify its Umami session with context:{contextId}. Keep @thumbmarkjs/thumbmarkjs at the same version on both sides, since two versions can produce two different fingerprints for the same browser.

  4. If you do not want to expose the endpoint at all, remove the context route from routes/api.php.

v1.0.1

v1.0.1

This patch release fixes the sidebar trigger icon on mobile.

Fixed

  • Fixed the mobile sidebar trigger showing the wrong icon. SidebarTrigger selected its icon from the isMobile media query, which resolves to false during server-side rendering and on the first client render, so the server markup and the hydrated markup disagreed and phones showed a panel icon instead of the menu icon on initial load. The icons are now always rendered and switched with the Tailwind md responsive classes, which match the (max-width: 767px) breakpoint used by SidebarProvider, so the markup is identical on the server and on the client.

Upgrade Guide

  1. Rebuild frontend assets with npm run build or your normal frontend workflow.
  2. If you customized resources/js/components/ui/sidebar/SidebarTrigger.vue, apply the same change: render Menu with md:hidden and PanelLeftOpen / PanelLeftClose with hidden md:block, instead of branching on isMobile.

v1.0.0

v1.0.0

First stable release of Inertia Start ๐ŸŽ‰

v0.15.0

v0.15.0

This release moves JavaScript unit tests from Node's native test runner to Vitest, so frontend tests run through Vite's module resolution and can import application code that uses the default Laravel @ alias.

Highlights

  • JavaScript unit tests now run with Vitest.
  • Tests can import frontend modules that use Vite aliases such as @/lib/... without a custom Node loader.

Added

  • Added Vitest as a development dependency.

Changed

  • npm run test now runs vitest run tests/**/*.test.mjs instead of node --test tests/**/*.test.mjs.
  • Existing JavaScript unit tests now import test and afterEach from vitest instead of node:test.

Upgrade Guide

  1. Run npm install.
  2. If you have custom JavaScript tests that import lifecycle or test helpers from node:test, update those imports to vitest. For example, replace import test from 'node:test' with import { test } from 'vitest', and replace import { afterEach, test } from 'node:test' with import { afterEach, test } from 'vitest'.
  3. Keep using npm run test as before.