# Instatic's 38-Capability Access Control System: Implementation and Enforcement

> Explore Instatic's 38-capability access control system. Learn how this fine-grained model is implemented and enforced for robust app security. Discover the `hasCapability` function and middleware validation.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-07-30

---

**Instatic implements a fine-grained capability-based access control model that defines 38 distinct permissions in [`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts), enforced through the `hasCapability()` helper in [`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts) and middleware validation in [`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts).**

The CoreBunch/Instatic content management system relies on a comprehensive 38-capability access control system to govern user interactions across the admin interface and API. This capability-based architecture assigns atomic permissions—such as `site.structure.edit` and `content.publish.any`—that combine to create precise access profiles. By centralizing these 38 capabilities in a type-safe registry, Instatic ensures consistent authorization enforcement on both client and server.

## Core Capability Registry and Type Safety

The foundation of Instatic's permission model rests in [`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts), which exports the master list of all 38 capabilities.

### The CORE_CAPABILITIES Constant

This file defines `CORE_CAPABILITIES`, a constant array containing every capability string supported by the system. This array serves as the single source of truth for authorization logic throughout the application, covering domains from site editing and media management to plugin installation and AI features.

### The CoreCapability Type

Derived directly from the `CORE_CAPABILITIES` array, the `CoreCapability` TypeScript type provides compile-time safety. When functions require a specific permission, they accept parameters of type `CoreCapability`, preventing invalid capability strings from entering the system and ensuring that the 38-capability set remains the exclusive vocabulary for access control.

## Client-Side Permission Checking with hasCapability()

All UI authorization logic flows through utility functions defined in [`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts).

### The hasCapability() Function

The primary validation helper checks user sessions against required permissions:

```typescript
export function hasCapability(user: CmsCurrentUser | null, capability: CoreCapability): boolean {
  return user?.capabilities?.includes(capability) ?? false
}

```

### Semantic Permission Predicates

Built atop `hasCapability()`, higher-level predicates clarify intent throughout the admin codebase:

- `canEditSiteStructure(user)` validates `site.structure.edit`
- `canPublishContent(user)` validates `content.publish.any`
- `canManagePlugins(user)` validates `plugins.install`

### Conditional UI Rendering Examples

The admin interface uses these helpers to control visibility. In [`src/admin/state/useSiteSummary.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/state/useSiteSummary.ts), the application checks `hasCapability(currentUser, 'site.read')` before rendering site data. The capability picker in [`src/admin/pages/users/utils/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/users/utils/capabilities.ts) references `CORE_CAPABILITIES` metadata to disable checkboxes for permissions the current user cannot grant, ensuring the 38-capability system governs even the permission-assignment interface itself.

## Server-Side Enforcement Architecture

### Session Capability Attachment

During authentication, [`server/auth/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/capabilities.ts) queries the database to retrieve a user's assigned capabilities from the 38-capability set and attaches them to the session object. This makes the permission data available for subsequent request authorization without repeated database lookups.

### AuthZ Middleware Protection

The router middleware in [`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts) extracts capabilities from the encrypted session cookie and validates them against route requirements. If a request lacks the declared capability—such as `media.write` for upload endpoints—the middleware throws an `ApiError` and terminates the request before reaching the handler. This ensures that client-side bypass attempts cannot compromise the 38-capability access control boundaries.

## Plugin SDK and Capability Integration

The plugin architecture extends the 38-capability system to third-party extensions through [`src/core/plugin-sdk/types/serverApi.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/serverApi.ts).

### Route-Level Capability Requirements

Plugin routes declare required capabilities using the `api.cms.routes` methods, ensuring third-party code respects the same authorization boundaries:

```typescript
api.cms.routes.get(path, capability, handler)

```

This signature allows plugins to specify which of the 38 capabilities—such as `plugins.read` or `plugins.install`—are required to access their functionality.

## Practical Code Examples

Checking permissions in admin UI components:

```typescript
import { hasCapability } from '@admin/access'

if (hasCapability(currentUser, 'media.write')) {
  // render "Upload Media" button
}

```

Protecting API endpoints with capability requirements:

```typescript
// server/auth/routes.ts
api.cms.routes.post(
  '/api/media/upload',
  'media.write',               // required capability from the 38-capability set
  async (req, res) => {
    // handler executes only for authorized users
  }
)

```

Plugin route registration with capability gating:

```typescript
// src/core/plugin-sdk/types/serverApi.ts
api.cms.routes.get(
  '/my-plugin/health',
  'plugins.read',              // gates access to plugin functionality
  async (req, res) => { /* ... */ }
)

```

## Summary

- The **38-capability access control system** is centralized in [`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts) via the `CORE_CAPABILITIES` array and `CoreCapability` type.
- **Client-side checks** use the `hasCapability()` helper in [`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts) to toggle UI elements based on permissions like `site.read` or `content.publish.any`.
- **Server-side enforcement** occurs in [`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts), which validates session-attached capabilities before executing route handlers.
- **Semantic predicates** such as `canEditSiteStructure()` abstract common permission patterns for cleaner business logic.
- The **plugin SDK** integrates with the capability system through [`src/core/plugin-sdk/types/serverApi.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/serverApi.ts), requiring explicit capability declarations for all plugin routes.
- **Test coverage** in [`capability-picker-coverage.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/capability-picker-coverage.test.ts) ensures every capability maintains appropriate UI picker metadata.

## Frequently Asked Questions

### Where are the 38 capabilities defined in Instatic?

The complete set of 38 capabilities is defined as the `CORE_CAPABILITIES` constant array in [`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts). This file also exports the `CoreCapability` TypeScript type, which provides compile-time validation by restricting strings to those present in the array.

### How does the hasCapability() function work?

Located in [`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts), `hasCapability()` accepts a `CmsCurrentUser` object and a `CoreCapability` string. It returns `true` if the user's capabilities array includes the requested permission, powering both individual checks and higher-level predicates like `canManagePlugins()`.

### What prevents API access if UI capability checks are disabled?

Server-side middleware in [`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts) performs canonical authorization by extracting capabilities from the session cookie and comparing them against route requirements. If the user lacks the required capability—such as `plugins.install` for plugin management—the middleware rejects the request with an `ApiError` before the handler executes.

### How do plugins interact with the 38-capability system?

Plugins declare required capabilities when registering routes through the SDK in [`src/core/plugin-sdk/types/serverApi.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/serverApi.ts). The `api.cms.routes` methods accept a capability parameter (e.g., `plugins.read`) that the auth middleware enforces, ensuring third-party extensions respect the same access control boundaries as core functionality.