--- permalink: /inertia/ title: Inertia description: Discover how to integrate flash notifications into your Inertia.js application with PHPFlasher. Follow this guide to set up the library and enhance your user interface with dynamic messages. ---
Installation | Usage | Examples | Frontend | Advanced

PHPFlasher works seamlessly with Inertia.js, providing a smooth way to display flash notifications in your single-page applications.

Required

#### PHP Version

v8.2 or higher
Required

#### Laravel Version

v11.0 or higher
Required

#### Inertia.js

v1.0 or higher

#### Using older versions?

If you need to use PHP < v8.2, Laravel < v11.0, or an older Inertia.js version, use PHPFlasher v1 instead. Check out the v1 documentation here .

## Installation

To use PHPFlasher with Inertia.js, you'll need to install both the PHP package and JavaScript package.

### 1. Install the PHP Package

Terminal
PHP Installation

    composer require php-flasher/flasher-laravel

### 2. Add JavaScript Package

Add `@flasher/flasher` to your `package.json`:

package.json

    {
        "dependencies": {
            "@flasher/flasher": "file:vendor/php-flasher/flasher/Resources"
        }
    }

Then, install the JavaScript dependencies:

Terminal
JS Installation

    npm install --force

#### Why the --force flag?

The `--force` flag is required because we're installing a local package from the filesystem rather than from npm. This is the recommended approach for PHPFlasher to ensure version compatibility.

## Usage

To use PHPFlasher with Inertia.js, you need to:

### 1. Modify Your Middleware

Update your `HandleInertiaRequests` middleware to share flash notifications:

app/Http/Middleware/HandleInertiaRequests.php

    <?php

    namespace App\Http\Middleware;

    use Illuminate\Http\Request;
    use Inertia\Middleware;

    class HandleInertiaRequests extends Middleware
    {
        /**
         * Defines the props that are shared by default.
         *
         * @see https://inertiajs.com/shared-data
         */
        public function share(Request $request): array
        {
            return array_merge(parent::share($request), [
                // Add flash messages to Inertia shared data
                'messages' => flash()->render('array'),

                // You can include other shared data here
                'auth' => [
                    'user' => $request->user(),
                ],
            ]);
        }
    }

#### Pro Tip: Multiple Ways to Share Flash Messages

The `render('array')` method converts flash messages to a format compatible with JavaScript. You can also use `render('json')` and parse it in your frontend code.

### 2. Set Up Your Frontend

Add the code to render notifications in your layout component (example shown with Vue.js):

resources/js/Shared/Layout.vue

    <script>
    import flasher from "@flasher/flasher";

    export default {
      props: {
        messages: Object,
      },
      watch: {
        messages(value) {
          if (value) {
            flasher.render(value);
          }
        }
      },
      mounted() {
        // Display messages on initial load
        if (this.messages) {
          flasher.render(this.messages);
        }
      }
    }
    </script>

    <template>
      <div>
        <header class="bg-white shadow">
          <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
            <!-- Your navigation -->
            <h1 class="text-xl font-bold text-gray-900">My App</h1>
          </div>
        </header>

        <main>
          <div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
            <slot />
          </div>
        </main>

        <footer class="bg-white border-t border-gray-200 mt-auto">
          <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
            <p class="text-center text-gray-500 text-sm">
              &copy; 2025 My Application
            </p>
          </div>
        </footer>
      </div>
    </template>

### 3. Creating Flash Messages

Now you can create flash notifications in your controllers:

app/Http/Controllers/UsersController.php

    <?php

    namespace App\Http\Controllers;

    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Redirect;
    use Inertia\Inertia;

    class UsersController extends Controller
    {
        /**
         * Display a listing of users.
         */
        public function index()
        {
            return Inertia::render('Users/Index', [
                'users' => User::all()
            ]);
        }

        /**
         * Show the form for creating a new user.
         */
        public function create()
        {
            return Inertia::render('Users/Create');
        }

        /**
         * Store a newly created user in storage.
         */
        public function store(Request $request)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'email' => 'required|string|email|max:255|unique:users',
                'password' => 'required|string|min:8|confirmed',
            ]);

            User::create([
                'name' => $validated['name'],
                'email' => $validated['email'],
                'password' => Hash::make($validated['password']),
            ]);

            // Add a success notification
            flash()->success('User created successfully!');

            // Redirect back, and Inertia will handle passing the flash message
            return Redirect::route('users.index');
        }

        /**
         * Display the specified user.
         */
        public function show(User $user)
        {
            return Inertia::render('Users/Show', [
                'user' => $user
            ]);
        }

        /**
         * Show the form for editing the specified user.
         */
        public function edit(User $user)
        {
            return Inertia::render('Users/Edit', [
                'user' => $user
            ]);
        }

        /**
         * Update the specified user in storage.
         */
        public function update(Request $request, User $user)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
            ]);

            $user->update($validated);

            flash()->success('User updated successfully!');

            return Redirect::route('users.index');
        }

        /**
         * Remove the specified user from storage.
         */
        public function destroy(User $user)
        {
            $user->delete();

            flash()->info('User has been deleted.');

            return Redirect::route('users.index');
        }
    }

