Skip to main content

Server-Side Usage

Use feature flags in Nitro server routes, API handlers, and middleware.

Prefer ambient EvalContext — configure identity, groups, claims, and request headers once (like .NET AddTogglyWeb), then call isEventFeatureOn(event, 'X') or useEventToggly(event).isFeatureOn('X') without per-call options. HTTP segment fields are filled from the H3 event via fromHttpRequest.

configureEventEvalContext​

Register providers once (e.g. in a Nitro plugin). Event helpers resolve them per request and cache the result on the H3 event.

server/plugins/toggly-context.ts
import { configureEventEvalContext } from '@ops-ai/nuxt-toggly-server'

export default defineNitroPlugin(() => {
configureEventEvalContext({
getIdentity: (event) => getCookie(event, 'userId'),
getGroups: async (event) => {
const user = await getUserFromSession(event)
return user?.roles
},
getClaims: async (event) => {
const user = await getUserFromSession(event)
return user ? { role: user.role, plan: user.plan } : undefined
},
// Or getContext: (event) => ({ identity, groups, claims, request })
})
})

defineTogglyContextMiddleware​

Alternatively, resolve and cache ambient context in middleware:

server/middleware/toggly-context.ts
import { defineTogglyContextMiddleware } from '@ops-ai/nuxt-toggly-server'

export default defineTogglyContextMiddleware({
getIdentity: (event) => getCookie(event, 'userId'),
getGroups: () => ['beta'],
getClaims: () => ({ role: 'admin' }),
})

Missing request keys are still filled from H3 headers even when you supply getContext.

Server Utilities​

useEventToggly​

Get a Toggly client scoped to the current request. Ambient EvalContext (from providers / middleware) is bound automatically — no identity props needed:

server/api/data.ts
export default defineEventHandler(async (event) => {
const toggly = useEventToggly(event)

const features = {
newApi: await toggly.isFeatureOn('new-api'),
betaMode: await toggly.isFeatureOn('beta-mode'),
mobileCheckout: await toggly.isFeatureOn('MobileCheckout'),
}

if (features.newApi) {
return getNewApiData()
}

return getLegacyApiData()
})

isEventFeatureOn​

Quick check for a single feature in an event handler (uses ambient context):

server/api/checkout.ts
export default defineEventHandler(async (event) => {
if (await isEventFeatureOn(event, 'new-checkout')) {
return processNewCheckout(event)
}

return processLegacyCheckout(event)
})

evaluateEventFeatureGate​

Check multiple features with logical operators.

server/api/premium.ts
export default defineEventHandler(async (event) => {
// All features must be enabled
const hasPremiumAccess = await evaluateEventFeatureGate(
event,
['premium-tier', 'api-access'],
'all'
)

if (!hasPremiumAccess) {
throw createError({
statusCode: 403,
message: 'Premium access required',
})
}

return getPremiumData()
})

Protected Handlers​

defineFeatureHandler​

Wrap entire API routes with feature gates.

server/api/beta-data.ts
export default defineFeatureHandler('beta-api', async (event) => {
// This handler only executes if 'beta-api' feature is enabled
return {
data: 'beta content',
timestamp: Date.now(),
}
})

With custom error response:

server/api/premium-api.ts
export default defineFeatureHandler(
'premium-api',
async (event) => {
return getPremiumData()
},
{
statusCode: 403,
message: 'This feature requires a premium subscription',
}
)

Multiple features required:

server/api/admin.ts
export default defineFeatureHandler(
['admin-panel', 'api-v2'],
async (event) => {
return getAdminData()
},
{
requirement: 'all', // Both features must be enabled
statusCode: 404,
message: 'Not found',
}
)

Middleware​

defineFeatureMiddleware​

Create middleware that gates routes by feature flags.

server/middleware/beta.ts
export default defineFeatureMiddleware({
featureKey: 'beta-access',
statusCode: 404,
message: 'Not found',
})

Multiple features:

server/middleware/admin.ts
export default defineFeatureMiddleware({
featureKey: ['admin-panel', 'internal-tools'],
requirement: 'all',
statusCode: 403,
message: 'Access denied',
})

Route-Specific Middleware​

Apply middleware to specific routes:

server/middleware/beta-routes.ts
export default defineEventHandler(async (event) => {
const path = getRequestURL(event).pathname

// Only apply to /beta/* routes
if (path.startsWith('/api/beta/')) {
if (!(await isEventFeatureOn(event, 'beta-access'))) {
throw createError({
statusCode: 404,
message: 'Not found',
})
}
}
})

Server Caching​

