npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@object-ui/permissions

v18.3.0

Published

RBAC permission system for ObjectUI with object/field/row-level access control, permission guards, and hooks.

Readme

@object-ui/permissions

Role-Based Access Control (RBAC) for Object UI — permission guards, field-level access, and policy evaluation.

Features

  • 🔐 PermissionProvider - Context provider for permission-aware applications
  • 🛡️ PermissionGuard - Conditionally render components based on user permissions
  • 🎣 usePermissions - Hook for checking access to actions and resources
  • 📝 Field-Level Permissions - Control visibility and editability per field with useFieldPermissions
  • 🔍 Row-Level Security - Filter data based on permission conditions
  • ⚡ Permission Evaluator - Programmatic permission checking engine
  • 🎯 Type-Safe - Full TypeScript support with exported types

Installation

npm install @object-ui/permissions

Peer Dependencies:

  • react ^19.0.0

Quick Start

import {
  PermissionProvider,
  PermissionGuard,
  usePermissions,
  type ObjectPermissionConfig,
  type RoleDefinition,
} from '@object-ui/permissions';

const roles: RoleDefinition[] = [
  { name: 'admin', label: 'Administrator' },
  { name: 'editor', label: 'Editor' },
];

const permissions: ObjectPermissionConfig[] = [
  {
    object: 'orders',
    roles: {
      editor: { actions: ['read', 'create', 'update'] },
    },
  },
];

function App() {
  return (
    <PermissionProvider roles={roles} permissions={permissions} userRoles={['editor']}>
      <Dashboard />
    </PermissionProvider>
  );
}

function Dashboard() {
  const { can } = usePermissions();

  return (
    <div>
      <h1>Orders</h1>
      <PermissionGuard object="orders" action="create" fallback="custom" fallbackContent={<p>No access</p>}>
        <button>Create Order</button>
      </PermissionGuard>
      {can('orders', 'delete') && <button>Delete</button>}
    </div>
  );
}

Roles are RoleDefinition records (identity and inheritance); what a role may do lives in each object's ObjectPermissionConfig.roles map, and userRoles names the roles the current user actually holds.

API

PermissionProvider

Wraps your application with permission context:

import {
  PermissionProvider,
  type ObjectPermissionConfig,
  type RoleDefinition,
} from '@object-ui/permissions';

declare const roleDefinitions: RoleDefinition[];
declare const permissionMap: ObjectPermissionConfig[];

function App() {
  return <div>Your application</div>;
}

const tree = (
  <PermissionProvider roles={roleDefinitions} permissions={permissionMap} userRoles={['editor']}>
    <App />
  </PermissionProvider>
);

usePermissions

Hook for checking permissions programmatically. can and cannot take the object first and the action second:

import { usePermissions } from '@object-ui/permissions';

function OrderToolbar() {
  const { can, cannot, roles } = usePermissions();

  if (can('orders', 'update')) {
    // allow editing
  }

  return <p>{cannot('orders', 'delete') ? 'Read only' : roles.join(', ')}</p>;
}

useFieldPermissions

Hook for field-level permission checks. It takes the object name, and returns predicates you call per field:

import { useFieldPermissions } from '@object-ui/permissions';

function DiscountField() {
  const { canRead, canWrite } = useFieldPermissions('orders');

  const isVisible = canRead('discount');
  const isEditable = canWrite('discount');

  return isVisible ? <input readOnly={!isEditable} /> : null;
}

checkField — which write a field question is about

usePermissions().checkField(object, field, action) answers three questions. 'read' asks whether the caller may see the field. 'write' asks whether they may change it on an EXISTING record (the server's update rule) and 'create' whether they may set it on a NEW one (its insert rule). The two write questions differ only for a field the permission set does not mention: the server lets such a field through and object admission decides, so 'write' falls back to the object's allowEdit and 'create' to its allowCreate. An explicit field-level entry answers both the same way.

One field's update needs more than its own grant: the record's owner, the field the spec's SystemFieldName constant names OWNER_ID. The server refuses an update that writes it unless the caller holds the transfer grant (allowTransfer, or modifyAllRecords), so MePermissionsProvider answers 'write' for the owner field with that grant as well (objectui#12103). 'create' is unchanged: on insert the server stamps an empty owner to the caller and accepts the caller's own id.

import { usePermissions } from '@object-ui/permissions';

function TitleInput({ mode }: { mode: 'create' | 'edit' }) {
  const { checkField } = usePermissions();
  // A create form asks the create question: a role that may create orders but
  // not edit them can still fill a new order's title.
  const writable = checkField('orders', 'title', mode === 'create' ? 'create' : 'write');
  return <input disabled={!writable} />;
}

Console affordances do not pick the question themselves: they read it from the affordance-to-grant map in @object-ui/core (AFFORDANCE_GRANTS, resolveAffordance, resolveFieldAffordance).

ServerReadScope

MePermissionsProvider's read verdicts — check(object, 'read') and checkField(object, field, 'read') — mirror the data API's rule. Records of some objects reach a page through another door: a service route that admits the caller by its own rule and serves only what that rule allows. A caller the data API keeps out of such an object is absent from the permissions answer for it, both read verdicts fail closed, and the page would hide or strip everything the door served.

Below ServerReadScope, both read verdicts answer "allowed" for the named objects (matched case-insensitively): whatever the server serves is what the viewer may read. Every other action, the field 'write' and 'create' verdicts, getFieldPermissions, effectiveObjects and every other object keep the enclosing provider's answer:

import { ServerReadScope } from '@object-ui/permissions';

function CaseSurfaces({ children }: { children: React.ReactNode }) {
  // Every read of these two objects below goes through a service route that
  // serves each party to a case what it may see, not through the data API.
  return (
    <ServerReadScope objects={['support_case', 'support_case_event']}>
      {children}
    </ServerReadScope>
  );
}

Mount it only over surfaces whose reads of those objects all come back server-filtered. With no enclosing provider it renders its children unchanged.

PermissionGuard

Conditionally renders children based on permissions. fallback selects the denied behaviour, and fallbackContent carries the node rendered for fallback="custom":

import { PermissionGuard } from '@object-ui/permissions';

function DeleteButton() {
  return <button>Delete</button>;
}

const guard = (
  <PermissionGuard object="orders" action="delete" fallback="custom" fallbackContent={<span>Read only</span>}>
    <DeleteButton />
  </PermissionGuard>
);

evaluatePermission

Programmatic permission evaluation:

import {
  evaluatePermission,
  type ObjectPermissionConfig,
  type RoleDefinition,
} from '@object-ui/permissions';

declare const roleDefinitions: RoleDefinition[];
declare const permissionConfig: ObjectPermissionConfig[];

const result = evaluatePermission({
  roles: roleDefinitions,
  permissions: permissionConfig,
  userRoles: ['editor'],
  user: { id: 'user-1', roles: ['editor'] },
  object: 'orders',
  action: 'update',
});

result.allowed; // true | false

createPermissionStore

Creates a permission store for advanced use cases:

import {
  createPermissionStore,
  type ObjectPermissionConfig,
  type RoleDefinition,
} from '@object-ui/permissions';

declare const roleDefinitions: RoleDefinition[];
declare const permissionConfig: ObjectPermissionConfig[];

const store = createPermissionStore({
  roles: roleDefinitions,
  permissions: permissionConfig,
  userRoles: ['editor'],
});

store.check('orders', 'read'); // PermissionCheckResult: { allowed, reason?, ... }

Links

License

MIT — see LICENSE.