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

@plakboek/permissions

v0.5.1

Published

Frozen permission catalogue and role types for Plakboek CMS installations

Readme

@plakboek/permissions

The fixed permission catalogue and code-defined role system for Plakboek CMS installations.

What this package is

@plakboek/permissions owns a single, frozen catalogue of every permission string Plakboek understands (resource:action, e.g. pages:publish). The catalogue is exported by this package and cannot be extended, renamed, or altered per installation (USER-05) -- it ships with the CMS core, the same for every install. What an installation can configure in code is which role holds which of these permissions (defineRoles), never the catalogue itself.

The package has zero runtime dependencies and does no I/O: everything here is pure, synchronous, in-memory validation and lookup.

Install

pnpm add @plakboek/permissions

Permissions

The table below is generated from the catalogue (PERMISSIONS) and the shipped default roles (defaultRoles) -- see docs:permissions below for how it stays in sync.

| Permission | Group | Description | superadmin | admin | editor | | -------------------------- | ------------- | -------------------------------------------------------------------------- | ---------- | ----- | ------ | | pages:read | pages | See pages and their published content in the admin | ✓ | ✓ | ✓ | | pages:read-drafts | pages | See unpublished drafts, previews and revision history of pages | ✓ | ✓ | ✓ | | pages:create | pages | Add new pages to the site | ✓ | ✓ | ✓ | | pages:edit | pages | Change page content, blocks, layout, SEO fields and translations as drafts | ✓ | ✓ | ✓ | | pages:publish | pages | Publish or schedule page drafts so visitors receive them | ✓ | ✓ | ✓ | | pages:delete | pages | Move pages to the trash and restore them | ✓ | ✓ | ✓ | | pages:delete-permanent | pages | Permanently remove trashed pages | ✓ | | | | entries:read | entries | See content entries and their published content in the admin | ✓ | ✓ | ✓ | | entries:read-drafts | entries | See unpublished drafts, previews and revision history of entries | ✓ | ✓ | ✓ | | entries:create | entries | Add new content entries | ✓ | ✓ | ✓ | | entries:edit | entries | Change entry fields and translations as drafts | ✓ | ✓ | ✓ | | entries:publish | entries | Publish or schedule entry drafts so visitors receive them | ✓ | ✓ | ✓ | | entries:delete | entries | Move entries to the trash and restore them | ✓ | ✓ | ✓ | | entries:delete-permanent | entries | Permanently remove trashed entries | ✓ | | | | templates:edit | templates | Change the shared block tree used to render a content type's detail pages | ✓ | ✓ | ✓ | | templates:publish | templates | Publish changes to shared detail templates | ✓ | ✓ | ✓ | | content-types:read | content-types | See content types and the fields attached to them | ✓ | ✓ | ✓ | | content-types:create | content-types | Add new content types | ✓ | ✓ | | | content-types:edit | content-types | Change content type settings and add, change or remove fields | ✓ | ✓ | | | content-types:delete | content-types | Remove content types | ✓ | ✓ | | | media:read | media | Browse and search the media library | ✓ | ✓ | ✓ | | media:upload | media | Upload images and files | ✓ | ✓ | ✓ | | media:edit | media | Change alt text, copyright labels and replace assets in place | ✓ | ✓ | ✓ | | media:delete | media | Delete assets, including ones still in use after a warning | ✓ | ✓ | | | menus:read | menus | See menus declared by the site and their links | ✓ | ✓ | ✓ | | menus:edit | menus | Add, remove and re-arrange menu links | ✓ | ✓ | ✓ | | redirects:read | seo | See redirects and how long each has been active | ✓ | ✓ | ✓ | | redirects:manage | seo | Create, change and remove redirects | ✓ | ✓ | | | users:read | users | See users and their roles | ✓ | ✓ | | | users:create | users | Add users and send or resend set-password emails | ✓ | ✓ | | | users:edit | users | Change other users' profile details | ✓ | ✓ | | | users:deactivate | users | Deactivate and reactivate users | ✓ | ✓ | | | users:assign-roles | users | Assign a code-defined role to a user | ✓ | ✓ | | | users:reset-password | users | Set or reset another user's password | ✓ | ✓ | | | users:impersonate | users | Act as another user and return to your own session | ✓ | | | | audit-log:read | audit | See the audit log of data-changing actions | ✓ | ✓ | | | settings:read | settings | See installation settings | ✓ | ✓ | | | settings:manage | settings | Change core configuration, styles and rules | ✓ | ✓ | | | modules:manage | settings | Turn installation modules on or off | ✓ | | | | blocks:manage | settings | Turn individual built-in sections and blocks on or off | ✓ | | | | bootstrap:run | settings | Run installation bootstrap steps | ✓ | | | | api-tokens:create | api | Create and revoke your own API tokens for MCP clients | ✓ | ✓ | ✓ | | api-tokens:manage | api | See and revoke any user's API tokens | ✓ | ✓ | | | backups:manage | backups | Configure backup targets and run backups | ✓ | | | | backups:restore | backups | Restore the installation from a backup | ✓ | | |

