Working with Users

Staff Permissions

Declare granular staff permissions in VitNode plugins, enforce them on Hono routes, and gate AdminCP UI controls.

VitNode separates staff into two groups: Moderators and Administrators. Plugins declare granular permissions that restrict what staff members or roles can access.

New Admin APIs Require Staff Permissions

Routes under /admin/ are automatically gated by the admin session, but you must declare an explicit staff permission tuple to restrict specific actions.

Quick start

1. Declare Permissions in Plugin API Config

plugins/blog/src/config.api.ts
import { buildApiPlugin } from "@vitnode/core/api/lib/plugin"

export const blogApiPlugin = () =>
  buildApiPlugin({
    pluginId: "@vitnode/blog",
    permissionStaff: {
      admin: {
        posts: [
          "can_view",
          { permission: "can_delete", dependsOn: ["can_view"] },
        ],
      },
    },
  })

2. Enforce on Hono Route

Add adminStaffPermission to automatically return 403 Forbidden if unauthorized:

plugins/blog/src/api/modules/admin/posts/routes/delete.route.ts
export const deletePostRoute = buildRoute({
  pluginId: "@vitnode/blog",
  adminStaffPermission: { module: "posts", permission: "can_delete" },
  route: {
    method: "delete",
    path: "/{id}",
    responses: {
      200: { description: "Post deleted" },
      403: { description: "Forbidden" },
    },
  },
  handler: async (c) => {
    // Execution only reaches here if caller has permission
  },
})

Add Permissions Step by Step

1. Define Translation Labels

Add flat labels in plugins/blog/src/locales/en.json:

plugins/blog/src/locales/en.json
{
  "@vitnode/blog": { "title": "Blog" },
  "@vitnode/blog:posts": "Articles",
  "@vitnode/blog:posts:can_view": "View articles list",
  "@vitnode/blog:posts:can_create": "Create articles",
  "@vitnode/blog:posts:can_delete": "Delete articles"
}

2. Gate UI Controls

Hide buttons or panels from unauthorized staff with AdminStaffPermissionGate:

plugins/blog/src/admin/delete-post-button.tsx
import { AdminStaffPermissionGate } from "@vitnode/core/components/staff-permission/provider"

export const DeletePostButton = () => (
  <AdminStaffPermissionGate
    plugin="@vitnode/blog"
    module="posts"
    permission="can_delete"
  >
    <button className="text-destructive">Delete</button>
  </AdminStaffPermissionGate>
)

Or check permissions imperatively with useStaffPermissions:

const { hasPermission } = useStaffPermissions()
const canDelete = hasPermission({ module: "posts", permission: "can_delete" })

3. Gate Sidebar Navigation

Attach permission to navigation items in src/admin/nav.tsx:

plugins/blog/src/admin/nav.tsx
export const adminNav: PluginAdminNav = {
  nav: [
    {
      id: "posts",
      href: "/admin/blog/posts",
      icon: <ListIcon />,
      permission: { module: "posts", permission: "can_view" }, 
    },
  ],
}

4. Grant Permissions in AdminCP

  1. Go to Staff → Administrators (/admin/core/staff/admins).
  2. Select a User or Role and click Edit.
  3. Choose Restricted, toggle the desired permissions, and click Save Changes.

Permission Dependencies

When a permission depends on another (e.g. creating posts requires viewing them), declare dependsOn:

plugins/blog/src/config.api.ts
permissionStaff: {
  admin: {
    posts: [
      "can_view",
      { permission: "can_create", dependsOn: ["can_view"] },
      { permission: "can_delete", dependsOn: ["can_view"] },
    ],
  },
}

The AdminCP permission editor automatically disables child toggles until prerequisites are checked.

Learn More