next-safe-action
API Reference

createMiddleware()

createMiddleware creates a standalone middleware function that can be shared across multiple action clients. It provides type constraints so TypeScript can verify the middleware is only used with compatible clients.

import { createMiddleware } from "next-safe-action";

const myMiddleware = createMiddleware<Constraints>().define(middlewareFn);

Signature

function createMiddleware<
	BaseData extends {
		serverError?: any;
		ctx?: object;
		metadata?: any;
	}
>(): {
	define: <NextCtx extends object>(middlewareFn: MiddlewareFn) => MiddlewareFn;
};

Generic parameter

createMiddleware accepts a single generic parameter BaseData that declares the minimum requirements for the middleware. The client using this middleware must have at least these properties available.

Prop

Type

.define() method

The define method accepts a middleware function and returns it with proper typing. The middleware function receives the same options as inline .use() middleware:

Prop

Type

Examples

No constraints (universal middleware)

src/lib/middleware/logging.ts
import { createMiddleware } from "next-safe-action";

export const loggingMiddleware = createMiddleware().define(
	async ({ next, metadata }) => {
		const start = Date.now();
		const result = await next();
		console.log(`Action took ${Date.now() - start}ms`, metadata);
		return result;
	}
);

With context constraints

src/lib/middleware/admin-guard.ts
import { createMiddleware } from "next-safe-action";

// Requires ctx.user with a role property
export const adminGuard = createMiddleware<{
	ctx: { user: { id: string; role: string } };
}>().define(async ({ next, ctx }) => {
	if (ctx.user.role !== "admin") {
		throw new Error("Admin access required");
	}
	// ctx.user is fully typed here
	return next();
});

With metadata constraints

src/lib/middleware/rate-limit.ts
import { createMiddleware } from "next-safe-action";

export const rateLimitMiddleware = createMiddleware<{
	metadata: { actionName: string };
}>().define(async ({ next, metadata }) => {
	await checkRateLimit(metadata.actionName);
	return next();
});

Extending context

src/lib/middleware/with-db.ts
import { createMiddleware } from "next-safe-action";

export const withDb = createMiddleware().define(async ({ next }) => {
	const db = await getDbConnection();

	return next({
		ctx: { db }, // Adds db to context for downstream middleware/actions
	});
});

Usage with clients

src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
import { loggingMiddleware } from "./middleware/logging";
import { adminGuard } from "./middleware/admin-guard";

const baseClient = createSafeActionClient().use(loggingMiddleware);

// ✅ Works — adminGuard requires ctx.user, which authMiddleware provides
const adminClient = baseClient
	.use(async ({ next }) => {
		const user = await getUser();
		return next({ ctx: { user } });
	})
	.use(adminGuard);

// ❌ Type error — adminGuard requires ctx.user, but baseClient doesn't have it
const broken = baseClient.use(adminGuard);

See also

On this page