### Success Message

    flash()->success('Item created successfully!')

### Error Message

    flash()->error('An error occurred!')

### Warning Message

    flash()->warning('Please review your submission.')

### Info Message

    flash()->info('Your session expires in 10 minutes.')

#### Expert Tips for Inertia.js Integration

- Keep It Consistent: Use the same notification types for similar actions throughout your application for a predictable user experience.
- Form Validation: Use flash notifications for general form status, but rely on Inertia's built-in error handling for field-specific validation errors.
- Watch for Changes: Always use the `watch` property to detect new notifications after navigation or form submissions.
- Package Bundling: When using Vite or Webpack, ensure @flasher/flasher is properly included in your bundle.

## Examples

### CRUD Operations

Here's a complete example of CRUD operations with flash messages:

app/Http/Controllers/ProductController.php

    <?php

    namespace App\Http\Controllers;

    use App\Models\Product;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Redirect;
    use Inertia\Inertia;

    class ProductController extends Controller
    {
        /**
         * Display a listing of products.
         */
        public function index()
        {
            return Inertia::render('Products/Index', [
                'products' => Product::all()
            ]);
        }

        /**
         * Show the form for creating a new product.
         */
        public function create()
        {
            return Inertia::render('Products/Create');
        }

        /**
         * Store a newly created product in storage.
         */
        public function store(Request $request)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'price' => 'required|numeric',
                'description' => 'nullable|string|max:1000',
                'category_id' => 'required|exists:categories,id',
            ]);

            Product::create($validated);

            flash()->success('Product created successfully!');

            return Redirect::route('products.index');
        }

        /**
         * Display the specified product.
         */
        public function show(Product $product)
        {
            return Inertia::render('Products/Show', [
                'product' => $product->load('category')
            ]);
        }

        /**
         * Show the form for editing the specified product.
         */
        public function edit(Product $product)
        {
            return Inertia::render('Products/Edit', [
                'product' => $product,
                'categories' => \App\Models\Category::all()
            ]);
        }

        /**
         * Update the specified product in storage.
         */
        public function update(Request $request, Product $product)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'price' => 'required|numeric',
                'description' => 'nullable|string|max:1000',
                'category_id' => 'required|exists:categories,id',
            ]);

            $product->update($validated);

            flash()->success('Product updated successfully!');

            return Redirect::route('products.index');
        }

        /**
         * Remove the specified product from storage.
         */
        public function destroy(Product $product)
        {
            try {
                $product->delete();
                flash()->info('Product has been deleted.');
            } catch (\Exception $e) {
                flash()->error('Cannot delete this product. It may be in use.');
            }

            return Redirect::route('products.index');
        }
    }

#### Best Practice: Error Handling

Always wrap database operations in try-catch blocks and provide user-friendly error messages. This helps users understand what went wrong and what actions they can take to resolve issues.

### Authentication Flow

Here's an example of using flash notifications in an authentication flow:

app/Http/Controllers/Auth/AuthController.php

    <?php

    namespace App\Http\Controllers\Auth;

    use App\Http\Controllers\Controller;
    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Redirect;
    use Illuminate\Validation\ValidationException;
    use Inertia\Inertia;

    class AuthController extends Controller
    {
        /**
         * Show the login form
         */
        public function showLogin()
        {
            return Inertia::render('Auth/Login');
        }

        /**
         * Handle user login request
         */
        public function login(Request $request)
        {
            $credentials = $request->validate([
                'email' => 'required|email',
                'password' => 'required',
            ]);

            if (Auth::attempt($credentials, $request->boolean('remember'))) {
                $request->session()->regenerate();

                flash()->success('Welcome back, ' . Auth::user()->name . '!');

                return Redirect::intended(route('dashboard'));
            }

            throw ValidationException::withMessages([
                'email' => __('auth.failed'),
            ]);
        }

        /**
         * Show registration form
         */
        public function showRegistration()
        {
            return Inertia::render('Auth/Register');
        }

        /**
         * Handle user registration
         */
        public function register(Request $request)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'email' => 'required|string|email|max:255|unique:users',
                'password' => 'required|string|min:8|confirmed',
            ]);

            $user = User::create([
                'name' => $validated['name'],
                'email' => $validated['email'],
                'password' => Hash::make($validated['password']),
            ]);

            Auth::login($user);

            flash()->success('Welcome to our application! Your account has been created.');

            return Redirect::route('dashboard');
        }

        /**
         * Log the user out
         */
        public function logout(Request $request)
        {
            Auth::logout();

            $request->session()->invalidate();
            $request->session()->regenerateToken();

            flash()->info('You have been logged out successfully.');

            return Redirect::route('login');
        }
    }

