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

> Learn how to add custom commands to your OpenAI plugin. This guide details automating command discovery using markdown files and the plugin manifest.

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

---

**OpenAI plugins expose custom slash commands by creating declarative markdown files in the `commands/` directory, which the plugin manifest auto-discovers without requiring manual registration.**

The `openai/plugins` repository provides a declarative framework for building Codex-compatible plugins. Adding custom commands—also known as **slash commands**—allows users to invoke deterministic workflows directly from chat by placing structured markdown files in your plugin's `commands/` directory.

## Understanding the OpenAI Plugin Command Architecture

OpenAI plugins use a convention-over-configuration approach for command management. When the Codex runtime starts, it recursively scans the `commands/` directory for all markdown files (excluding those prefixed with underscores), parses their YAML front-matter, and automatically registers them as invocable slash commands.

This design couples documentation with logic, making commands self-describing and version-controllable. The authoritative structure is defined in each plugin's [`commands/_conventions.md`](https://github.com/openai/plugins/blob/main/commands/_conventions.md) file, which specifies exactly how the runtime interprets your command files.

## Step-by-Step Guide to Adding Custom Commands

### Step 1: Create the Command File

Create a new markdown file in your plugin's `commands/` directory. The filename (minus the `.md` extension) becomes the command name.

```bash
touch plugins/<your-plugin>/commands/my-custom-task.md

```

Files prefixed with an underscore (e.g., [`_conventions.md`](https://github.com/openai/plugins/blob/main/_conventions.md)) are ignored by the loader, so use these for internal documentation or shared templates.

### Step 2: Add Required YAML Front-Matter

Every command file must begin with YAML front-matter containing at least a `description` field. This description appears in the UI when users browse available commands.

```markdown
---
description: Perform a custom task on the repository (e.g., lint, generate a report).
---

```

### Step 3: Structure the Six Required Sections

The body must contain six specific sections that define the command's lifecycle. According to the [`commands/_conventions.md`](https://github.com/openai/plugins/blob/main/commands/_conventions.md) specification in the `openai/plugins` repository, these are:

**Preflight** – Pre-execution checks such as verifying environment variables, ensuring repositories are clean, or checking tool availability.

**Plan** – A narrative description of what the command will do, which files it will modify, and which secrets it will reference (without revealing their values).

**Commands** – The actual implementation steps, including shell commands, script invocations, or file edits. Use fenced code blocks with language specifiers.

**Verification** – Steps to confirm success, such as re-reading changed files, checking exit codes, or validating API responses.

**Summary** – A concise result block in a fenced code block that presents the final outcome.

**Next Steps** – Optional suggestions for follow-up actions or remediation.

### Step 4: Verify Auto-Discovery

The plugin manifest at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) automatically discovers all non-underscore markdown files in `commands/`. No manual registration or JSON editing is required. Simply commit your new file to the repository.

## Complete Command File Example

Below is a production-ready template based on the Zoom plugin's implementation pattern found in [`plugins/zoom/commands/setup-zoom-oauth.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/commands/setup-zoom-oauth.md):

```markdown
---
description: Perform a custom task on the repository (e.g., lint, generate a report).
---

# My Custom Task

## Preflight

1. Verify the repo is clean (`git status --porcelain`).
2. Ensure the required tool (`my-tool`) is installed (`which my-tool`).

## Plan

The command will:
- Run `my-tool lint` on the `src/` directory.
- Write the results to `reports/lint-report.txt`.

## Commands

```sh
my-tool lint src/ > reports/lint-report.txt

```

## Verification

1. Check that `reports/lint-report.txt` exists.
2. Print the first few lines of the report (`head -n 10 reports/lint-report.txt`).

## Summary

```text

## Result

- Action: lint source files
- Status: success
- Details: reports/lint-report.txt created, contains 42 warnings

```

## Next Steps

- Review the lint report and fix the highlighted issues.
- Re-run the command after fixing problems.

```

## Understanding the Command Conventions

The [`commands/_conventions.md`](https://github.com/openai/plugins/blob/main/commands/_conventions.md) file in each plugin directory serves as the style guide and schema validator. It defines the exact formatting requirements for each section and the expected behavior of the runtime parser.

For reference implementations, examine the Zoom plugin's [`setup-zoom-oauth.md`](https://github.com/openai/plugins/blob/main/setup-zoom-oauth.md) command file, which demonstrates proper secret handling, API calls, and verification patterns used in production OpenAI plugins.

## Testing Your Custom Command

Test your implementation using the chat interface by invoking the command with a forward slash:

```

/my-custom-task

```

Alternatively, use the CLI interface to validate the parsing and execution:

```bash
codex run <plugin-name> my-custom-task

```

The runtime validates that all required sections are present and that the preflight checks pass before executing the Commands section.

## Summary

- **File Location**: Place command files in `<plugin-root>/commands/` with the `.md` extension.
- **Auto-Discovery**: The [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) manifest automatically includes all non-underscore markdown files without manual registration.
- **Required Structure**: Include YAML front-matter with a `description` field and six body sections (Preflight, Plan, Commands, Verification, Summary, Next Steps).
- **Conventions**: Follow the specifications in [`commands/_conventions.md`](https://github.com/openai/plugins/blob/main/commands/_conventions.md) for formatting and style consistency.
- **Testing**: Invoke commands via `/command-name` in chat or `codex run <plugin> <command>` in the CLI.

## Frequently Asked Questions

### How does the plugin manifest discover new commands without manual registration?

The plugin loader automatically scans the `commands/` directory at runtime, parsing every `.md` file that does not start with an underscore. It extracts the command name from the filename and registers it using the description from the YAML front-matter, as implemented in the `openai/plugins` runtime architecture.

### What happens if I omit one of the required six sections?

The Codex runtime expects all six sections (Preflight, Plan, Commands, Verification, Summary, Next Steps) to be present in every command file. While the specific behavior depends on the runtime version, missing sections typically cause validation errors or prevent the command from appearing in the available slash commands list.

### Can I use subdirectories within the `commands/` folder?

The analysis indicates that the loader reads all non-underscore markdown files in `commands/`. While the provided examples show flat structures, you should verify support for nested directories by checking your specific plugin runtime version or the [`_conventions.md`](https://github.com/openai/plugins/blob/main/_conventions.md) file in your plugin's directory.

### How do I handle sensitive data like API keys in custom commands?

Reference secrets in the **Plan** section by name only (e.g., "will use the ZOOM_API_KEY secret"), then access them within the **Commands** section using environment variable syntax or your plugin's secret injection mechanism. Never hardcode credentials or print secret values in the **Summary** or **Verification** sections.