Skip to content

Flash Messages

Flash messages are short-lived pieces of data that persist for exactly one subsequent request and are then discarded. They are typically used to display success or error messages after form submissions, redirects, or other state-changing operations. For example, after creating a new record you might flash a success message, redirect to an index page, and show that message once.

Pass a flash store instance to InertiaModule.forRoot() to enable flash messages:

import { Module } from 'stratal/module'
import { InertiaModule, CookieFlashStore } from '@stratal/inertia'
import rootView from './inertia/root.html'
@Module({
imports: [
InertiaModule.forRoot({
rootView,
flash: {
store: new CookieFlashStore({
secret: env.APP_SECRET,
}),
},
}),
],
})
export class AppModule {}

CookieFlashStore stores flash data in an HMAC-signed cookie. It works out of the box with no external dependencies.

import { CookieFlashStore } from '@stratal/inertia'
new CookieFlashStore({
secret: env.APP_SECRET, // HMAC signing secret
cookie: 'stratal_flash', // cookie name (default)
cookieOptions: { // optional
path: '/',
httpOnly: true,
sameSite: 'Lax',
},
})
OptionTypeDefaultDescription
secretstringrequiredSecret used to sign the cookie value
cookiestring'stratal_flash'Name of the cookie
cookieOptionsobject{}Standard cookie attributes

Use ctx.flash() inside any controller handler to store data for the next request:

import { Controller, IController, RouterContext } from 'stratal/router'
import { InertiaPost } from '@stratal/inertia'
@Controller('/posts')
export class PostsController implements IController {
@InertiaPost('/')
async store(ctx: RouterContext) {
// ... create the post
ctx.flash('success', 'Post created successfully!')
return ctx.redirect('/posts')
}
}

You can flash any serializable value:

ctx.flash('success', 'Item created!')
ctx.flash('error', 'Something went wrong.')
ctx.flash('formData', { title: 'Draft', body: '' })

Flash data is automatically merged into the page props on the next request. Access it through Inertia’s usePage() hook:

import { usePage } from '@inertiajs/react'
export default function PostsIndex({ posts }) {
const { flash } = usePage().props
return (
<div>
{flash.success && (
<div className="alert alert-success">{flash.success}</div>
)}
{flash.error && (
<div className="alert alert-error">{flash.error}</div>
)}
{/* render posts */}
</div>
)
}

If cookies are not suitable for your use case (for example, when flash payloads are large or you need server-side storage), implement the FlashStore interface:

interface FlashStore {
read(ctx: RouterContext): Promise<Record<string, unknown>>
write(ctx: RouterContext, data: Record<string, unknown>): Promise<void>
clear(ctx: RouterContext): Promise<void>
}
MethodPurpose
readRetrieve the current flash data for this request
writePersist flash data so it is available on the next request
clearRemove flash data after it has been read

The following example stores flash data in a Cloudflare KV namespace, keyed by a session identifier:

import type { FlashStore } from '@stratal/inertia'
import type { RouterContext } from 'stratal/router'
export class KvFlashStore implements FlashStore {
constructor(private readonly binding: KVNamespace) {}
async read(ctx: RouterContext): Promise<Record<string, unknown>> {
const key = this.getKey(ctx)
const data = await this.binding.get(key, 'json')
return (data as Record<string, unknown>) ?? {}
}
async write(ctx: RouterContext, data: Record<string, unknown>): Promise<void> {
const key = this.getKey(ctx)
await this.binding.put(key, JSON.stringify(data), {
expirationTtl: 300, // 5 minutes
})
}
async clear(ctx: RouterContext): Promise<void> {
const key = this.getKey(ctx)
await this.binding.delete(key)
}
private getKey(ctx: RouterContext): string {
const sessionId = ctx.c.req.cookie('session_id')
return `flash:${sessionId}`
}
}

Register it the same way as the built-in store:

InertiaModule.forRoot({
rootView,
flash: {
store: new KvFlashStore(env.FLASH_KV),
},
})