### Form Validation

Combine PHPFlasher with Inertia's validation error handling:

app/Http/Controllers/FormController.php

    <?php

    namespace App\Http\Controllers;

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Redirect;
    use Inertia\Inertia;

    class FormController extends Controller
    {
        /**
         * Show the contact form
         */
        public function showContactForm()
        {
            return Inertia::render('Contact/Form');
        }

        /**
         * Handle the contact form submission
         */
        public function submitContactForm(Request $request)
        {
            try {
                $validated = $request->validate([
                    'name' => 'required|string|max:255',
                    'email' => 'required|email',
                    'message' => 'required|string|min:10|max:1000',
                ]);

                // Process the form data
                // e.g., send an email, save to database, etc.

                // Show success notification
                flash()->success(
                    'Thank you for contacting us! We will get back to you soon.',
                    'Message Sent'
                );

                return Redirect::route('contact.thankyou');

            } catch (\Illuminate\Validation\ValidationException $e) {
                // With Inertia, validation errors are automatically passed back
                // Add a generic error message as well
                flash()->error('Please fix the errors in your form.', 'Form Validation Error');

                // With Inertia, this will redirect back with errors
                throw $e;
            } catch (\Exception $e) {
                // Handle other exceptions
                flash()->error(
                    'Sorry, we could not process your request. Please try again later.',
                    'System Error'
                );

                return Redirect::back();
            }
        }

        /**
         * Show thank you page
         */
        public function showThankYou()
        {
            return Inertia::render('Contact/ThankYou');
        }
    }

### Corresponding Vue Form Component

resources/js/Pages/Contact/Form.vue

    <script setup>
    import { useForm } from '@inertiajs/vue3'
    import Layout from '@/Layouts/MainLayout.vue'

    const form = useForm({
      name: '',
      email: '',
      message: ''
    })

    const submit = () => {
      form.post(route('contact.submit'))
    }
    </script>

    <template>
      <Layout>
        <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
          <h1 class="text-2xl font-semibold text-gray-900 mb-6">Contact Us</h1>

          <form @submit.prevent="submit" class="space-y-6">
            <div>
              <label for="name" class="block text-sm font-medium text-gray-700">Name</label>
              <input
                id="name"
                v-model="form.name"
                type="text"
                class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200 focus:ring-opacity-50"
              />
              <div v-if="form.errors.name" class="text-red-500 text-sm mt-1">{{ form.errors.name }}</div>
            </div>

            <div>
              <label for="email" class="block text-sm font-medium text-gray-700">Email</label>
              <input
                id="email"
                v-model="form.email"
                type="email"
                class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200 focus:ring-opacity-50"
              />
              <div v-if="form.errors.email" class="text-red-500 text-sm mt-1">{{ form.errors.email }}</div>
            </div>

            <div>
              <label for="message" class="block text-sm font-medium text-gray-700">Message</label>
              <textarea
                id="message"
                v-model="form.message"
                rows="4"
                class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200 focus:ring-opacity-50"
              ></textarea>
              <div v-if="form.errors.message" class="text-red-500 text-sm mt-1">{{ form.errors.message }}</div>
            </div>

            <div class="flex justify-end">
              <button
                type="submit"
                :disabled="form.processing"
                class="inline-flex items-center px-4 py-2 bg-emerald-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-emerald-700 active:bg-emerald-800 focus:outline-none focus:border-emerald-800 focus:ring focus:ring-emerald-300 disabled:opacity-25 transition"
              >
                <span v-if="form.processing">Processing...</span>
                <span v-else>Submit</span>
              </button>
            </div>
          </form>
        </div>
      </Layout>
    </template>

#### Tip: Handling Both Generic and Field-Specific Errors

