How to Migrate Legacy Plugins to the New Manifest Format

To migrate legacy OpenAI plugins, move the existing 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 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 according to the repository README. Begin migration by creating the hidden folder and relocating your legacy file.

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. 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:

{
  "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:

{
  "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), 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: UI configuration for the plugin marketplace (see 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:

// 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:

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 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 path
  • Extend functionality by adding optional .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 within each plugin directory. Legacy plugins with root-level 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 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 from the plugin root to avoid confusion. The repository standard mandates that only the .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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →