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 useargparseto dispatch to specific command handlers..app.jsonorplugin.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:
- Documentation in
plugins/zotero/skills/zotero/SKILL.mddefines the interface the model sees. - Implementation in
plugins/zotero/skills/zotero/scripts/zotero.pyusesargparsesub-parsers to route tocmd_list_items. - 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 whereargparsesub-commands map directly to entries inSKILL.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
argparsesub-parser with afuncdefault 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.jsononly if new runtime requirements exist.
Summary
- Document first — Add command syntax and examples to
SKILL.mdso 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →