Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

anonymouse703's avatar

What is the best approach to pass session message in Laravel-Vue Inertia?

Right now is I need to pass a function in the index to trigger the flash message like this

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SampleRequest;
use App\Models\Sample;
use Illuminate\Http\Request;
use App\Repositories\Contracts\SampleRepositoryInterface;
use Inertia\Inertia;


class SampleController extends Controller
{
    public function __construct(protected SampleRepositoryInterface $sampleRepository){}

    public function index (Request $request){

        $samples = $this->sampleRepository->getSamples($request);

        return Inertia::render('Sample/Index', [
            'samples' => $samples,
            'flash' => $this->flash()
        ]);
    }

    public function store(SampleRequest $request){
        $this->sampleRepository->create($request->all());
        return redirect()->route('sample.index')->with('success', 'Sample successfully added');
    }

    public function flash(){
        return [
            'info' => session('info'),
            'success' => session('success'),
            'danger' => session('danger'),
            'warning' => session('warning'),
            'light' => session('light'),
            'dark' => session('dark'),
        ];
    }
}

The case is I put the Banner component in the LayoutAuthenticated.vue

<script setup>
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import { ref, watch } from 'vue'
import { router, usePage } from '@inertiajs/vue3';
import menuAside from '@/menuAside.js'
import menuNavBar from '@/menuNavBar.js'
import { useDarkModeStore } from '@/Stores/darkMode.js'
import BaseIcon from '@/Components/BaseIcon.vue'
import FormControl from '@/Components/FormControl.vue'
import NavBar from '@/Components/NavBar.vue'
import NavBarItemPlain from '@/Components/NavBarItemPlain.vue'
import AsideMenu from '@/Components/AsideMenu.vue'
import FooterBar from '@/Components/FooterBar.vue'
import Banner from '@/Components/Banner.vue'

const layoutAsidePadding = 'xl:pl-60'
const darkModeStore = useDarkModeStore()
const isAsideMobileExpanded = ref(false)
const isAsideLgActive = ref(false)

const page = usePage();
const flashMessage = page.props.flash
console.log(flashMessage);

router.on('navigate', () => {
    isAsideMobileExpanded.value = false
    isAsideLgActive.value = false
})

const menuClick = (event, item) => {
    if (item.isToggleLightDark) {
        darkModeStore.set()
    }

    if (item.isLogout) {
        router.post(route('logout'))
    }
}
</script>

<template>
    <div :class="{
        'overflow-hidden lg:overflow-visible': isAsideMobileExpanded
    }">
        <div :class="[layoutAsidePadding, { 'ml-60 lg:ml-0': isAsideMobileExpanded }]"
            class="pt-14 min-h-screen w-screen transition-position lg:w-auto bg-gray-50 dark:bg-slate-800 dark:text-slate-100">
            <NavBar :menu="menuNavBar" :class="[layoutAsidePadding, { 'ml-60 lg:ml-0': isAsideMobileExpanded }]"
                @menu-click="menuClick">
                <NavBarItemPlain display="flex lg:hidden"
                    @click.prevent="isAsideMobileExpanded = !isAsideMobileExpanded">
                    <BaseIcon :path="isAsideMobileExpanded ? mdiBackburger : mdiForwardburger" size="24" />
                </NavBarItemPlain>
                <NavBarItemPlain display="hidden lg:flex xl:hidden" @click.prevent="isAsideLgActive = true">
                    <BaseIcon :path="mdiMenu" size="24" />
                </NavBarItemPlain>
                <NavBarItemPlain use-margin>
                    <FormControl placeholder="Search (ctrl+k)" ctrl-k-focus transparent borderless />
                </NavBarItemPlain>
            </NavBar>
            <AsideMenu :is-aside-mobile-expanded="isAsideMobileExpanded" :is-aside-lg-active="isAsideLgActive"
                :menu="menuAside" @menu-click="menuClick" @aside-lg-close-click="isAsideLgActive = false" />
            <Banner :flash-messages="flashMessage" />
            <slot />
            <FooterBar>
                Get more with
                <a href=" https://tailwind-vue.justboil.me/" target="_blank" class="text-blue-600">Premium version</a>
            </FooterBar>
        </div>
    </div>
</template>

and this is how I set the Banner.vue

<script setup>
import { defineProps, onMounted } from 'vue';

const props = defineProps({
    flashMessages: {
        type: Object,
        default: () => ({}),
    },
});

const getBannerClass = (type) => {
    const typeMap = {
        success: 'flash-message__success',
        info: 'flash-message__info',
        danger: 'flash-message__danger',
        warning: 'flash-message__warning',
        light: 'flash-message__light',
        secondary: 'flash-message__secondary',
        dark: 'flash-message__dark',
    };

    return `flash-message ${typeMap[type] || 'flash-message__default'}`;
};

const handleRemoveMessage = (type) => {
    props.flashMessages[type] = '';
};

onMounted(() => {
    // Clear messages after 5 seconds
    for (const type in props.flashMessages) {
        if (props.flashMessages[type]) {
            setTimeout(() => {
                handleRemoveMessage(type);
            }, 5000);
        }
    }
});

</script>

<template>
    <div>
        <div v-for="(message, type) in props.flashMessages" :key="type">
            <div :class="getBannerClass(type)" v-if="message && type">
                <button type="button" class="close">x</button>
                <strong>{{ message }}</strong>
            </div>
        </div>
    </div>
</template>


<style scoped>
.flash-message {
    padding: 10px;
    margin-bottom: 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-radius: 4px;
}

.flash-message__success {
    background-color: #d4edda;
    color: #155724;
    border: 1px solid #c3e6cb;
}

.flash-message__info {
    background-color: #d1ecf1;
    color: #0c5460;
    border: 1px solid #bee5eb;
}

.flash-message__danger {
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
}

.flash-message__warning {
    background-color: #fff3cd;
    color: #856404;
    border: 1px solid #ffeeba;
}

/* Light Theme */
.flash-message__light {
    background-color: #fff3cd;
    color: #856404;
    border: 1px solid #ffeeba;
}

/* Dark Theme */
.flash-message__dark {
    background-color: #343a40;
    color: #adb5bd;
    border: 1px solid #212529;
}

/* Secondary Theme */
.flash-message__secondary {
    background-color: #d1ecf1;
    color: #0c5460;
    border: 1px solid #bee5eb;
}


.flash-message__default {
    background-color: #f0f0f0;
    color: #333;
    border: 1px solid #ccc;
}

.flash-message__text {
    margin: 0;
}

.flash-message__close {
    cursor: pointer;
}
</style>
0 likes
1 reply
ErmishinD's avatar
Level 1

Check out the official documentation of Inertia: https://inertiajs.com/shared-data#flash-messages

So, you can add all session messages you need inside app/Http/Middleware/HandleInertiaRequests.php file:

class HandleInertiaRequests extends Middleware {
    public function share(Request $request) {
        return array_merge(parent::share($request), [
            'flash' => [
                'info' => fn () => $request->session()->get('info'),
                'success' => fn () => $request->session()->get('success'),
				// ...
            ],
        ]);
    }
}

And then access this data in your Vue component in similar way:

<template>
	<main>
		{{ page.props.flash }}
	</main>
</template>
1 like

Please or to participate in this conversation.