Hono (@ops-ai/toggly-hono)
Examples and requirements
Open the Hono sample. Read the README, then src/catalog.js for flag inputs, src/app.js for request-scoped middleware and Order checks, and test/http.test.js for gate and isolation expectations. The catalog lists current sample availability.
Identity, groups, and claims are targeting inputs, not authentication. Populate them from your trusted session; a caller-controlled identity header does not authorize a user.
Requires Node.js 18+ and Hono 4.x (^4.0.0 in adapter 0.3.0). The
compatibility suite covers current Hono 4.13.7 with the Node server adapter,
packed ESM and CommonJS consumers, typechecking, request isolation, gates,
refresh, signed-definition rejection, and shutdown. Node 18/20/22 keep unit
coverage; Node 24 and 26 run the packed-host checks.
Install
npm install @ops-ai/toggly-hono
Middleware
import { Hono } from 'hono'
import { togglyMiddleware, featureGate, closeHonoToggly } from '@ops-ai/toggly-hono'
const app = new Hono()
app.use(
'*',
togglyMiddleware({
appKey: process.env.TOGGLY_APP_KEY!,
environment: 'Production',
verifySignatures: true,
enableStreaming: true,
}),
)
app.get('/dashboard', async (c) => {
const toggly = c.get('toggly')
if (await toggly.isFeatureOn('new-dashboard')) {
return c.json({ version: 'v2' })
}
return c.json({ version: 'v1' })
})
app.get('/beta', featureGate({ featureKey: 'beta-access' }), (c) =>
c.json({ ok: true }),
)
process.on('SIGTERM', () => closeHonoToggly())
Helpers live on the Hono context via c.get('toggly').
Ambient EvalContext (recommended)
Configure identity, groups, and claims once on middleware. Segment
request.* fields are filled from headers via fromHttpRequest. Then
toggly.isFeatureOn('X') needs no context argument.
app.use(
'*',
togglyMiddleware({
appKey: process.env.TOGGLY_APP_KEY!,
getIdentity: (c) => c.get('user')?.id,
getGroups: (c) => c.get('user')?.roles ?? [],
getClaims: (c) => ({ role: c.get('user')?.role ?? '' }),
// Or getContext: (c) => ({ identity, groups, claims, request })
}),
)
app.get('/checkout', async (c) => {
const toggly = c.get('toggly')
if (await toggly.isFeatureOn('MobileCheckout')) {
return c.json({ flow: 'mobile' })
}
return c.json({ flow: 'desktop' })
})
Default identity (when getIdentity is unset): x-toggly-identity header.
When you supply getContext, missing request keys are still filled from
headers. Prefer getIdentity / getGroups / getClaims for the happy path.
Overrides and entity checks
In the adapter, the request helper isFeatureOn(key) accepts only the flag key and uses the middleware's context. Passing a second argument does not override that context. Call its core client explicitly and merge the fields you want to preserve:
const toggly = c.get('toggly')
const evaluation = {
...toggly.context,
identity: 'impersonated-user',
claims: { ...toggly.context.claims, role: 'admin' },
}
await toggly.client.isFeatureOn('MobileCheckout', evaluation)
// A ready entity context keeps Order data separate from user targeting.
await toggly.client.isFeatureOn('OrderBadge', toggly.context, {
kind: 'Order',
key: 'ORD-42',
attributes: { Status: 'Paid' },
})
The entity kind/property must match the feature definition. See Node entity context for mapper registration and HTTP segment filters for request fields.
Feature gates
Same gate options as Express. Also: featureRoutes, withFeature, featuresHandler, getHonoToggly.
Core config: Configuration.
Feature snapshot endpoint
featuresHandler is the Hono handler itself; pass it without calling it. Register it after the middleware:
app.get('/features', featuresHandler)
The response pairs the shared client's boolean feature snapshot with the current request's identity. It does not re-evaluate that snapshot using the request's groups, claims, or Order entity. An identity in the response therefore does not mean the returned flags were evaluated for that user. Use the request's isFeatureOn(key) helper for request-targeted decisions, or its client with explicit context/entity arguments as shown above.