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
nametoid: Must follow theorg/plugin-nameformat - add
manifest_version: Fixed integer value of1 - restructure
api: Changed from string to object containingtype,url, and optionalauth - nest
auth: Now requires atypefield (none,apiKey, oroauth2) - replace
commandswithactions: Each action requiresname,description, andinput_schemafollowing 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 (seeplugins/close/.app.jsonfor a reference implementation)assets/: Directory for icons, screenshots, and media referenced by the manifestskills/: 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.jsoninto a.codex-plugindirectory to satisfy the current loader requirements defined in the README - Upgrade the schema by adding
manifest_version: 1, renamingnametoid, convertingapito an object, and replacingcommandswith MCP-backedactions - Update all file references in test suites and CI pipelines to point to the new
.codex-plugin/plugin.jsonpath - Extend functionality by adding optional
.app.json,assets/, andskills/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →