PluginsREST API
API Routes
Validate plugin-owned Hono route inputs and responses with Zod, then protect staff actions with a clear permission.
Create the plugin and its API module first. Then put the endpoint in that module so its validation, permission, and OpenAPI record travel together.
Validate input and response
import { z } from '@hono/zod-openapi'
import { buildRoute } from '@vitnode/core/api/lib/route'
export const getNoteRoute = buildRoute({
pluginId: '@acme/site-notes',
route: {
method: 'get',
path: '/{id}',
request: {
params: z.object({ id: z.coerce.number().int().positive() }),
},
responses: {
200: {
content: {
'application/json': {
schema: z.object({ id: z.number(), title: z.string() }),
},
},
description: 'One site note.',
},
},
},
handler: (c) => {
const { id } = c.req.valid('param')
return c.json({ id, title: 'Plugin-owned note' })
},
})Gate an AdminCP action
Add adminStaffPermission to an action that only staff should call. The API
still authorizes server-side; a hidden button is merely good manners.
import { z } from '@hono/zod-openapi'
import { buildRoute } from '@vitnode/core/api/lib/route'
export const publishNoteRoute = buildRoute({
adminStaffPermission: {
module: 'site_notes',
permission: 'can_publish',
},
pluginId: '@acme/site-notes',
route: {
method: 'post',
path: '/{id}/publish',
responses: {
200: {
content: {
'application/json': {
schema: z.object({ published: z.literal(true) }),
},
},
description: 'The note was published.',
},
},
},
handler: async (c) => c.json({ published: true }),
})One route, one plugin owner
Keep handlers with the feature that owns the data. The host API config only composes plugins; it should not become a surprise sequel to your business logic.