Skip to content
Content
Skill

/permissions

Schema-based permission system for API features. Use this skill when implementing authorization in use cases, defining permission schemas with createPermissionSchema, creating injectable permissions via createPermissionsAbstraction/createPermissionsFeature, checking

BOOST
From plugin
webiny-js
8k76 skills3 MCP
Install
$ npx -y skills add webiny/webiny-js --skill permissions --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/permissions

Context preview

The summary Claude sees to decide when to auto-load this skill.

Schema-based permission system for API features. Use this skill when implementing authorization in use cases, defining permission schemas with createPermissionSchema, creating injectable permissions via createPermissionsAbstraction/createPermissionsFeature, checking

SKILL.md

permissions.SKILL.md
name: webiny-api-permissions
description: >
  Schema-based permission system for API features. Use this skill when implementing
  authorization in use cases, defining permission schemas with createPermissionSchema,
  creating injectable permissions via createPermissionsAbstraction/createPermissionsFeature,
  checking read/write/delete/publish permissions, handling own-record scoping,
  or testing permission scenarios. Covers the full pattern from schema definition
  to use case integration to test matrices.

API Permissions

Overview

Permissions follow two layers: **domain** (schema) and **features** (DI abstractions + feature registration). Each package declares a permission schema and gets a typed `Permissions` abstraction injectable into use cases via DI. Methods like `canRead`, `canEdit`, `canDelete`, `canPublish`, `onlyOwnRecords` replace manual `identityContext.getPermission()` calls.

Layer 1: Domain — Permission Schema

Define the schema in `src/domain/permissionsSchema.ts`:

import { createPermissionSchema } from "webiny/api/security";

export const SM_PERMISSIONS_SCHEMA = createPermissionSchema({
  prefix: "sm",
  fullAccess: true,
  entities: [
    {
      id: "product",
      permission: "sm.product",
      scopes: ["full", "own"],
      actions: [{ name: "rwd" }, { name: "pw" }]
    },
    {
      id: "settings",
      permission: "sm.settings",
      scopes: ["full"]
    }
  ]
});

The schema MUST use `as const` inference (handled by `createPermissionSchema`) for TypeScript to narrow entity IDs in method signatures.

Schema Fields

| Field | Description | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `prefix` | Namespaces the DI abstraction: `${prefix}:Permissions` | | `fullAccess` | `true` for standard full access. Pass an object with custom boolean flags for full-access extras (e.g., `{ canForceUnlock: true }`). | | `entities[].id` | Entity identifier used in method calls: `canRead("product")` | | `entities[].permission` | Permission name matched against identity permissions | | `entities[].scopes` | `["full"]` or `["full", "own"]` — determines if own-scope supported | | `entities[].actions` | Action definitions — built-in: `"rwd"`, `"pw"`; custom: boolean flags |

Scopes

  • **`"full"`** — User can access all records (default when no `own` flag on permission object)
  • **`"own"`** — User can only access records where `createdBy.id === identity.id`

Simple Apps (No Entities)

Omit `entities` for binary full/no access:

export const MA_PERMISSIONS_SCHEMA = createPermissionSchema({
  prefix: "ma",
  fullAccess: true
});

---

Layer 2: Features — DI Artifacts + Registration

Abstraction (`src/features/permissions/abstractions.ts`)

import { createPermissionsAbstraction } from "webiny/api/security";
import type { Permissions } from "webiny/api/security";
import { SM_PERMISSIONS_SCHEMA } from "~/domain/permissionsSchema.js";

export const SmPermissions = createPermissionsAbstraction(SM_PERMISSIONS_SCHEMA);

export namespace SmPermissions {
  export type Interface = Permissions<typeof SM_PERMISSIONS_SCHEMA>;
}

Feature (`src/features/permissions/feature.ts`)

import { createPermissionsFeature } from "webiny/api/security";
import { SM_PERMISSIONS_SCHEMA } from "~/domain/permissionsSchema.js";
import { SmPermissions } from "./abstractions.js";

export const SmPermissionsFeature = createPermissionsFeature(SM_PERMISSIONS_SCHEMA, SmPermissions);

Registration

Register the feature in your context plugin:

import { SmPermissionsFeature } from "~/features/permissions/feature.js";

// In createContext:
SmPermissionsFeature.register(container);

---

File Structure

src/
├── domain/
│   └── permissionsSchema.ts              # createPermissionSchema()
├── features/
│   └── permissions/
│       ├── abstractions.ts               # createPermissionsAbstraction() + namespace type
│       └── feature.ts                    # createPermissionsFeature()
└── index.ts                              # SmPermissionsFeature.register(container)

---

Permission Methods

All methods follow a 3-tier bypass:

1. `identityContext.hasFullAccess()` → `name: "*"` permission (super admin) 2. `hasFullSchemaAccess()` → wildcard permission (e.g. `"sm.*"`) 3. Entity-level permission check

Method Reference

| Method | Purpose | Item-aware | Notes | | --------------------------- | --------------------- | ---------- | --------------------------------------------------------------------------------------------- | | `canAccess(entity, item?)` | General access check | Yes | Without item: checks entity permission exists. With item + `own: true`: checks `createdBy.id` | | `onlyOwnRecords(entity)` | List filter flag | No | Returns `true` when ALL permissions have `own: true` | | `canRead(entity)` | Read permission | No | Checks `rwd` includes `"r"` (or no `rwd` = unrestricted) | | `canCreate(entity)` | Create permission | No | Checks `rwd` includes `"w"`

Read more
Ships withwebiny-js

Open-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.

Get the whole plugin
Stats
8,049
Stars
682
Forks
Active
Maintenance
TypeScript
Language
10h ago
Last commit
8y ago
Created
2d ago
Added

Repo: webiny/webiny-js

Other skills on webiny-js.