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

> Avoid common pitfalls when building OpenAI plugins. Learn to validate manifests, manage async calls, return IDs, and handle OAuth tokens for robust plugin development.

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

---

**Always validate your manifest structure, await every asynchronous call, return all mutated node IDs, and handle rotating OAuth tokens to prevent silent failures and runtime errors in OpenAI plugins.**

OpenAI plugins are built as **Codex-plugin bundles** within the `openai/plugins` repository, requiring strict adherence to manifest validation, skill prerequisites, and runtime constraints. Each plugin lives under `plugins/<name>/` and contains a manifest ([`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json)), optional skills, agents, and commands. Understanding the architectural traps documented across the Figma, Zoom, and Base44 implementations prevents hard-to-debug failures.

## Manifest and Entry Point Validation

The Codex runtime validates the plugin manifest strictly. A missing file or incorrect path stops the plugin from loading entirely.

### Misconfigured Plugin.json Files

Forgetting the required [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) or mis-naming the `skills/` folder in the manifest will prevent the plugin from loading. The Codex runtime expects the manifest exactly at `plugins/<name>/.codex-plugin/plugin.json`.

When referencing the skills folder, always use a trailing slash: `"skills": "./skills/"`. The Zoom plugin provides a correct example in its manifest at [`plugins/zoom/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/zoom/.codex-plugin/plugin.json).

## Skill Execution and State Management

Skills are defined in markdown files (typically [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md)) that document public APIs, prerequisites, and gotchas. The runtime executes skill code inside a sandbox where **state is persisted only through explicit returns**.

### Missing Skill Prerequisites

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. Always load the prerequisite skill first, as documented in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md).

### Async Handling Errors

Omitting `await` on Promise-returning calls (e.g., `figma.loadFontAsync`, `figma.setCurrentPageAsync`) produces race conditions. The helper scripts run in an async wrapper; un-awaited Promises fire-and-forget, causing missing changes. The skill documentation explicitly requires you to *“await every Promise”* as noted in the `figma-use` rules.

### Node-ID Management Failures

Not returning created or mutated node IDs from `use_figma` breaks subsequent calls. The Codex harness only receives data that a skill returns; without IDs, later calls cannot reference newly created objects. Always return the specific structure:

```json
{
  "createdNodeIds": [...],
  "mutatedNodeIds": [...]
}

```

This requirement is documented in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md) under section 1.5.

### Page Context Violations

Switching pages multiple times inside a single `use_figma` script causes unnecessary reloads and triggers the error *“Setting figma.currentPage is not supported”*. Each script reloads the page once. Call `await figma.setCurrentPageAsync(page)` **once per script** and split multi-page work into separate parallel `use_figma` calls.

## OAuth and API Integration Pitfalls

OAuth flows and REST API interactions present specific failure modes around token lifecycle and pagination.

### Token Lifecycle Assumptions

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

```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()
    # Zoom returns a new refresh token each time

    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 one registered in the provider console causes OAuth flows to reject the request 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). See [`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) for debugging steps.

### Rate Limits and Pagination

Ignoring pagination tokens or exceeding API rate limits results in incomplete data or throttled requests. Many services return partial results and enforce limits per minute. Check the `next_page_token` in responses and respect `Retry-After` headers when receiving 429 status codes.

```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}"})
        if resp.status_code == 429:
            retry = int(resp.headers.get("Retry-After", "1"))
            time.sleep(retry)
            continue
        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
    return results

```

This pattern follows the guidance 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).

## Runtime and Environment Constraints

Plugins execute across multiple runtimes (Python helpers, JavaScript SDKs, OAuth flows), requiring careful handling of file systems and platform capabilities.

### File System Side Effects

Creating folders or files without checking existence causes crashes on first run or in CI environments where directories already exist. Guard against existing paths or use `os.makedirs(..., exist_ok=True)` in Python helpers. The `base44-cli` skill documents this in [`plugins/base44/skills/base44-cli/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/base44/skills/base44-cli/SKILL.md).

### Platform-Specific API Usage

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

### Environment Variable and Dependency Issues

Hard-coding API keys or forgetting to add them to a `.env` file causes runtime errors, as secrets are stripped from the repository. Create a `.env` placeholder (`API_KEY=`) and document it in the plugin's README. Additionally, 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).

## Summary

- **Validate manifest structure**: Keep [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) at the root and reference `skills/` with a trailing slash.
- **Await every async call**: Un-awaited Promises in JavaScript skills create race conditions and missing state changes.
- **Return all node IDs**: Always include `createdNodeIds` and `mutatedNodeIds` in return values for Figma skills.
- **Handle OAuth rotation**: Store new refresh tokens immediately, as providers like Zoom invalidate old ones after use.
- **Respect rate limits**: Check `next_page_token` for pagination and `Retry-After` headers for throttling.
- **Guard file operations**: Use `exist_ok=True` or existence checks before creating directories.
- **Check platform APIs**: Verify `figma.editorType` before calling environment-specific methods.

## Frequently Asked Questions

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

The Codex harness cannot track objects created during skill execution. Without returning `createdNodeIds` or `mutatedNodeIds` in the skill response, subsequent commands cannot reference those objects, effectively making them unreachable in the conversation context. Always return these IDs as specified in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md).

### Why does my Zoom plugin fail after the first OAuth refresh?

Zoom rotates refresh tokens on every use, invalidating the previous token. If your code stores the original refresh token and reuses it, subsequent authentication attempts will fail. You must capture and store the new `refresh_token` from each token response, as shown in the Zoom OAuth reference at [`plugins/zoom/skills/oauth/references/full-guide.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/references/full-guide.md).

### How do I handle rate limiting in OpenAI plugin API calls?

Implement exponential backoff using the `Retry-After` header when receiving 429 status codes. For paginated endpoints, always check for `next_page_token` in the response and continue fetching until no token remains. This pattern is 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).

### Can I use FigJam-specific APIs in a standard Figma design file?

No. APIs like `figma.createPage()` are restricted to specific editor types. You must check `figma.editorType` before calling environment-specific methods, or the runtime will throw a "not supported" error. Consult the editor mode documentation in [`plugins/figma/skills/figma-use/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/figma/skills/figma-use/SKILL.md) for the API compatibility matrix.