How to Add Commands to an OpenAI Plugin: A Complete Developer Guide

Adding commands to an OpenAI plugin requires three steps: documenting the command in SKILL.md, implementing the logic in the scripts directory using argparse sub-commands, and updating the plugin manifest if new runtime dependencies are required.

OpenAI plugins follow a modular architecture where human-readable descriptions and machine-executable code are stored separately. In the openai/plugins repository, each plugin lives in its own folder (e.g., plugins/zotero) and contains specific files that the Codex runtime uses to discover and execute functionality. Understanding how to extend these files allows you to add new capabilities that the model can automatically discover and invoke.

Understanding the Plugin Architecture

Before adding commands, you must understand the three core components that define a plugin's interface:

  • SKILL.md — Human-readable documentation that describes available commands, their syntax, and usage examples. The model parses this markdown to decide which command to run.
  • scripts/ — Machine-readable implementation files (typically Python) that handle argument parsing and service integration. These scripts use argparse to dispatch to specific command handlers.
  • .app.json or plugin.json — Runtime metadata that tells the Codex runtime how to discover the skill, launch scripts, and provision dependencies.

Step-by-Step Guide to Adding a New Command

Follow these steps to extend any plugin in the repository with additional functionality.

Define the Command in SKILL.md

First, document the command in plugins/<plugin>/skills/<skill>/SKILL.md. Add a new heading describing the command, a bash-style invocation example, and parameter definitions.

In plugins/zotero/skills/zotero/SKILL.md, you would add:


## list-items

List the titles of the items in the current Zotero library.

```bash
python3 <plugin-root>/skills/zotero/scripts/zotero.py list-items [--collection <id>]
  • --collection <id> — (optional) limit the output to a specific collection.
    The command returns a JSON array of objects { "title": "...", "key": "..." }.

### Implement the Logic in the Script

Next, extend the script in `plugins/<plugin>/skills/<skill>/scripts/<script>.py` to handle the new sub-command. As implemented in `openai/plugins`, you must add a new `argparse` sub-parser and connect it to a handler function.

Add the command handler function:

```python
def cmd_list_items(args: argparse.Namespace) -> None:
    """Implementation of the `list-items` command."""
    params = {}
    if args.collection:
        params["collection"] = args.collection
    resp = request("/api/items", method="GET", headers=API_VERSION_HEADERS, timeout=10)
    if not resp.ok:
        exit_with(f"Failed to fetch items: {resp.error or resp.status}")
    data = json.loads(resp.text)
    # Filter by collection if requested

    if args.collection:
        data = [itm for itm in data if itm.get("collections", []) == [args.collection]]
    out = [{"title": itm.get("title", ""), "key": itm.get("key")} for itm in data]
    dump_json(out)

Then wire it into the argument parser:

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="zotero")
    sub = parser.add_subparsers(dest="command", required=True)

    # Existing commands … (status, search, etc.)

    # NEW command

    lp = sub.add_parser("list-items", help="List item titles")
    lp.add_argument("--collection", help="Filter by collection key")
    lp.set_defaults(func=cmd_list_items)

    return parser

def main() -> None:
    parser = build_parser()
    args = parser.parse_args()
    # All commands expose a `func` attribute that does the work.

    args.func(args)

if __name__ == "__main__":
    main()

Update the Plugin Manifest (Optional)

If your command requires new Python packages or environment variables, update plugins/<plugin>/.app.json (or .codex-plugin/plugin.json). Add entries under "dependencies" or "environment" so the runtime can provision them. If no new requirements are needed, no manifest changes are necessary.

Complete Working Example

Here is the full implementation pattern for the list-items command in the Zotero plugin:

  1. Documentation in plugins/zotero/skills/zotero/SKILL.md defines the interface the model sees.
  2. Implementation in plugins/zotero/skills/zotero/scripts/zotero.py uses argparse sub-parsers to route to cmd_list_items.
  3. Output must be valid JSON (or the specific format the model expects) printed to stdout.

Test your implementation locally before deploying:

python3 plugins/zotero/skills/zotero/scripts/zotero.py list-items --collection ABC123

Key Files and Their Roles

  • plugins/<plugin>/skills/<skill>/SKILL.md — The human-readable command catalogue that the model uses to discover available operations.
  • plugins/<plugin>/skills/<skill>/scripts/<script>.py — The CLI implementation where argparse sub-commands map directly to entries in SKILL.md.
  • plugins/<plugin>/.app.json — Runtime metadata specifying dependencies, environment variables, and entry points.

Validation Checklist

Before submitting your new command:

  • Add a descriptive heading and example invocation in SKILL.md.
  • Implement the argparse sub-parser with a func default pointing to your handler.
  • Ensure the command prints valid JSON that the model can parse.
  • Test the command locally to verify output format and error handling.
  • Update .app.json only if new runtime requirements exist.

Summary

  • Document first — Add command syntax and examples to SKILL.md so the model knows when to invoke it.
  • Implement with argparse — Use sub-parsers in the script file to dispatch to command-specific logic.
  • Output JSON — Commands must return structured data that the model can consume.
  • Declare dependencies — Update the plugin manifest only when adding new runtime requirements.

Frequently Asked Questions

What file defines the command syntax that the AI model sees?

The SKILL.md file defines the command syntax. Located at plugins/<plugin>/skills/<skill>/SKILL.md, this markdown file contains the heading structure, bash-style invocation examples, and parameter descriptions that the model parses to determine available commands and their arguments.

Can I use languages other than Python for the script implementation?

Yes. While the openai/plugins repository primarily uses Python with argparse, you can implement scripts in Node.js, Go, or other languages. The runtime executes the file specified in the plugin manifest, so any executable script that reads argv and prints valid output will work, provided you document the invocation syntax correctly in SKILL.md.

What output format should my command use?

Commands should output valid JSON by default, as this is the format the model most reliably parses. Use json.dumps() or equivalent to serialize your response. Plain text is supported for simple responses, but JSON is required for structured data containing multiple fields or arrays.

Where do I declare new Python dependencies for my command?

Declare new dependencies in the plugin's .app.json file (or .codex-plugin/plugin.json depending on the plugin structure). Add the package names under the "dependencies" array. The Codex runtime uses this manifest to provision the execution environment before invoking your script.

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 →