React
@vperms/react re-exports the client factory plus
React bindings. Importing @vperms/react resolves to the server entry under
the react-server condition and to the client entry otherwise, so the same
import works in both environments.
import { createVPerms } from "@vperms/react";
export const vperms = createVPerms("https://api.example.com", { subjectResolver: async () => currentUser,});Server Components
Section titled “Server Components”createVPerms(origin, config) returns a ServerVPerms whose getResolvedSubject
and getAbility are wrapped in a request/render-scoped cache (React.cache by
default). Every Server Component in the same render reuses one resolved subject.
import { vperms } from "@/permissions";
export default async function Page() { const ability = await vperms.getAbility();
if (!ability.can("dashboard.read")) return null;
return ( <vperms.Provider> <vperms.Ability permission="admin.read" fallback={<NoAccess />}> <Dashboard /> </vperms.Ability> </vperms.Provider> );}interface ServerVPerms { client: VPermsClient; getResolvedSubject(subjectId?): Promise<ResolvedSubject>; getAbility(subjectId?): Promise<PermissionAbility>; Provider: (props: { children?: ReactNode }) => Promise<ReactNode>; Ability: (props: AbilityProps) => Promise<ReactNode>;}The most recently created instance also becomes the module default used by the
standalone exports getAbility, getResolvedSubject, Provider and
ServerAbility (alias Ability). Calling them without an instance throws
MISSING_INSTANCE_MESSAGE.
The server Provider resolves the subject once and hands the JSON-safe snapshot
to the client AbilityProvider — the class instance never crosses the RSC
boundary.
Client Components
Section titled “Client Components”"use client";
import { vperms } from "@/permissions";
function CreateButton() { const ability = vperms.useAbility();
return ability.can("projects.create") ? <button>Create</button> : null;}createVPerms on the client returns a ClientVPerms:
interface ClientVPerms { client: VPermsClient; getResolvedSubject: VPermsClient["getResolvedSubject"]; getAbility: VPermsClient["getAbility"]; Provider: typeof AbilityProvider; Ability: typeof ClientAbility; useAbility: typeof useAbility;}useAbility() throws MISSING_PROVIDER_MESSAGE outside a provider.
AbilityProvider takes a ResolvedSubject snapshot and hydrates the ability
synchronously for the subtree.
<Ability> props
Section titled “<Ability> props”interface AbilityProps { permission?: string; permissions?: string[]; any?: boolean; fallback?: ReactNode; children?: ReactNode;}permission and permissions require every permission by default; set
any to require at least one. Both short-circuit, and fallback renders when
access is denied. abilityAllows(ability, props) is exported if you need the
same logic without rendering.
<vperms.Ability permissions={["a.read", "b.read"]} any fallback={<NoAccess />}> <Content /></vperms.Ability>Exports
Section titled “Exports”- Server:
createVPerms,getAbility,getResolvedSubject,Provider,ServerAbility(aliasAbility),abilityAllows,MISSING_INSTANCE_MESSAGE, plus the client’screateAbility,fetchResolvedSubject,PermissionAbility,VPermsHttpErrorand types. - Client:
createVPerms,AbilityProvider,ClientAbility(aliasAbility),getAbilityContext,useAbility,MISSING_PROVIDER_MESSAGE, plus the same client re-exports.
ServerAbility and ClientAbility are exported explicitly for advanced use.
Security
Section titled “Security”Client-side permissions are for conditional rendering and UX only. They are not authoritative authorization and client state must never be trusted. Every sensitive operation must still be authorized on the server with VeguiPerms.