Features are cached server-side for performance. Long-lived Node processes also keep definitions fresh via WebSocket live updates by default (enableLiveUpdates: true, refreshInterval: 0). Set enableLiveUpdates: false if you need to opt out.

nuxt.config.ts
export default defineNuxtConfig({
toggly: {
serverCache: true, // Enable caching (default)
serverCacheTtl: 60000, // Cache TTL: 1 minute (default)
// enableLiveUpdates: false, // opt out of server WebSocket sync
},
})

Cache Behavior​

  1. First request fetches from Toggly API
  2. Subsequent requests use cached values
  3. Cache expires after TTL
  4. WebSocket push (or next TTL miss) refreshes definitions

Disable Caching​

For real-time feature updates without the in-memory HTTP cache helper:

export default defineNuxtConfig({
toggly: {
serverCache: false, // Do not seed from/write server storage cache
},
})

Live WebSocket updates still apply unless enableLiveUpdates is set to false.

Overrides (per-call EvalContext)​

Prefer ambient providers above. When one check needs different identity, groups, claims, or headers, use isServerFeatureOn (or client.isFeatureOn(key, entity, kind, overrides)). Explicit fields win field-by-field over ambient.

server/api/checkout.ts
import { getRequestHeaders } from 'h3'
import { isServerFeatureOn } from '@ops-ai/nuxt-toggly-server'

export default defineEventHandler(async (event) => {
const user = await getUserFromSession(event)

const mobile = await isServerFeatureOn('MobileCheckout', {
identity: user?.id,
groups: user?.roles,
claims: { role: user?.role ?? '' },
headers: getRequestHeaders(event),
})

return { mobile }
})
FieldPurpose
identityPercentage / Targeting
groupsTargeting groups
claimsUserClaims
request{ userAgent, acceptLanguage, country }
headersMapped with fromHttpRequest (explicit request wins)

Init-time groups / claims on the module config are process-wide defaults on the shared client. Prefer ambient providers (or per-call overrides) under concurrency.

See SDK × filter matrix and Feature filters.

Entity context​

Nitro handlers evaluate full definitions with Context Property filters per request entity. See Entity & page context.

Pass the domain object on each isFeatureOn call. Ambient context covers the user (identity / groups / claims / request); entity args stay per-call.

server/api/orders/[id].ts
export default defineEventHandler(async (event) => {
const order = await loadOrder(event)
const toggly = useEventToggly(event)

const express = await toggly.isFeatureOn('ExpressCheckout', order, 'Order')
return { express }
})

Register entity kinds at server startup (PUT sdk/{appKey}/contexts) unless you opt out. Browsers never perform that PUT. Unknown kind fails closed.

User Identity on Server​

Configure identity once via ambient providers (see above). Handlers then check flags without setIdentity or per-call options:

server/api/personalized.ts
export default defineEventHandler(async (event) => {
const toggly = useEventToggly(event)
const showNewDashboard = await toggly.isFeatureOn('new-dashboard')

return { showNewDashboard }
})

Avoid mutating process-wide defaults with setIdentity on long-lived Node processes.

Error Handling​

server/api/features.ts
export default defineEventHandler(async (event) => {
try {
const toggly = useEventToggly(event)
const isEnabled = await toggly.isFeatureOn('my-feature')

return { isEnabled }
} catch (error) {
// Log error, return safe default
console.error('Feature check failed:', error)

return { isEnabled: false }
}
})

Environment Detection​

server/api/config.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()

return {
environment: config.public.toggly.environment,
hasAppKey: !!config.public.toggly.appKey,
}
})

Combining with Auth​

server/api/protected.ts
export default defineEventHandler(async (event) => {
// 1. Check authentication
const user = await requireAuth(event)

// 2. Check feature flag
if (!(await isEventFeatureOn(event, 'protected-feature'))) {
throw createError({
statusCode: 403,
message: 'Feature not available',
})
}

// 3. Check user permissions
if (!user.hasPermission('read:data')) {
throw createError({
statusCode: 403,
message: 'Insufficient permissions',
})
}

return getProtectedData(user)
})

Usage & metrics​

Usage checks and business metrics: Usage & metrics. Install @grpc/grpc-js and @grpc/proto-loader to send telemetry on Node/Nitro. Nitro edge uses HTTPS (telemetryTransport: 'https'). Set TOGGLY_DISABLE_TELEMETRY=1 to force telemetry off.

TypeScript​

Import server types as needed:

import type {
FeatureMiddlewareOptions,
TogglyServerConfig,
} from '@ops-ai/nuxt-toggly-server'