As shown in the example above, it's often helpful to use PHPFlasher for global form status messages while using Inertia's built-in error handling for field-specific validation errors. This gives users both context-specific and general feedback.

## Frontend Framework Integration

Inertia.js works with multiple frontend frameworks. Here's how to integrate PHPFlasher with each:

### Vue.js Integration

resources/js/Layouts/AppLayout.vue

    <script setup>
    import { computed, watch } from 'vue'
    import { usePage } from '@inertiajs/vue3'
    import flasher from '@flasher/flasher'

    // Access shared data from Inertia
    const page = usePage()
    const messages = computed(() => page.props.messages)

    // Watch for changes in flash messages
    watch(
      messages,
      (newMessages) => {
        if (newMessages) {
          flasher.render(newMessages)
        }
      },
      { immediate: true }
    )
    </script>

    <template>
      <div class="min-h-screen bg-gray-100">
        <nav class="bg-white border-b border-gray-100">
          <!-- Navigation content -->
        </nav>

        <!-- Page Content -->
        <main>
          <slot />
        </main>

        <footer class="bg-white border-t border-gray-100 mt-auto">
          <!-- Footer content -->
        </footer>
      </div>
    </template>

Using flash messages in a Vue component:

resources/js/Pages/Dashboard.vue

    <script setup>
    import { useForm } from '@inertiajs/vue3'
    import AppLayout from '@/Layouts/AppLayout.vue'
    import flasher from '@flasher/flasher'

    const form = useForm({
      title: '',
      content: ''
    })

    const submit = () => {
      form.post(route('posts.store'), {
        onSuccess: () => {
          // You can also trigger notifications directly from the frontend
          flasher.success('Post created from the frontend!')

          // Reset the form
          form.reset()
        }
      })
    }
    </script>

    <template>
      <AppLayout>
        <div class="py-12">
          <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
              <div class="p-6 bg-white border-b border-gray-200">
                <form @submit.prevent="submit">
                  <!-- Form fields -->
                  <button type="submit" :disabled="form.processing">Create Post</button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </AppLayout>
    </template>

### React Integration

resources/js/Layouts/AppLayout.jsx

    import { useEffect } from 'react'
    import { usePage } from '@inertiajs/react'
    import flasher from '@flasher/flasher'

    export default function AppLayout({ children }) {
      const { messages } = usePage().props

      // Render flash messages whenever they change
      useEffect(() => {
        if (messages) {
          flasher.render(messages)
        }
      }, [messages])

      return (
        <div className="min-h-screen bg-gray-100">
          <nav className="bg-white border-b border-gray-100">
            {/* Navigation content */}
          </nav>

          {/* Page Content */}
          <main>{children}</main>

          <footer className="bg-white border-t border-gray-100 mt-auto">
            {/* Footer content */}
          </footer>
        </div>
      )
    }

Using flash messages in a React component:

resources/js/Pages/Dashboard.jsx

    import { useState } from 'react'
    import { useForm } from '@inertiajs/react'
    import AppLayout from '@/Layouts/AppLayout'
    import flasher from '@flasher/flasher'

    export default function Dashboard() {
      const { data, setData, post, processing, errors, reset } = useForm({
        title: '',
        content: ''
      })

      const handleSubmit = (e) => {
        e.preventDefault()

        post(route('posts.store'), {
          onSuccess: () => {
            // You can also trigger notifications directly from the frontend
            flasher.success('Post created from the frontend!')

            // Reset the form
            reset()
          }
        })
      }

      return (
        <AppLayout>
          <div className="py-12">
            <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
              <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div className="p-6 bg-white border-b border-gray-200">
                  <form onSubmit={handleSubmit}>
                    <div className="mb-4">
                      <label htmlFor="title" className="block text-sm font-medium text-gray-700">Title</label>
                      <input
                        type="text"
                        id="title"
                        value={data.title}
                        onChange={e => setData('title', e.target.value)}
                        className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
                      />
                      {errors.title && <div className="text-red-500 text-sm mt-1">{errors.title}</div>}
                    </div>

                    <div className="mb-4">
                      <label htmlFor="content" className="block text-sm font-medium text-gray-700">Content</label>
                      <textarea
                        id="content"
                        value={data.content}
                        onChange={e => setData('content', e.target.value)}
                        className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
                        rows="4"
                      ></textarea>
                      {errors.content && <div className="text-red-500 text-sm mt-1">{errors.content}</div>}
                    </div>

                    <button
                      type="submit"
                      disabled={processing}
                      className="bg-emerald-600 text-white px-4 py-2 rounded-md hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500"
                    >
                      {processing ? 'Creating...' : 'Create Post'}
                    </button>
                  </form>
                </div>
              </div>
            </div>
          </div>
        </AppLayout>
      )
    }

### Svelte Integration

resources/js/Layouts/AppLayout.svelte

    <script>
      import { page } from '$inertia/inertia-svelte'
      import { onMount, afterUpdate } from 'svelte'
      import flasher from '@flasher/flasher'

      export let title = ''

      // Render flash messages initially
      onMount(() => {
        if ($page.props.messages) {
          flasher.render($page.props.messages)
        }
      })

      // Watch for changes in flash messages
      afterUpdate(() => {
        if ($page.props.messages) {
          flasher.render($page.props.messages)
        }
      })
    </script>

    <div class="min-h-screen bg-gray-100">
      <nav class="bg-white border-b border-gray-100">
        <!-- Navigation content -->
      </nav>

      <!-- Page Content -->
      <main>
        <slot />
      </main>

      <footer class="bg-white border-t border-gray-100 mt-auto">
        <!-- Footer content -->
      </footer>
    </div>

Using flash messages in a Svelte component:

resources/js/Pages/Dashboard.svelte

    <script>
      import { inertia, useForm } from '@inertiajs/inertia-svelte'
      import AppLayout from '@/Layouts/AppLayout.svelte'
      import flasher from '@flasher/flasher'

      let form = useForm({
        title: '',
        content: ''
      })

      function submit() {
        $form.post(route('posts.store'), {
          onSuccess: () => {
            // You can also trigger notifications directly from the frontend
            flasher.success('Post created from the frontend!')

            // Reset the form
            $form.reset()
          }
        })
      }
    </script>

    <AppLayout>
      <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
          <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
            <div class="p-6 bg-white border-b border-gray-200">
              <form on:submit|preventDefault={submit}>
                <div class="mb-4">
                  <label for="title" class="block text-sm font-medium text-gray-700">Title</label>
                  <input
                    type="text"
                    id="title"
                    bind:value={$form.title}
                    class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
                  />
                  {#if $form.errors.title}
                    <div class="text-red-500 text-sm mt-1">{$form.errors.title}</div>
                  {/if}
                </div>

                <div class="mb-4">
                  <label for="content" class="block text-sm font-medium text-gray-700">Content</label>
                  <textarea
                    id="content"
                    bind:value={$form.content}
                    class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
                    rows="4"
                  ></textarea>
                  {#if $form.errors.content}
                    <div class="text-red-500 text-sm mt-1">{$form.errors.content}</div>
                  {/if}
                </div>

                <button
                  type="submit"
                  disabled={$form.processing}
                  class="bg-emerald-600 text-white px-4 py-2 rounded-md hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500"
                >
                  {$form.processing ? 'Creating...' : 'Create Post'}
                </button>
              </form>
            </div>
          </div>
        </div>
      </div>
    </AppLayout>

#### Framework-Specific Integration Tips

#### Vue.js

Use the `watch` function with `immediate: true` to ensure flash messages are shown on both initial load and after navigation.

#### React

With React, use `useEffect` with `messages` as a dependency to catch updates to flash messages.

#### Svelte

Combine `onMount` and `afterUpdate` lifecycle functions to ensure flash messages are shown both initially and after updates.

## Advanced

### Custom Notification Options

Customize your notifications with various options:

CustomOptions.php

    <?php

    namespace App\Http\Controllers;

    use Illuminate\Http\Request;
    use Inertia\Inertia;

    class NotificationController extends Controller
    {
        public function showExamples()
        {
            return Inertia::render('Notifications/Examples');
        }

        public function positionExample()
        {
            // Position options
            flash()
                ->option('position', 'bottom-right')
                ->success('Positioned at the bottom-right');

            return back();
        }

        public function timeoutExample()
        {
            // Timeout
            flash()
                ->option('timeout', 8000) // 8 seconds
                ->info('This message stays longer');

            return back();
        }

        public function animationExample()
        {
            // Animation
            flash()
                ->option('showAnimation', 'fadeIn')
                ->option('hideAnimation', 'fadeOut')
                ->success('Custom animations');

            return back();
        }

        public function multipleOptionsExample()
        {
            // Multiple options at once
            flash()
                ->options([
                    'position' => 'top-center',
                    'timeout' => 3000,
                    'closeButton' => false
                ])
                ->success('Multiple options at once');

            return back();
        }
    }