The clerkMiddleware() helper integrates Clerk authentication into your Next.js application through Middleware. clerkMiddleware() is compatible with both the App and Pages routers.
import { clerkMiddleware } from'@clerk/nextjs/server'exportdefaultclerkMiddleware()exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
By default, clerkMiddleware will not protect any routes. All routes are public and you must opt-in to protection for routes.
createRouteMatcher() is a Clerk helper function that allows you to protect multiple routes. createRouteMatcher() accepts an array of routes and checks if the route the user is trying to visit matches one of the routes passed to it. The paths provided to this helper can be in the same format as the paths provided to the Next Middleware matcher.
The createRouteMatcher() helper returns a function that, if called with the req object from the Middleware, will return true if the user is trying to access a route that matches one of the routes passed to createRouteMatcher().
In the following example, createRouteMatcher() sets all /dashboard and /forum routes as protected routes.
If you have a <Link> tag on a public page that points to a protected page that returns a 400-level error, like a 401, the data prefetch will fail because it will be redirected to the sign-in page and throw a confusing error in the console. To prevent this behavior, disable prefetching by adding prefetch={false} to the <Link> component.
You can protect routes based on a user's authentication status by checking if the user is signed in.
There are two methods that you can use:
Use auth.protect()Next.js Icon if you want to redirect unauthenticated users to the sign-in route automatically.
Use auth().userIdNext.js Icon if you want more control over what your app does based on user authentication status.
auth.protect()
auth().userId()
middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'constisProtectedRoute=createRouteMatcher(['/dashboard(.*)','/forum(.*)'])exportdefaultclerkMiddleware(async (auth, req) => {if (isProtectedRoute(req)) awaitauth.protect()})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
app/middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'constisProtectedRoute=createRouteMatcher(['/dashboard(.*)','/forum(.*)'])exportdefaultclerkMiddleware(async (auth, req) => {const { userId,redirectToSignIn } =awaitauth()if (!userId &&isProtectedRoute(req)) {// Add custom logic to run before redirectingreturnredirectToSignIn() }})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
You can protect routes based on a user's authorization status by checking if the user has the required roles or permissions.
There are two methods that you can use:
Use auth.protect()Next.js Icon if you want Clerk to return a 404 if the user does not have the role or permission.
Use auth().has()Clerk Icon if you want more control over what your app does based on the authorization status.
auth.protect()
auth().has()
middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'constisProtectedRoute=createRouteMatcher(['/admin(.*)'])exportdefaultclerkMiddleware(async (auth, req) => {// Restrict admin routes to users with specific permissionsif (isProtectedRoute(req)) {awaitauth.protect((has) => {returnhas({ permission:'org:admin:example1' }) ||has({ permission:'org:admin:example2' }) }) }})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
Warning
Using has()on the server-side to check permissions works only with custom permissions, as system permissions aren't included in the session token claims. To check system permissions, verify the user's role instead.
middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'constisProtectedRoute=createRouteMatcher(['/admin(.*)'])exportdefaultclerkMiddleware(async (auth, req) => {const { has,redirectToSignIn } =awaitauth()// Restrict admin routes to users with specific permissionsif ( (isProtectedRoute(req) &&!has({ permission:'org:admin:example1' })) ||!has({ permission:'org:admin:example2' }) ) {// Add logic to run if the user does not have the required permissionsreturnredirectToSignIn() }})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
You can use more than one createRouteMatcher() in your application if you have two or more groups of routes.
The following example uses the has()Clerk Icon method from the auth() helper.
Tip
If you have a <Link> tag on a public page that points to a protected page that returns a 400-level error, like a 401, the data prefetch will fail because it will be redirected to the sign-in page and throw a confusing error in the console. To prevent this behavior, disable prefetching by adding prefetch={false} to the <Link> component.
middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'constisTenantRoute=createRouteMatcher(['/organization-selector(.*)','/orgid/(.*)'])constisTenantAdminRoute=createRouteMatcher(['/orgId/(.*)/memberships','/orgId/(.*)/domain'])exportdefaultclerkMiddleware(async (auth, req) => {// Restrict admin routes to users with specific permissionsif (isTenantAdminRoute(req)) {awaitauth.protect((has) => {returnhas({ permission:'org:admin:example1' }) ||has({ permission:'org:admin:example2' }) }) }// Restrict organization routes to signed in usersif (isTenantRoute(req)) awaitauth.protect()})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
To protect all routes in your application and define specific routes as public, you can use any of the above methods and simply invert the if condition.
Tip
If you have a <Link> tag on a public page that points to a protected page that returns a 400-level error, like a 401, the data prefetch will fail because it will be redirected to the sign-in page and throw a confusing error in the console. To prevent this behavior, disable prefetching by adding prefetch={false} to the <Link> component.
middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'constisPublicRoute=createRouteMatcher(['/sign-in(.*)','/sign-up(.*)'])exportdefaultclerkMiddleware(async (auth, req) => {if (!isPublicRoute(req)) {awaitauth.protect() }})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
You can protect routes based on token types by checking if the request includes the required token (e.g. OAuth token, API key, machine token or session token). This ensures that only requests with the appropriate token type can access the route.
The following example uses the protect()Next.js Icon method from the auth() helper. Requests without the required token will return an appropriate error:
A 404 error for unauthenticated requests with a session token type.
A 401 error for unauthenticated requests with machine token types.
middleware.ts
import { clerkMiddleware, createRouteMatcher } from'@clerk/nextjs/server'// Create route matchers to identify which token type each route should requireconstisOAuthAccessible=createRouteMatcher(['/oauth(.*)'])constisApiKeyAccessible=createRouteMatcher(['/api(.*)'])constisMachineTokenAccessible=createRouteMatcher(['/m2m(.*)'])constisUserAccessible=createRouteMatcher(['/user(.*)'])constisAccessibleToAnyValidToken=createRouteMatcher(['/any(.*)'])exportdefaultclerkMiddleware(async (auth, req) => {// Check if the request matches each route and enforce the corresponding token typeif (isOAuthAccessible(req)) awaitauth.protect({ token:'oauth_token' })if (isApiKeyAccessible(req)) awaitauth.protect({ token:'api_key' })if (isMachineTokenAccessible(req)) awaitauth.protect({ token:'machine_token' })if (isUserAccessible(req)) awaitauth.protect({ token:'session_token' })if (isAccessibleToAnyValidToken(req)) awaitauth.protect({ token:'any' })})exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
If you are having issues getting your Middleware dialed in, or are trying to narrow down auth-related issues, you can use the debugging feature in clerkMiddleware(). Add { debug: true } to clerkMiddleware() and you will get debug logs in your terminal.
middleware.ts
import { clerkMiddleware } from'@clerk/nextjs/server'exportdefaultclerkMiddleware( (auth, req) => {// Add your middleware checks }, { debug:true },)exportconstconfig= { matcher: [// Skip Next.js internals and all static files, unless found in search params'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',// Always run for API routes'/(api|trpc)(.*)', ],}
If you would like to set up debugging for your development environment only, you can use the process.env.NODE_ENV variable to conditionally enable debugging. For example, { debug: process.env.NODE_ENV === 'development' }.
The clerkMiddleware() function accepts an optional object. The following options are available:
Name
audience?
Type
string | string[]
Description
A string or list of audiences. If passed, it is checked against the aud claim in the token.
Name
authorizedParties?
Type
string[]
Description
An allowlist of origins to verify against, to protect your application from the subdomain cookie leaking attack. For example: ['http://localhost:3000', 'https://example.com']
Name
clockSkewInMs?
Type
number
Description
Specifies the allowed time difference (in milliseconds) between the Clerk server (which generates the token) and the clock of the user's application server when validating a token. Defaults to 5000 ms (5 seconds).
Name
domain?
Type
string
Description
The domain used for satellites to inform Clerk where this application is deployed.
Name
isSatellite?
Type
boolean
Description
When using Clerk's satellite feature, this should be set to true for secondary domains.
Name
jwtKey
Type
string
Description
Used to verify the session token in a networkless manner. Supply the JWKS Public Key from the API keys page in the Clerk Dashboard. It's recommended to use the environment variable instead. For more information, refer to Manual JWT verification.
Used to activate a specific organization or personal account based on URL path parameters. If there's a mismatch between the active organization in the session (e.g., as reported by auth()Next.js Icon) and the organization indicated by the URL, the middleware will attempt to activate the organization specified in the URL.
Name
proxyUrl?
Type
string
Description
Specify the URL of the proxy, if using a proxy.
Name
signInUrl
Type
string
Description
The full URL or path to your sign-in page. Needs to point to your primary application on the client-side. Required for a satellite application in a development instance. It's recommended to use the environment variable instead.
Name
signUpUrl
Type
string
Description
The full URL or path to your sign-up page. Needs to point to your primary application on the client-side. Required for a satellite application in a development instance. It's recommended to use the environment variable instead.
Name
publishableKey
Type
string
Description
The Clerk Publishable Key for your instance. This can be found on the API keys page in the Clerk Dashboard.
Name
secretKey?
Type
string
Description
The Clerk Secret Key for your instance. This can be found on the API keys page in the Clerk Dashboard. The CLERK_ENCRYPTION_KEY environment variable must be set when providing secretKey as an option, refer to Dynamic keys.
It's also possible to dynamically set options based on the incoming request:
middleware.ts
import { clerkMiddleware } from'@clerk/nextjs/server'exportdefaultclerkMiddleware( (auth, req) => {// Add your middleware checks }, (req) => ({// Provide `domain` based on the request host domain:req.nextUrl.host, }),)
Dynamic keys are not accessible on the client-side.
The following options, known as "Dynamic Keys," are shared to the Next.js application server through clerkMiddleware, enabling access by server-side helpers like auth()Next.js Icon:
signUpUrl
signInUrl
secretKey
publishableKey
Dynamic keys are encrypted and shared during request time using a AES encryption algorithm. When providing a secretKey, the CLERK_ENCRYPTION_KEY environment variable is mandatory and used as the encryption key. If no secretKey is provided to clerkMiddleware, the encryption key defaults to CLERK_SECRET_KEY.
When providing CLERK_ENCRYPTION_KEY, it is recommended to use a 32-byte (256-bit), pseudorandom value. You can use openssl to generate a key:
terminal
opensslrand--hex32
For multi-tenant applications, you can dynamically define Clerk keys depending on the incoming request. Here's an example:
middleware.ts
import { clerkMiddleware } from'@clerk/nextjs/server'// You would typically fetch these keys from a external store or environment variables.consttenantKeys= { tenant1: { publishableKey:'pk_tenant1...', secretKey:'sk_tenant1...' }, tenant2: { publishableKey:'pk_tenant2...', secretKey:'sk_tenant2...' },}exportdefaultclerkMiddleware( (auth, req) => {// Add your middleware checks }, (req) => {// Resolve tenant based on the requestconsttenant=getTenant(req)return tenantKeys[tenant] },)
The organizationSyncOptions property on the clerkMiddleware()Next.js Icon options
object has the type OrganizationSyncOptions, which has the following properties:
Specifies URL patterns that are organization-specific, containing an organization ID or slug as a path parameter. If a request
matches this path, the organization identifier will be used to set that org as active.
If the route also matches the personalAccountPatterns prop, this prop takes precedence.
Patterns must have a path parameter named either :id (to match a Clerk organization ID) or :slug (to match a Clerk organization slug).
Warning
If the organization can't be activated—either because it doesn't exist or the user lacks access—the previously active organization will remain unchanged. Components must detect this case and provide an appropriate error and/or resolution pathway, such as calling notFound() or displaying an <OrganizationSwitcher />.