Defining roles in code

Roles map a role key to a list of permissions, entirely in your installation's own code -- there is no database table and no admin UI for changing what a role can do (only which role a user holds is data, handled elsewhere). The shipped defaults work unmodified; add or replace roles by spreading defaultRoles:

import {
  defineRoles,
  defaultRoles,
  ALL_PERMISSIONS,
} from '@plakboek/permissions';

const roles = defineRoles({
  ...defaultRoles,
  // A host-specific role: exactly these two permissions, nothing inherited.
  client: ['pages:read', 'pages:edit'],
});

superadmin is reserved and required: it must be present and must equal ALL_PERMISSIONS exactly (D-11, D-12) --

defineRoles({
  superadmin: ALL_PERMISSIONS,
  // ...
});

-- defineRoles throws a single RoleConfigError (with a .issues array listing every problem found) if superadmin is missing, narrowed, or if any role references an unknown permission string. There is no inheritance, extends, or wildcard (D-14): a role's permissions are exactly what its list contains, so any role can be audited by reading one array.

Orphaned role keys

A user's stored role key (Phase 2) can outlive the role it once named -- the installation's role config changed, or the value was mistyped upstream. createPermissionResolver never throws for this case: an unknown role key resolves to an empty permission set, and the event is reported once, rate-limited per key, through an injectable hook (D-15):

import { createPermissionResolver } from '@plakboek/permissions';

const resolver = createPermissionResolver(roles, {
  onOrphanedRole(event) {
    // event: { roleKey, userId?, occurredAt, suppressedCount }
  },
  rateLimitMs: 60_000, // default: one warning per role key per 60s
});

resolver.resolve('some-role-key', { userId: user.id });

OrphanedRoleEvent carries only roleKey, the opaque userId (when known), occurredAt, and suppressedCount -- never any other user data. Without a hook, the resolver logs via console.warn. A hook that throws is caught and never propagates into the permission check itself: the empty set is already decided before the hook runs.

Phase 2's boot-time scan of stored role keys and the Phase 10/11 admin banner both consume this exact hook rather than re-implementing orphaned role detection.

Permission supersets

permissionsIncludeAll answers one question: does a resolved permission set contain every permission of another one (D-44)? "Higher" here means superset, not rank -- equal sets qualify. It underlies edit-lock takeover in @plakboek/content and Phase 11's "act only on users/roles whose permissions you already hold" rule, both resolving their two sides through PermissionResolver.resolve before comparing them:

import {
  createPermissionResolver,
  permissionsIncludeAll,
} from '@plakboek/permissions';

const resolver = createPermissionResolver(roles);
const canTakeOver = permissionsIncludeAll(
  resolver.resolve(actorRoleKey),
  resolver.resolve(currentHolderRoleKey),
);

Stability policy

Permission strings are a published contract every host's role config depends on. Adding a permission is a minor version bump. Renaming or removing one is a major version bump -- while the package is pre-1.0, a rename/removal is a minor bump instead -- and the old name is kept as a deprecated alias for one full major version after the rename/removal ships. A host config that still references the old name keeps booting, with a warning, for that entire window; it is never rejected outright.

Security notes

users:assign-roles lets its holder assign any code-defined role to a user -- including roles more privileged than their own. This package does not, and cannot, prevent that: it has no concept of "the currently acting user" or which permissions they hold at the point of assignment. Wherever role assignment is implemented (Phase 11), the handler must refuse to assign a role holding any permission the assigning user does not themselves hold -- otherwise a user with users:assign-roles could grant themselves superadmin (USER-02).