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

> Learn how to add commands to your OpenAI plugin in 3 easy steps. This developer guide covers documentation, implementation with argparse, and manifest updates.

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

---

**Adding commands to an OpenAI plugin requires three steps: documenting the command in [`SKILL.md`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/.app.json) or [`plugin.json`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/plugins/zotero/skills/zotero/SKILL.md), you would add:

```markdown

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

```python
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`](https://github.com/openai/plugins/blob/main/.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`](https://github.com/openai/plugins/blob/main/plugins/zotero/skills/zotero/SKILL.md) defines the interface the model sees.
2. **Implementation** in [`plugins/zotero/skills/zotero/scripts/zotero.py`](https://github.com/openai/plugins/blob/main/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:

```bash
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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/.app.json) only if new runtime requirements exist.

## Summary

- **Document first** — Add command syntax and examples to [`SKILL.md`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/.app.json) file (or [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.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.