How to Add Custom Commands to an OpenAI Codex Plugin: A Complete Guide

Add custom commands to an OpenAI Codex plugin by defining them in the .codex-plugin/plugin.json manifest, implementing executable scripts that read JSON from stdin and write JSON to stdout, and validating the configuration against the plugin-json-spec.

Custom commands are the primary mechanism for extending OpenAI Codex with external capabilities. In the openai/plugins repository, these commands are declared in a JSON manifest and backed by executable scripts that handle the actual logic. This guide walks through the exact structure required to add custom commands to an OpenAI Codex plugin, referencing the official source code and implementation patterns used in production plugins like Mem and Zotero.

Defining Commands in the Plugin Manifest

Every Codex plugin centers around a manifest file located at .codex-plugin/plugin.json within your plugin directory. This JSON file follows the plugin-json-spec and contains a commands array where each custom command is declared.

According to the reference schema in .agents/skills/plugin-creator/references/plugin-json-spec.md, the manifest structure looks like this:

{
  "name": "my-plugin",
  "version": "0.1.0",
  "description": "Example plugin showing custom commands.",
  "interface": {
    "displayName": "My Plugin",
    "shortDescription": "Demo of custom commands",
    "category": "Productivity"
  },
  "commands": [
    {
      "name": "my-command",
      "description": "Summarize a URL and extract key bullet points.",
      "script": "./scripts/my-command.py",
      "input": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "Web page to summarize." }
        },
        "required": ["url"]
      },
      "output": {
        "type": "object",
        "properties": {
          "summary": { "type": "string", "description": "Generated summary." }
        }
      }
    }
  ]
}

The Commands Array Schema

Each command object in the commands array requires specific fields that define the contract between the model and your code:

  • name – The unique identifier used by Codex to invoke the command (e.g., my-command).
  • description – Human-readable text shown in the UI and used by the model to decide when to call the command.
  • script – Relative path from the plugin root to the executable script (e.g., ./scripts/my-command.py).
  • input – JSON Schema defining the arguments the model must provide.
  • output – JSON Schema describing the data structure returned to the model.
  • auth (optional) – Authentication requirements such as OAuth scopes.
  • rateLimit (optional) – Throttling configuration for API calls.

For a concrete implementation example, see the Mem plugin manifest at plugins/mem/.codex-plugin/plugin.json in the repository.

Implementing the Command Script

The runtime contract is strict: scripts receive arguments via STDIN as a JSON object conforming to the input schema, and must write a JSON object to STDOUT matching the output schema. Exit status 0 indicates success; any non-zero status is treated as a failure and the error text is reported back to the model.

Python Implementation Example

Here is a complete implementation that follows the stdin/stdout contract used by the Codex runtime:

#!/usr/bin/env python3
import json
import sys
import requests
from bs4 import BeautifulSoup

def main():
    # Read arguments from stdin

    payload = json.load(sys.stdin)
    url = payload["url"]

    # Do the work (simple HTML extraction)

    resp = requests.get(url, timeout=10)
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, "html.parser")
    # Grab first three <p> elements as a naive summary

    paragraphs = [p.get_text(strip=True) for p in soup.find_all("p")[:3]]
    summary = "\n\n".join(paragraphs)

    # Emit result to stdout

    result = {"summary": summary}
    json.dump(result, sys.stdout)

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        # Return a non-zero exit code + error message for Codex handling

        sys.stderr.write(str(e))
        sys.exit(1)

Make the script executable before testing:

chmod +x plugins/my-plugin/scripts/my-command.py

For a real-world reference showing multiple sub-commands and error handling, examine plugins/zotero/skills/zotero/scripts/zotero.py lines 730-740 in the source repository.

Registering Commands in the Codex Marketplace

To make your command appear in a Codex Marketplace listing, add an entry to the marketplace JSON file:

{
  "name": "my-plugin",
  "source": { "source": "local", "path": "./plugins/my-plugin" },
  "policy": {
    "installation": "AVAILABLE",
    "authentication": "ON_INSTALL"
  },
  "category": "Productivity"
}

Personal plugins use ~/.agents/plugins/marketplace.json, while repository-wide plugins use <repo-root>/.agents/plugins/marketplace.json. The marketplace workflow is documented in .agents/skills/plugin-creator/SKILL.md.

Testing and Validation

Before deploying, validate your plugin structure and test the command manually.

Validating the Manifest

Run the quick-validator shipped with the repository:

python3 .agents/skills/plugin-creator/scripts/quick_validate.py plugins/my-plugin

This script checks your plugin.json against the official schema and catches common configuration errors.

Local Command Testing

Test the script manually to confirm JSON I/O works correctly:

echo '{"url":"https://example.com"}' | \
python3 plugins/my-plugin/scripts/my-command.py

Expected output:

{"summary":"First paragraph text...\n\nSecond paragraph text...\n\nThird paragraph text..."}

Once manual testing passes, start the Codex runtime pointing at your plugin directory to invoke the command through the model.

Summary

Frequently Asked Questions

What file format does the OpenAI Codex plugin manifest use?

The manifest uses standard JSON and must be named plugin.json and placed inside a .codex-plugin/ directory at your plugin root. The file follows the plugin-json-spec schema, which defines required fields like name, version, and the commands array.

How does a Codex plugin script receive arguments from the model?

Scripts receive arguments via standard input (STDIN) as a JSON object that conforms to the input schema defined in the manifest. The script must parse this JSON, execute the logic, and return results by writing a JSON object to standard output (STDOUT).

Where can I find the official schema definition for Codex plugin commands?

The official schema reference is located at .agents/skills/plugin-creator/references/plugin-json-spec.md in the openai/plugins repository. This document defines all valid fields for the manifest, including command definitions, authentication requirements, and rate limiting options.

How do I make my custom command appear in the Codex Marketplace?

Add a marketplace entry to either ~/.agents/plugins/marketplace.json (for personal plugins) or <repo-root>/.agents/plugins/marketplace.json (for repository-wide plugins). The entry must reference your plugin's source path and specify installation policies as documented in .agents/skills/plugin-creator/SKILL.md.

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 →