# How to Implement Read/Write/Interactive Capabilities in OpenAI Codex Plugins

> Implement Read/Write/Interactive capabilities in OpenAI Codex plugins by declaring permissions in plugin.json and enforcing them with ensureCapability. Learn how to build robust plugins.

- Repository: [OpenAI/plugins](https://github.com/openai/plugins)
- Tags: how-to-guide
- Published: 2026-06-16

---

**To implement Read/Write/Interactive capabilities in plugins, declare the required permissions in the `capabilities` array of your [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) manifest and enforce them at runtime using the `ensureCapability()` function from `@openai/plugin-runtime`.**

Implementing Read/Write/Interactive capabilities in plugins requires understanding the permission model defined in the openai/plugins repository. These three capability types control whether a Codex plugin can retrieve data, modify resources, or prompt users for additional input during execution.

## Understanding the Three Capability Types

The Codex runtime recognizes three distinct permission levels that govern what actions a plugin can perform on external services.

### Read Capability

The **Read** capability grants a plugin permission to retrieve data without mutating resources. This is the most restrictive permission level, suitable for operations like fetching calendar events, reading documents, or pulling analytics data. When you implement Read/Write/Interactive capabilities in plugins that only retrieve information, declare only `["Read"]` in your manifest.

### Write Capability

The **Write** capability allows a plugin to create, update, or delete resources on the target service. Any skill that adds spreadsheet rows, creates tickets, or uploads files requires this permission. The runtime strictly enforces that mutations cannot occur without an explicit Write declaration in the manifest.

### Interactive Capability

The **Interactive** capability enables a plugin to prompt users for additional input during a session, such as requesting confirmation before a write operation or presenting UI widgets. This capability is required for any skill that needs user interaction beyond the initial prompt.

## Declaring Capabilities in the Plugin Manifest

The `capabilities` array resides in the `interface` object of your [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) file. The Codex runtime parses this manifest to determine which operations a plugin may perform.

For example, the minimal-plugin fixture declares both Interactive and Write capabilities in [`plugins/plugin-eval/fixtures/minimal-plugin/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/plugin-eval/fixtures/minimal-plugin/.codex-plugin/plugin.json):

```json
{
  "interface": {
    "capabilities": ["Interactive", "Write"]
  }
}

```

Production plugins follow similar patterns. The Twilio developer-kit plugin, located at [`plugins/twilio-developer-kit/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/twilio-developer-kit/.codex-plugin/plugin.json), lists Read and Write capabilities:

```json
{
  "interface": {
    "capabilities": ["Read", "Write"]
  }
}

```

## Enforcing Capabilities at Runtime

The Codex runtime automatically blocks any operation that exceeds the declared capabilities. To implement Read/Write/Interactive capabilities in plugins safely, use the `ensureCapability()` function to explicitly check permissions before executing sensitive operations.

### Write Operations

Before creating or modifying resources, verify the Write capability:

```typescript
// src/skills/updateRecord.ts
import { ensureCapability } from "@openai/plugin-runtime";

export async function updateRecord(id: string, payload: Record<string, any>) {
  // Runtime rejects this call if the plugin lacks Write
  await ensureCapability("Write");
  
  const result = await myServiceApi.put(`/records/${id}`, payload);
  return result;
}

```

### Read Operations

For read-only operations, enforce the Read capability:

```typescript
// src/skills/getRecord.ts
import { ensureCapability } from "@openai/plugin-runtime";

export async function getRecord(id: string) {
  await ensureCapability("Read");
  const data = await myServiceApi.get(`/records/${id}`);
  return data;
}

```

### Interactive Workflows

When requiring user confirmation, check for the Interactive capability:

```typescript
// src/skills/updateRecord.ts
import { ensureCapability } from "@openai/plugin-runtime";

export async function updateRecord(id: string, payload: Record<string, any>) {
  await ensureCapability("Write");
  
  // Optional interactive confirmation
  if (await ensureCapability("Interactive")) {
    const confirmed = await askUser(`Overwrite record ${id}?`);
    if (!confirmed) return { status: "cancelled" };
  }
  
  const result = await myServiceApi.put(`/records/${id}`, payload);
  return result;
}

```

## Capability Design Best Practices

When you implement Read/Write/Interactive capabilities in plugins according to the openai/plugins source code, follow these principles:

- **Request minimal permissions**: Only declare capabilities your plugin actually needs. The principle of least privilege reduces security risk and user friction.
- **Separate read and write skills**: Design read-only skills that can be reused across plugins without requiring Write permissions.
- **Use Interactive sparingly**: Mark Interactive only when skills truly require user input during execution, as unnecessary prompts increase friction.
- **Document capabilities**: Include capability requirements in your skill's README, as demonstrated in [`plugins/zoom/skills/zoom-apps-sdk/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/SKILL.md), so downstream developers understand the permission model.

## Summary

- Declare **Read**, **Write**, and **Interactive** capabilities in the `capabilities` array of [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json).
- The minimal-plugin fixture at [`plugins/plugin-eval/fixtures/minimal-plugin/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/plugin-eval/fixtures/minimal-plugin/.codex-plugin/plugin.json) demonstrates Interactive and Write declarations.
- The Twilio plugin at [`plugins/twilio-developer-kit/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/twilio-developer-kit/.codex-plugin/plugin.json) shows Read and Write combinations.
- Enforce capabilities at runtime using `ensureCapability()` from `@openai/plugin-runtime`.
- Always validate Write permissions before mutations and Interactive permissions before user prompts.
- Follow the principle of least privilege by requesting only necessary capabilities.

## Frequently Asked Questions

### What happens if a plugin attempts a write without the Write capability?

The Codex runtime blocks the operation and returns a "capability missing" error. The `ensureCapability("Write")` call will fail before your code executes the API request, preventing unauthorized mutations.

### Can a plugin declare all three capabilities simultaneously?

Yes. You can declare `"capabilities": ["Read", "Write", "Interactive"]` in your manifest if your plugin requires all permission types. However, best practices recommend declaring only the minimal set needed for your specific use case.

### Where is the capability enforcement logic implemented in the openai/plugins repository?

The enforcement logic is built into the core plugin framework within the generic plugin loader in `plugins/**/.codex-plugin/`. While the runtime handles checks automatically, the explicit `ensureCapability()` calls in your skills make the requirements transparent in logs and error messages.

### How do I implement user confirmation prompts in a skill?

To create an interactive skill, first declare "Interactive" in your [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) capabilities array. Then use `await ensureCapability("Interactive")` in your skill code to verify you can prompt the user, followed by functions like `askUser()` to request confirmation or additional input.