# How Instatic Implements Access Control with Its 38-Capability System

> Discover how Instatic's 38-capability system provides fine-grained access control. Explore centralized capabilities and runtime checks for robust security in your application. Learn more!

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-27

---

**Instatic enforces fine-grained permissions through a centralized capability array (`CORE_CAPABILITIES`) defined in [`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts), with runtime checks performed by the `hasCapability()` helper in [`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts) and enforced server-side via middleware in [`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts).**

The Instatic CMS, maintained in the CoreBunch/Instatic repository, employs a comprehensive **38-capability access control model** that governs every permission from site structure editing to AI feature access. This system centralizes all authorization logic in a type-safe registry, ensuring that both the admin interface and backend API endpoints remain synchronized on user permissions.

## The Core Capability Registry

All 38 permissions are enumerated as string constants in **[`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts)**. This file exports `CORE_CAPABILITIES`, a master array that serves as the single source of truth, and derives the TypeScript union type `CoreCapability` from it. The capabilities span domains including **site editing**, **media management**, **runtime configuration**, **storage**, **plugins**, **user and role administration**, **audit logging**, **data workspaces**, and **AI features**.

Adding a new permission requires appending a string to this array; the type system automatically propagates the change to all consumers. The accompanying test in **[`src/__tests__/architecture/capability-picker-coverage.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/architecture/capability-picker-coverage.test.ts)** guarantees that every entry includes UI picker metadata, preventing orphaned capabilities.

## Client-Side Permission Validation

When a user logs in, the server attaches their capability list to the session (handled in **[`server/auth/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/capabilities.ts)**). The admin UI then uses utility helpers from **[`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts)** to determine visibility of actions and navigation elements.

The primary check is the `hasCapability()` function:

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

```

Higher-level predicates build on this foundation for common operations:

- `canEditSiteStructure(user)` → `hasCapability(user, 'site.structure.edit')`
- `canPublishContent(user)` → `hasCapability(user, 'content.publish.any')`
- `canManagePlugins(user)` → `hasCapability(user, 'plugins.install')`

Components like the site summary view in **[`src/admin/state/useSiteSummary.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/state/useSiteSummary.ts)** invoke these helpers to gate data fetching, while **[`src/admin/pages/users/utils/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/users/utils/capabilities.ts)** uses the metadata from `CORE_CAPABILITIES` to disable checkboxes the current user cannot grant.

## Server-Side Enforcement

Authorization is not merely a UI concern. The middleware in **[`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts)** extracts the capability list from the session cookie on every request and validates it against the route's required permission. If the user lacks the capability, the middleware throws an `ApiError`, rejecting the request before it reaches the handler.

Route definitions specify their required capability as a parameter:

```typescript
// server/auth/routes.ts
api.cms.routes.post(
  '/api/media/upload',
  'media.write',               // capability required
  async (req, res) => {
    // handler runs only if the user holds `media.write`
  }
)

```

This ensures that even if a client bypasses UI checks, the backend will reject unauthorized requests targeting sensitive endpoints.

## Plugin Integration

Plugins leverage the same system for their own routes through the SDK defined in **[`src/core/plugin-sdk/types/serverApi.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/serverApi.ts)**. When registering a route, plugins declare which existing capability guards it:

```typescript
api.cms.routes.get(
  '/my-plugin/health',
  'plugins.read',              // only users with this capability can call it
  async (req, res) => { /* ... */ }
)

```

The `api.cms.routes` helper wires these requirements into the router, subjecting plugin endpoints to the same **[`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts)** validation as native CMS routes.

## Summary

- The **`CORE_CAPABILITIES`** array in [`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts) serves as the single source of truth for all 38 permissions, grouped by functional domain.
- The **`hasCapability()`** helper in [`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts) provides the primary client-side check, returning a boolean based on the user's session data.
- Server-side middleware in **[`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts)** validates capabilities on every API request, rejecting unauthorized access with an `ApiError`.
- Plugins register protected routes through `api.cms.routes` with explicit capability requirements defined in **[`src/core/plugin-sdk/types/serverApi.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/serverApi.ts)**, ensuring third-party extensions adhere to the same security model.

## Frequently Asked Questions

### What are the 38 capabilities in Instatic?

The capabilities cover functional domains including site structure editing, content publishing, media management, plugin installation, user and role administration, audit logging, data workspace operations, and AI features. The complete, type-safe list is maintained as the `CORE_CAPABILITIES` constant array in **[`src/core/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/capabilities.ts)**.

### How does Instatic check user permissions in the admin UI?

Admin components import the **`hasCapability()`** function from **[`src/admin/access.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/access.ts)** to conditionally render UI elements. For example, the site summary view checks `hasCapability(currentUser, 'site.read')` before rendering data, while higher-level helpers like `canEditSiteStructure()` wrap this logic for specific editorial actions.

### Can plugins define their own custom capabilities?

While the base system provides 38 core capabilities, plugins register 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)** by specifying which existing capability is required (e.g., `api.cms.routes.get(path, 'plugins.read', handler)`). The middleware in **[`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts)** enforces these requirements regardless of whether the route is native or plugin-provided, ensuring consistent enforcement.

### How is the capability system tested for completeness?

The architecture test suite in **[`src/__tests__/architecture/capability-picker-coverage.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/architecture/capability-picker-coverage.test.ts)** verifies that every string in `CORE_CAPABILITIES` has corresponding metadata for the UI picker component. This guarantees that new capabilities added to the array cannot be assigned to users without interface support, preventing configuration drift.