Working with Users

Roles

Helper functions and components to work with roles.

Roles in VitNode are stored in the core_roles table. Every user belongs to exactly one role through their roleId, and a role carries the flags and color used for permissions and display.

A role exposes the following flags:

FieldDescription
protectedBuilt-in role that cannot be deleted.
defaultRole assigned to newly registered users.
rootSuper-admin role with full access to everything.
guestRole representing non-authenticated visitors.
colorColor used to style the members of the role (nullable).

Role names are translatable

A role has no name column. Its display name lives in the core_languages_words i18n table (seeded per language during database setup), so it can be translated per locale. Resolve the name for the active language and pass it to the components below.

Backend

Reading a user's role

Every user references a role through roleId. Load the role by querying core_roles with that id:

plugins/{plugin_name}/src/api/routes/role.route.ts
import { eq } from "drizzle-orm";
import { buildRoute } from "@vitnode/core/api/lib/route";
import { core_roles } from "@vitnode/core/database/roles";

export const roleRoute = buildRoute({
  handler: async c => {
    const user = c.get("user");
    if (!user) return c.json({ message: "Not signed in" }, 401);

    const [role] = await c
      .get("db")
      .select()
      .from(core_roles)
      .where(eq(core_roles.id, user.roleId))
      .limit(1);

    return c.json({ role });
  },
});

Checking admin access

Membership in an admin role is resolved with checkIfUserIsAdmin from the SessionAdminModel. It returns true when the user — by their own id or their roleId — has admin permission.

plugins/{plugin_name}/src/api/routes/admin-only.route.ts
import { buildRoute } from "@vitnode/core/api/lib/route";
import { SessionAdminModel } from "@vitnode/core/api/models/session-admin";

export const adminOnlyRoute = buildRoute({
  handler: async c => {
    const user = c.get("user");
    const isAdmin = user
      ? await new SessionAdminModel(c).checkIfUserIsAdmin(user.id)
      : false;

    if (!isAdmin) return c.json({ message: "Forbidden" }, 403);

    return c.json({ ok: true });
  },
});

Frontend

On the frontend a user's role is identified by the roleId carried on the session user, available through getSessionApi().

Formatting a role

Use the RoleFormat component to render a role's name styled with its color. Because role names are translatable, the name is the full list of translations; RoleFormat resolves it to the active locale with getLocale(), so it is an async Server Component.

role-badge.tsx
import { RoleFormat } from "@vitnode/core/components/role-format";

<RoleFormat
  role={{
    id: 2,
    name: [
      { languageCode: "en", name: "Administrator" },
      { languageCode: "pl", name: "Administrator" },
    ],
    color: "#ef4444",
  }}
/>;

The role prop has the following shape:

{
  id: number;
  name: {
    languageCode: string;
    name: string;
  }[];
  color: string | null;
}

Server Component only

RoleFormat calls getLocale() from next-intl/server, so it can only be rendered on the server. The admin roles list route returns the name array in exactly this shape.

Exposing role metadata

Role details such as color and its flags live in the core_roles table on the backend. To display them on the frontend, return the data you need from a backend route and consume it with the fetcher.

On this page