# How to Migrate Legacy Plugins to the New Manifest Format

> Easily migrate legacy OpenAI plugins to the new manifest format. Update your schema manifest version and rename name to id for a smoother transition.

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

---

**To migrate legacy OpenAI plugins, move the existing [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) into a `.codex-plugin` directory, upgrade the schema to include `manifest_version: 1` and rename `name` to `id`, then convert legacy `commands` arrays to MCP-backed `actions` with proper JSON Schema input definitions.**

The OpenAI Plugins repository stores each plugin under `plugins/<plugin-name>/`. While legacy plugins relied on a flat [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) at the root directory, the current standard requires a structured manifest inside a `.codex-plugin` folder that supports richer capabilities like MCP-backed actions, asset bundles, and versioned metadata.

## Step 1: Create the .codex-plugin Directory

Every plugin must now contain its manifest at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) according to the repository README. Begin migration by creating the hidden folder and relocating your legacy file.

```text
plugins/<your-plugin>/
├── .codex-plugin/          ← new folder
│   └── plugin.json         ← moved manifest
├── assets/
├── skills/
└── …                      ← other plugin files

```

As documented in the repository structure guidelines, the loader specifically looks for the manifest within this subdirectory rather than the plugin root.

## Step 2: Convert the Manifest Schema

The new manifest schema is defined in [`.agents/skills/plugin-creator/references/plugin-json-spec.md`](https://github.com/openai/plugins/blob/main/.agents/skills/plugin-creator/references/plugin-json-spec.md). This specification introduces several breaking changes from the legacy format:

- **rename `name` to `id`**: Must follow the `org/plugin-name` format
- **add `manifest_version`**: Fixed integer value of `1`
- **restructure `api`**: Changed from string to object containing `type`, `url`, and optional `auth`
- **nest `auth`**: Now requires a `type` field (`none`, `apiKey`, or `oauth2`)
- **replace `commands` with `actions`**: Each action requires `name`, `description`, and `input_schema` following JSON Schema definitions

### Legacy vs. New Schema Comparison

| Legacy Key | New Key | Required Change |
|------------|---------|----------------|
| `name` | `id` | Rename to `org/plugin-name` format |
| `api` | `api` | Convert string URL to object with `type` and `url` |
| `auth` | `auth` | Nest under `type` with specific enum values |
| `commands` | `actions` | Add required `input_schema` for each action |
| — | `manifest_version` | Add integer `1` at top level |

### Example Migration

Legacy [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json):

```json
{
  "name": "zotero",
  "description": "Zotero integration",
  "api": "https://api.zotero.org",
  "auth": {
    "type": "apiKey"
  },
  "commands": [
    {
      "name": "search",
      "description": "Search the library"
    }
  ]
}

```

New [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json):

```json
{
  "manifest_version": 1,
  "id": "openai/zotero",
  "description": "Zotero integration",
  "api": {
    "type": "rest",
    "url": "https://api.zotero.org"
  },
  "auth": {
    "type": "apiKey",
    "required": true
  },
  "actions": [
    {
      "name": "search",
      "description": "Search the library",
      "input_schema": {
        "type": "object",
        "properties": {
          "query": { "type": "string" }
        },
        "required": ["query"]
      }
    }
  ]
}

```

This structure mirrors modern implementations such as the Close plugin ([`plugins/close/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/close/.codex-plugin/plugin.json)), which demonstrates proper MCP action definitions and nested authentication objects.

## Step 3: Update References and Add Companion Files

After relocating the manifest, update any code that loads the plugin configuration. Test harnesses, CI scripts, and validation tools must reference the new path:

- **Old path**: `plugins/<name>/plugin.json`
- **New path**: `plugins/<name>/.codex-plugin/plugin.json`

### Optional Companion Files

The new format supports additional configuration files that legacy plugins lacked:

- **[`.app.json`](https://github.com/openai/plugins/blob/main/.app.json)**: UI configuration for the plugin marketplace (see [`plugins/close/.app.json`](https://github.com/openai/plugins/blob/main/plugins/close/.app.json) for a reference implementation)
- **`assets/`**: Directory for icons, screenshots, and media referenced by the manifest
- **`skills/`**: Directory containing additional agent scripts or skill definitions

These files are automatically discovered by the plugin loader when present in the plugin directory.

## Automated Migration Script

For bulk migrations, use this Node.js script to convert legacy manifests programmatically:

```javascript
// migrate-manifest.js
const fs = require('fs');
const path = require('path');

function migrate(pluginRoot) {
  const oldPath = path.join(pluginRoot, 'plugin.json');
  const newDir = path.join(pluginRoot, '.codex-plugin');
  const newPath = path.join(newDir, 'plugin.json');

  if (!fs.existsSync(oldPath)) {
    console.error('Legacy manifest not found.');
    return;
  }

  const legacy = JSON.parse(fs.readFileSync(oldPath, 'utf8'));

  const newManifest = {
    manifest_version: 1,
    id: `openai/${path.basename(pluginRoot)}`,
    description: legacy.description,
    api: { type: 'rest', url: legacy.api },
    auth: { type: legacy.auth?.type || 'none', required: true },
    actions: (legacy.commands || []).map(c => ({
      name: c.name,
      description: c.description,
      input_schema: { type: 'object', properties: {} }
    }))
  };

  fs.mkdirSync(newDir, { recursive: true });
  fs.writeFileSync(newPath, JSON.stringify(newManifest, null, 2));
  fs.unlinkSync(oldPath);
  console.log(`Migrated ${pluginRoot}`);
}

// Usage: node migrate-manifest.js plugins/<plugin-name>
migrate(process.argv[2]);

```

### Loading the New Manifest (Python)

Update your Python utilities to read from the correct subdirectory:

```python
import json
from pathlib import Path

def load_manifest(plugin_dir: Path):
    manifest_path = plugin_dir / ".codex-plugin" / "plugin.json"
    with manifest_path.open() as f:
        return json.load(f)

# Example usage

manifest = load_manifest(Path("plugins/zotero"))
assert manifest["manifest_version"] == 1

```

## Summary

- **Move** the legacy [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) into a `.codex-plugin` directory to satisfy the current loader requirements defined in the README
- **Upgrade** the schema by adding `manifest_version: 1`, renaming `name` to `id`, converting `api` to an object, and replacing `commands` with MCP-backed `actions`
- **Update** all file references in test suites and CI pipelines to point to the new [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) path
- **Extend** functionality by adding optional [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json), `assets/`, and `skills/` directories as implemented in reference plugins like Close

## Frequently Asked Questions

### What happens if I don't migrate to the new manifest format?

The current OpenAI plugin loader specifically searches for manifests at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) within each plugin directory. Legacy plugins with root-level [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) files will not be discovered or loaded by the system, effectively making them incompatible with current tooling.

### Where is the official schema specification for the new manifest?

The complete specification resides at [`.agents/skills/plugin-creator/references/plugin-json-spec.md`](https://github.com/openai/plugins/blob/main/.agents/skills/plugin-creator/references/plugin-json-spec.md) in the repository. This document defines all required fields, valid enum values for authentication types, and the JSON Schema structure required for action input definitions.

### Can I keep the old plugin.json for backward compatibility?

You should not maintain duplicate manifests. After migration, remove the legacy [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) from the plugin root to avoid confusion. The repository standard mandates that only the [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) path is valid for current implementations.

### How do I convert legacy commands to MCP actions?

Replace the `commands` array with an `actions` array where each object includes `name`, `description`, and `input_schema`. The `input_schema` must be a valid JSON Schema object describing the parameters your action accepts, as required by the MCP protocol implementation in the new loader.