# Common Pitfalls to Avoid When Developing OpenAI Plugins: A Complete Guide

> Avoid development failures with OpenAI plugins. Learn to fix missing manifest files, unawaited async calls, and node ID issues for successful plugin creation. Master state, OAuth, and prerequisites.

- Repository: [OpenAI/plugins](https://github.com/openai/plugins)
- Tags: best-practices
- Published: 2026-07-12

---

**The most frequent failures in OpenAI plugin development stem from missing manifest files at `plugins/<name>/.codex-plugin/plugin.json`, unawaited async calls in skills like `use_figma`, and neglecting to return node IDs from mutations.** Successful plugins require strict adherence to the Codex runtime's expectations for state persistence, OAuth token rotation, and skill prerequisites.

Developing OpenAI plugins involves creating **Codex-plugin bundles** that live under `plugins/<name>/` in the openai/plugins repository. Each bundle must contain a valid manifest, optional skills, and proper async handling to avoid hard-to-debug runtime failures. Understanding these common pitfalls to avoid when developing OpenAI plugins early in your workflow prevents silent failures and integration errors that are difficult to trace.

## Manifest and Entry Point Configuration

### Missing or Incorrectly Named Manifest Files

The Codex runtime strictly validates the manifest location. Every plugin must place its configuration exactly at `plugins/<name>/.codex-plugin/plugin.json`. A missing file or misnamed `skills/` folder prevents the plugin from loading entirely, as the runtime checks for this file during initialization.

### Directory Structure Requirements

Reference the skills folder with a trailing slash (`"skills": "./skills/"`) in the manifest. The Zoom plugin provides a correct implementation example at [`plugins/zoom/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/zoom/.codex-plugin/plugin.json), demonstrating the required structure and path formatting.

## Skill Execution and Async Handling

### Prerequisite Skill Loading

Calling a skill like `use_figma` without first loading its prerequisite skill (`figma-use`) causes silent failures. The `figma-use` skill depends on global setup for API typings and page-state handling, as documented in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md). Always load the prerequisite skill before invoking dependent operations.

### Unawaited Promises and Race Conditions

The helper scripts run in an async wrapper. Omitting `await` on Promise-returning calls such as `figma.loadFontAsync` or `figma.setCurrentPageAsync` produces race conditions where changes may never persist. The skill documentation explicitly warns to *"await every Promise"* to ensure deterministic execution.

### Node-ID Return Requirements

The Codex harness only receives data that a skill explicitly returns. You must always return created or mutated node IDs so subsequent calls can reference newly created objects:

```json
{
  "tool": "use_figma",
  "skillNames": "figma-use",
  "code": "await figma.setCurrentPageAsync(page);\nconst rect = figma.createRectangle();\nrect.set({ width: 200, height: 100, fills: [{type:'SOLID',color:{r:0,g:0.5,b:0}}] });\nreturn { createdNodeIds: [rect.id] };"
}

```

This pattern follows the rule in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md) requiring you to **return all created/mutated node IDs**.

### Page Context Management

Switching pages multiple times inside a single `use_figma` script triggers unnecessary reloads and can raise the error "Setting figma.currentPage is not supported". Call `await figma.setCurrentPageAsync(page)` **once per script** and split multi-page work into separate parallel `use_figma` calls to avoid these restrictions.

## OAuth and API Integration Failures

### Refresh Token Rotation

Providers like Zoom and Google rotate refresh tokens on each use, invalidating previous tokens. Assuming refresh tokens remain stable causes authentication failures after the first exchange. Implement token rotation handling as shown in [`plugins/zoom/skills/oauth/references/full-guide.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/references/full-guide.md):

```python
import requests, os

TOKEN_URL = "https://zoom.us/oauth/token"
CLIENT_ID = os.getenv("ZOOM_CLIENT_ID")
CLIENT_SECRET = os.getenv("ZOOM_CLIENT_SECRET")
REFRESH_TOKEN = os.getenv("ZOOM_REFRESH_TOKEN")

def refresh_access():
    resp = requests.post(
        TOKEN_URL,
        auth=(CLIENT_ID, CLIENT_SECRET),
        data={"grant_type": "refresh_token", "refresh_token": REFRESH_TOKEN},
    )
    data = resp.json()
    # Store the new refresh token

    os.environ["ZOOM_REFRESH_TOKEN"] = data["refresh_token"]
    return data["access_token"]

```

### Redirect-URI Mismatches

Deploying a plugin with a callback URL that differs from the provider console registration causes immediate OAuth rejection before any token exchange occurs. Verify the exact URI in the provider's developer console and keep it in sync with [`/.app.json`](https://github.com/openai/plugins/blob/main//.app.json), as detailed in [`plugins/zoom/skills/oauth/troubleshooting/redirect-uri-issues.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/troubleshooting/redirect-uri-issues.md).

### Platform-Specific API Usage

Using APIs only available in one environment (e.g., `figma.createPage()` in FigJam) throws "not supported" errors. Detect the editor type using `figma.editorType` and call only supported methods, following the design-only vs Slides-only API lists in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md).

## Rate Limiting and Pagination

Ignoring pagination tokens or exceeding API limits results in partial data and 429 errors. Always check `next_page_token` and respect `Retry-After` headers as documented in [`plugins/zoom/skills/rest-api/troubleshooting/common-issues.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/troubleshooting/common-issues.md):

```python
import time, requests, os

def fetch_all(endpoint):
    token = os.getenv("ZOOM_ACCESS_TOKEN")
    url = f"https://api.zoom.us/v2/{endpoint}"
    results = []
    while url:
        resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})
        data = resp.json()
        results.extend(data.get("users", []))
        next_token = data.get("next_page_token")
        url = f"https://api.zoom.us/v2/{endpoint}?next_page_token={next_token}" if next_token else None
        if resp.status_code == 429:
            retry = int(resp.headers.get("Retry-After", "1"))
            time.sleep(retry)
    return results

```

## Environment and File System Safety

### File System Side Effects

Creating folders or files without checking existence causes crashes on first run or in CI environments where directories may already exist. Use `os.makedirs(..., exist_ok=True)` or explicit existence checks, as noted in the common pitfalls section of [`plugins/base44/skills/base44-cli/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/base44/skills/base44-cli/SKILL.md).

### Environment Variables and Secrets

Hard-coding API keys or forgetting `.env` files causes runtime errors since secrets are stripped from the repository. Create a `.env` placeholder (`API_KEY=`) and document required variables in the README to prevent launch failures.

### Deprecated Library Imports

Importing deprecated sub-modules (e.g., `@wix/data` instead of `@wix/wix-data-items-sdk`) causes build failures. Follow upgrade notes in skill documentation, such as those in [`plugins/wix/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md`](https://github.com/openai/plugins/blob/main/plugins/wix/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md), to maintain compatible dependencies.

## Summary

- Place the manifest strictly at `plugins/<name>/.codex-plugin/plugin.json` with correct trailing slashes for skill paths.
- Always load prerequisite skills (e.g., `figma-use` before `use_figma`) and await every Promise-returning call.
- Return all created and mutated node IDs from skills; the Codex runtime discards console logs and side-effects.
- Handle OAuth refresh token rotation and verify redirect URIs match provider console settings exactly.
- Implement pagination loops with `next_page_token` and respect `Retry-After` headers for rate limits.
- Guard file system operations with `exist_ok=True` and use environment variables for all secrets to avoid CI failures.

## Frequently Asked Questions

### What happens if I forget to return node IDs from a skill?

The Codex runtime persists only explicit return values. If you omit `createdNodeIds` or `mutatedNodeIds` from your return object, subsequent calls cannot reference those objects, causing "node not found" failures in multi-step workflows.

### Why does my plugin fail to load even though the code looks correct?

The most common cause is a missing manifest at `plugins/<name>/.codex-plugin/plugin.json` or an incorrectly named `skills/` folder. The Codex runtime validates these paths strictly during initialization, and any deviation prevents the plugin from registering.

### How do I handle APIs that return paginated results?

Check for `next_page_token` in the response and construct subsequent requests using that token. Always implement backoff logic that respects `Retry-After` headers when encountering 429 status codes to avoid rate limit blocks.

### Are refresh tokens stable across multiple OAuth requests?

No. Providers like Zoom and Google rotate refresh tokens on each use. You must capture and store the new `refresh_token` returned with each access token refresh to prevent "invalid refresh token" authentication failures.