packages/cli/skills/pikku-permissions/SKILL.md
Use when adding authorization checks to Pikku functions or routes — pikkuPermission, pikkuAuth, per-function permissions, pattern-based permissions, or understanding OR/AND permission logic. TRIGGER when: user wants to restrict who can call a function, check resource ownership, add role-based access, or understand where permission checks belong. DO NOT TRIGGER when: user asks about middleware or request interception (use pikku-middleware), authentication strategies (use pikku-security), or session management.
npx skillsauth add pikkujs/pikku pikku-permissionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ALWAYS put authorization checks in the permissions field of pikkuFunc or pikkuSessionlessFunc — NEVER inside the func body.
This includes: org access checks, repo access checks, role checks, resource ownership, and any other authorization logic. The permissions field runs before func, is visible to the inspector, and is the only place Pikku enforces authorization.
// CORRECT
export const deleteBook = pikkuFunc({
func: async ({ db }, { bookId }) => {
await db.deleteBook(bookId)
},
permissions: {
owner: isBookOwner, // ← authorization here
},
})
// WRONG — permission check inside func body
export const deleteBook = pikkuFunc({
func: async ({ db }, { bookId }, { session }) => {
if (!session) throw new UnauthorizedError() // ← never do this
await db.deleteBook(bookId)
},
})
pikku info permissions --verbose and pikku info functions --verbose to understand what permissions are already defined and applied.src/permissions.ts or domain-specific src/lib/*-permissions.ts file.permissions field on the function, or via addHTTPPermission / addPermission for pattern/tag-based application.pikku tsc to confirm permission checker signatures are correct.pikkuAuth(fn) — Session-Only ChecksUse for checks that only need the session — no request data required.
import { pikkuAuth } from '#pikku'
export const isAuthenticated = pikkuAuth(
async (_services, session) => !!session
)
export const isAdmin = pikkuAuth(
async (_services, session) => session?.role === 'admin'
)
pikkuPermission(fn) — Data-Aware ChecksUse when authorization depends on the actual request data (e.g., resource ownership).
import { pikkuPermission } from '#pikku'
export const isBookOwner = pikkuPermission(
async ({ db }, { bookId }, { session }) => {
const book = await db.getBook(bookId)
return book?.authorId === session?.userId
}
)
export const hasBookAccess = pikkuPermission(
async ({ db }, { bookId }, { session }) => {
return await db.hasAccess(session?.userId, bookId)
}
)
permissions: {
admin: isAdmin, // OR: admins can access
owner: isBookOwner, // OR: owners can access
reviewer: [isAuthenticated, hasBookAccess], // AND: both must pass
}
// Logic: admin OR owner OR (isAuthenticated AND hasBookAccess)
Groups are OR'd. Entries within a group array are AND'd.
export const deleteBook = pikkuFunc({
func: async ({ db }, { bookId }) => {
await db.deleteBook(bookId)
},
permissions: {
admin: isAdmin,
owner: isBookOwner,
},
})
addHTTPPermission)import { addHTTPPermission } from '@pikku/core/http'
addHTTPPermission('/admin/*', { admin: isAdmin })
addPermission)import { addPermission } from '.pikku/pikku-types.gen.js'
addPermission('internal', { machine: isMachineAgent })
// src/permissions.ts
import { pikkuAuth, pikkuPermission } from '#pikku'
export const isAuthenticated = pikkuAuth(
async (_services, session) => !!session
)
export const isAdmin = pikkuAuth(
async (_services, session) => session?.role === 'admin'
)
export const isOrgMember = pikkuPermission(
async ({ db }, { orgId }, { session }) => {
return await db.isMember(session?.userId, orgId)
}
)
// src/functions/org.function.ts
export const deleteOrg = pikkuFunc({
func: async ({ db }, { orgId }) => {
await db.deleteOrg(orgId)
},
permissions: {
admin: isAdmin,
owner: [isAuthenticated, isOrgMember],
},
})
pikku tsc # verify permission checker types are correct
pikku all # regenerate if wirings changed
documentation
Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.
testing
Use when adding any middleware to a Pikku app — global HTTP middleware, tag-scoped middleware (including service-to-service bearer auth), per-route middleware, session-setting middleware, or understanding middleware execution order and priority. TRIGGER when: user wants middleware on some or all routes, machine-to-machine auth, tag-scoped cross-cutting concerns, global interceptors, or middleware priority/order questions. DO NOT TRIGGER when: user asks about permissions/authorization checks (use pikku-permissions), auth strategies like authBearer/authCookie (use pikku-security), or deployment.
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.
development
Make a Pikku frontend work in both English (LTR) and Arabic / right-to-left languages. Direction is derived from the active locale, applied once at the document root, and the layout mirrors itself — but only if styling is written flow-relative (margin-inline-start, text-align: start, Mantine ms/me) instead of left/right. TRIGGER when: adding Arabic (or Hebrew/Farsi/Urdu), asked to "support RTL / right-to-left / bidi / mirror the layout", or writing layout styles in an app that may run RTL. Builds on pikku-i18n (an RTL language is just another locale file). DO NOT TRIGGER for backend functions or for LTR-only copy changes.