# Build plugins

This page is for plugin authors. If you want to browse, install, and use
plugins in ChatGPT Work on the web or in Work or Codex in the ChatGPT desktop app, see
[Plugins](https://learn.chatgpt.com/docs/plugins). If you are still iterating on one repo or one personal
workflow, start with a local skill. Build a plugin when you want to share that
workflow across teams, bundle connectors or MCP config, package lifecycle hooks,
or publish a stable package.

A plugin can include skills, an MCP-backed app, or both. If your plugin needs
to connect to a service or expose tools through an MCP server, see
[Build an app](https://learn.chatgpt.com/docs/build-app).

For complete public examples, inspect
[Figma](https://github.com/openai/plugins/tree/main/plugins/figma),
[Notion](https://github.com/openai/plugins/tree/main/plugins/notion), and
[Build web apps](https://github.com/openai/plugins/tree/main/plugins/build-web-apps).

## Create a plugin with `@plugin-creator`

For the fastest setup, use the built-in `@plugin-creator` skill.

<CodexScreenshot
  alt="Plugin creator skill in ChatGPT"
  lightSrc="/images/codex/plugins/plugin-creator.png"
  darkSrc="/images/codex/plugins/plugin-creator-dark.png"
/>

It scaffolds the required `.codex-plugin/plugin.json` manifest and can also
generate a local marketplace entry for testing. If you already have a plugin
folder, you can still use `@plugin-creator` to wire it into a local
marketplace.

<CodexScreenshot
  alt="how to invoke the plugin-creator skill"
  lightSrc="/images/codex/plugins/plugin-creator-invoke.png"
  darkSrc="/images/codex/plugins/plugin-creator-invoke-dark.png"
/>

### Create and test a plugin locally that points to an MCP-server-backed dev-mode app

You can also use `$plugin-creator` if you want to test a plugin locally that
includes an MCP-server backed app. The plugin still needs a local plugin folder
and manifest, but the app itself starts in ChatGPT developer mode.

First, enable developer mode in ChatGPT:

1. Open [ChatGPT](https://chatgpt.com).
2. Open **Settings**.
3. Select **Security and login**.
4. Turn on **Developer mode**.

Then create the app in developer mode:

1. Open **Settings → Plugins** or [the Plugins page](https://chatgpt.com/plugins).
2. Select the plus button.
3. Complete the modal to create a developer-mode app for your MCP server.
4. After ChatGPT creates it, copy the app ID from the browser URL. It starts
   with `plugin_asdk_app`.

Give that `plugin_asdk_app...` ID to `$plugin-creator` in ChatGPT or Codex. For
example:

<div class="not-prose my-6 overflow-hidden rounded-xl border border-subtle bg-surface shadow-sm">
  <div class="border-b border-subtle bg-surface-secondary px-4 py-3">
    <span class="inline-flex rounded-md bg-purple-100 px-2.5 py-1 text-sm font-medium text-purple-700 dark:bg-purple-950/60 dark:text-purple-200">
      Plugin Creator prompt
    </span>
  </div>
  <div class="overflow-x-auto p-4 font-mono text-sm leading-6 text-primary">
    <code class="whitespace-pre-wrap break-words">{`$plugin-creator create a Codex plugin for my ChatGPT app.
Use plugin_asdk_app_6a4c0062f3b88191855c0a80eac5d53d and name it Acme Support.
Include a personal marketplace entry so I can test it locally.`}</code>
  </div>
</div>

`$plugin-creator` will create the plugin folder, create the required
`.codex-plugin/plugin.json`, and add app wiring for the ChatGPT app. If you ask
it to create a personal marketplace entry, the plugin appears in your local
plugin directory for testing.

After `$plugin-creator` creates the plugin:

1. Review `.app.json` and confirm it points at the correct
   `plugin_asdk_app...` ID.
2. Review `.codex-plugin/plugin.json` and make sure its `apps` field points to
   `./.app.json`.
3. Add any bundled skills under `skills/` if the plugin should include
   repeatable workflows alongside the app.
4. If `$plugin-creator` created a personal marketplace entry, refresh ChatGPT
   and install the plugin from your local plugin directory. Then test it in a
   new task.

For the manifest shape and file layout, see [Plugin structure](#plugin-structure)
and [Path rules](#path-rules).

### Build your own curated plugin list

A marketplace is a JSON catalog of plugins. `@plugin-creator` can generate one
for a single plugin, and you can keep adding entries to that same marketplace
to build your own curated list for a repo, team, or personal workflow.

In Work or Codex in the ChatGPT desktop app, each marketplace appears as a
selectable source in the plugin directory. Use
`$REPO_ROOT/.agents/plugins/marketplace.json` for a repo-scoped list or
`~/.agents/plugins/marketplace.json` for a personal list. Add one entry per
plugin under `plugins[]`, point each `source.path` at the plugin folder with a
`./`-prefixed path relative to the marketplace root, and set
`interface.displayName` to the label you want the app to show in the marketplace
picker. Then restart the ChatGPT desktop app. After that, open the plugin
directory, choose your marketplace, and browse or install the plugins in that
curated list.

You don't need a separate marketplace per plugin. One marketplace can expose a
single plugin while you are testing, then grow into a larger curated catalog as
you add more plugins.

<CodexScreenshot
  alt="custom local marketplace in the plugin directory"
  lightSrc="/images/codex/plugins/codex-local-plugin-light.png"
  darkSrc="/images/codex/plugins/codex-local-plugin.png"
/>

### Add a marketplace from the CLI

Use `codex plugin marketplace add` to add and track a marketplace source instead
of editing `config.toml` by hand. These commands support plugin authoring and
catalog setup. Use the ChatGPT desktop app to install and test a local plugin.

```bash
codex plugin marketplace add owner/repo
codex plugin marketplace add owner/repo --ref main
codex plugin marketplace add https://github.com/example/plugins.git --sparse .agents/plugins
codex plugin marketplace add ./local-marketplace-root
```

Marketplace sources can be GitHub shorthand (`owner/repo` or
`owner/repo@ref`), HTTP or HTTPS Git URLs, SSH Git URLs, or local marketplace root
directories. Use `--ref` to pin a Git ref, and repeat `--sparse PATH` to use a
sparse checkout for Git-backed marketplace repos. `--sparse` is valid only for
Git marketplace sources.

To inspect, refresh, or remove configured marketplaces:

```bash
codex plugin marketplace list
codex plugin marketplace upgrade
codex plugin marketplace upgrade marketplace-name
codex plugin marketplace remove marketplace-name
```

`codex plugin marketplace list` prints each marketplace Codex is considering
and the root path it resolves from, including local default marketplaces and
configured marketplace snapshots.

### Create a plugin manually

Start with a minimal plugin that packages one skill.

1. Create a plugin folder with a manifest at `.codex-plugin/plugin.json`.

```bash
mkdir -p my-first-plugin/.codex-plugin
```

`my-first-plugin/.codex-plugin/plugin.json`

```json
{
  "name": "my-first-plugin",
  "version": "1.0.0",
  "description": "Reusable greeting workflow",
  "skills": "./skills/"
}
```

Use a stable plugin `name` in kebab-case. Codex uses it as the plugin
identifier and component namespace.

2. Add a skill under `skills/<skill-name>/SKILL.md`.

```bash
mkdir -p my-first-plugin/skills/hello
```

`my-first-plugin/skills/hello/SKILL.md`

```md
---
name: hello
description: Greet the user with a friendly message.
---

Greet the user warmly and ask how you can help.
```

3. Add the plugin to a marketplace. Use `@plugin-creator` to generate one, or
   follow [Build your own curated plugin list](#build-your-own-curated-plugin-list)
   to wire the plugin into Codex manually.

From there, you can add MCP config, connectors, or marketplace metadata
as needed.

### Install a local plugin manually

Use a repo marketplace or a personal marketplace, depending on who should be
able to access the plugin or curated list.

<Tabs
  id="codex-plugins-local-install"
  param="install-scope"
  defaultTab="workspace"
  tabs={[
    {
      id: "workspace",
      label: "Repo",
    },
    {
      id: "global",
      label: "Personal",
    },
  ]}
>
  <div slot="workspace">
    Add a marketplace file at `$REPO_ROOT/.agents/plugins/marketplace.json`
    and store your plugins under `$REPO_ROOT/plugins/`.

    **Repo marketplace example**

    Step 1: Copy the plugin folder into `$REPO_ROOT/plugins/my-plugin`.

```bash
mkdir -p ./plugins
cp -R /absolute/path/to/my-plugin ./plugins/my-plugin
```

    Step 2: Add or update `$REPO_ROOT/.agents/plugins/marketplace.json` so
    that `source.path` points to that plugin directory with a `./`-prefixed
    relative path:

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

    Step 3: Restart the ChatGPT desktop app and verify that the plugin appears.

  </div>

  <div slot="global">
    Add a marketplace file at `~/.agents/plugins/marketplace.json` and store
    your plugins under `~/.codex/plugins/`.

    **Personal marketplace example**

    Step 1: Copy the plugin folder into `~/.codex/plugins/my-plugin`.

```bash
mkdir -p ~/.codex/plugins
cp -R /absolute/path/to/my-plugin ~/.codex/plugins/my-plugin
```

    Step 2: Add or update `~/.agents/plugins/marketplace.json` so that the
    plugin entry's `source.path` points to that directory.

    Step 3: Restart the ChatGPT desktop app and verify that the plugin appears.

  </div>
</Tabs>

The marketplace file points to the plugin location, so those directories are
examples rather than fixed requirements. Codex resolves `source.path` relative
to the marketplace root, not relative to the `.agents/plugins/` folder. See
[Marketplace metadata](#marketplace-metadata) for the file format.

After you change the plugin, update the plugin directory that your marketplace
entry points to and restart the ChatGPT desktop app so the local install picks
up the new files.

### Share a local plugin with your workspace

After you create a plugin and add it to Work or Codex, you can share it with
other members of your ChatGPT workspace from the ChatGPT desktop app.

1. Open **Plugins** in the ChatGPT desktop app.
2. Go to **Created by you** and open the plugin details page.
3. Select **Share**.
4. Add workspace members or workspace groups, or copy a share link.
5. Choose who has access, then send the invitation or link.

People you share with can find the plugin under **Shared with you** in the
plugin directory. Sharing a local plugin with your workspace doesn't publish
it to the public Plugins Directory. Shared plugins stay within your workspace
and organization boundary; accounts that aren't signed in to that workspace
can't access them. Use groups when a team or role should share the same plugin
access. Use a marketplace when you want repo or CLI distribution, and use
workspace sharing when you want selected teammates to install a plugin from the
ChatGPT desktop app.

Workspace admins can disable plugin sharing from cloud-managed requirements by
adding `features.plugin_sharing = false` to `requirements.toml`:

```toml
features.plugin_sharing = false
```

### Marketplace metadata

If you maintain a repo marketplace, define it in
`$REPO_ROOT/.agents/plugins/marketplace.json`. For a personal marketplace, use
`~/.agents/plugins/marketplace.json`. A marketplace file controls plugin
ordering and install policies in the ChatGPT desktop app. It can represent one
plugin while you are testing or a curated list of plugins that you want the app
to show together under one marketplace name. Before you add a plugin to a
marketplace, make sure its `version`, publisher metadata, and install-surface
copy are ready for other developers to see.

```json
{
  "name": "local-example-plugins",
  "interface": {
    "displayName": "Local Example Plugins"
  },
  "plugins": [
    {
      "name": "my-plugin",
      "source": {
        "source": "local",
        "path": "./plugins/my-plugin"
      },
      "policy": {
        "installation": "AVAILABLE",
        "authentication": "ON_INSTALL"
      },
      "category": "Productivity"
    },
    {
      "name": "research-helper",
      "source": {
        "source": "local",
        "path": "./plugins/research-helper"
      },
      "policy": {
        "installation": "AVAILABLE",
        "authentication": "ON_INSTALL"
      },
      "category": "Productivity"
    }
  ]
}
```

- Use top-level `name` to identify the marketplace.
- Use `interface.displayName` for the marketplace title shown in the ChatGPT
  desktop app.
- Add one object per plugin under `plugins` to build a curated list that the app
  shows under that marketplace title.
- Point each plugin entry's `source.path` at the plugin directory you want
  Codex to load. For repo installs, that often lives under `./plugins/`. For
  personal installs, a common pattern is `./.codex/plugins/<plugin-name>`.
- Keep `source.path` relative to the marketplace root, start it with `./`, and
  keep it inside that root.
- For local entries, `source` can also be a plain string path such as
  `"./plugins/my-plugin"`.
- Always include `policy.installation`, `policy.authentication`, and
  `category` on each plugin entry.
- Use `policy.installation` values such as `AVAILABLE`,
  `INSTALLED_BY_DEFAULT`, or `NOT_AVAILABLE`.
- Use `policy.authentication` to decide whether auth happens on install or
  first use.

The marketplace controls where Codex loads the plugin from. A local
`source.path` can point somewhere else if your plugin lives outside those
example directories. A marketplace file can live in the repo where you are
developing the plugin or in a separate marketplace repo, and one marketplace
file can point to one plugin or many.

Marketplace entries can also point at Git-backed plugin sources. Use
`"source": "url"` when the plugin lives at the repository root, or
`"source": "git-subdir"` when the plugin lives in a subdirectory:

```json
{
  "name": "remote-helper",
  "source": {
    "source": "git-subdir",
    "url": "https://github.com/example/codex-plugins.git",
    "path": "./plugins/remote-helper",
    "ref": "main"
  },
  "policy": {
    "installation": "AVAILABLE",
    "authentication": "ON_INSTALL"
  },
  "category": "Productivity"
}
```

Git-backed entries may use `ref` or `sha` selectors. If Codex can't resolve a
marketplace entry's source, it skips that plugin entry instead of failing the
whole marketplace.

Marketplace entries can also install a plugin from a JavaScript package registry:

```json
{
  "name": "npm-helper",
  "source": {
    "source": "npm",
    "package": "@example/codex-plugin",
    "version": "^1.2.0",
    "registry": "https://registry.npmjs.org"
  },
  "policy": {
    "installation": "AVAILABLE",
    "authentication": "ON_INSTALL"
  },
  "category": "Productivity"
}
```

`package` is required and can include a registry scope. `version` is optional
and accepts package versions, distribution tags, and version ranges, but not
path or URL selectors.
`registry` is optional and must be an HTTPS URL without embedded credentials,
a query, or a fragment. Codex downloads the package without running lifecycle
scripts. The `npm` CLI must be installed, and registry authentication comes
from its configuration.

### How the ChatGPT desktop app uses marketplaces

A plugin marketplace is a JSON catalog of plugins that the ChatGPT desktop app
can read and install.

The app can read marketplace files from:

- the curated marketplace that powers the official Plugins Directory
- a repo marketplace at `$REPO_ROOT/.agents/plugins/marketplace.json`
- a legacy-compatible marketplace at `$REPO_ROOT/.claude-plugin/marketplace.json`
- a personal marketplace at `~/.agents/plugins/marketplace.json`

You can install any plugin exposed through a marketplace. The app installs
plugins into
`~/.codex/plugins/cache/$MARKETPLACE_NAME/$PLUGIN_NAME/$VERSION/`. For local
plugins, `$VERSION` is `local`, and the app loads the installed copy from that
cache path rather than directly from the marketplace entry.

You can enable or disable each plugin individually. The app stores each plugin's
on or off state in `~/.codex/config.toml`.

## Package and distribute plugins

### Plugin structure

Every plugin has a manifest at `.codex-plugin/plugin.json`. It can also include
a `skills/` directory, a `hooks/` directory for lifecycle hooks, an `.app.json`
file that points at one or more connectors, an `.mcp.json` file that
configures MCP servers, and assets used to present the plugin across supported
surfaces.

<FileTree
  class="mt-4"
  tree={[
    {
      name: "my-plugin/",
      open: true,
      children: [
        {
          name: ".codex-plugin/",
          open: true,
          children: [
            {
              name: "plugin.json",
              comment: "Required: plugin manifest",
            },
          ],
        },
        {
          name: "skills/",
          open: true,
          children: [
            {
              name: "my-skill/",
              open: true,
              children: [
                {
                  name: "SKILL.md",
                  comment: "Optional: skill instructions",
                },
              ],
            },
          ],
        },
        {
          name: "hooks/",
          open: true,
          children: [
            {
              name: "hooks.json",
              comment: "Optional: lifecycle hooks",
            },
          ],
        },
        {
          name: ".app.json",
          comment: "Optional: app or connector mappings",
        },
        {
          name: ".mcp.json",
          comment: "Optional: MCP server configuration",
        },
        {
          name: "assets/",
          comment: "Optional: icons, logos, screenshots",
        },
      ],
    },
  ]}
/>

Only `plugin.json` belongs in `.codex-plugin/`. Keep `skills/`, `hooks/`,
`assets/`, `.mcp.json`, and `.app.json` at the plugin root.

Published plugins typically use a richer manifest than the minimal example that
appears in quick-start scaffolds. The manifest has three jobs:

- Identify the plugin.
- Point to bundled components such as skills, connectors, MCP servers, or hooks.
- Provide install-surface metadata such as descriptions, icons, and legal
  links.

Here's a complete manifest example:

```json
{
  "name": "my-plugin",
  "version": "0.1.0",
  "description": "Bundle reusable skills and connectors.",
  "author": {
    "name": "Your team",
    "email": "team@example.com",
    "url": "https://example.com"
  },
  "homepage": "https://example.com/plugins/my-plugin",
  "repository": "https://github.com/example/my-plugin",
  "license": "MIT",
  "keywords": ["research", "crm"],
  "skills": "./skills/",
  "mcpServers": "./.mcp.json",
  "apps": "./.app.json",
  "hooks": "./hooks/hooks.json",
  "interface": {
    "displayName": "My Plugin",
    "shortDescription": "Reusable skills and connectors",
    "longDescription": "Distribute skills and connectors together.",
    "developerName": "Your team",
    "category": "Productivity",
    "capabilities": ["Read", "Write"],
    "websiteURL": "https://example.com",
    "privacyPolicyURL": "https://example.com/privacy",
    "termsOfServiceURL": "https://example.com/terms",
    "defaultPrompt": [
      "Use My Plugin to summarize new CRM notes.",
      "Use My Plugin to triage new customer follow-ups."
    ],
    "brandColor": "#10A37F",
    "composerIcon": "./assets/icon.png",
    "logo": "./assets/logo.png",
    "screenshots": ["./assets/screenshot-1.png"]
  }
}
```

`.codex-plugin/plugin.json` is the required entry point. The other manifest
fields are optional, but published plugins commonly use them.

### Manifest fields

Use the top-level fields to define package metadata and point to bundled
components:

- `name`, `version`, and `description` identify the plugin.
- `author`, `homepage`, `repository`, `license`, and `keywords` provide
  publisher and discovery metadata.
- `skills`, `mcpServers`, `apps`, and `hooks` point to bundled components
  relative to the plugin root.
- `interface` controls how install surfaces present the plugin.

Use the `interface` object for install-surface metadata:

- `displayName`, `shortDescription`, and `longDescription` control the title
  and descriptive copy.
- `developerName`, `category`, and `capabilities` add publisher and capability
  metadata.
- `websiteURL`, `privacyPolicyURL`, and `termsOfServiceURL` provide external
  links.
- `defaultPrompt`, `brandColor`, `composerIcon`, `logo`, and `screenshots`
  control starter prompts and visual presentation.

### Path rules

- Keep manifest paths relative to the plugin root and start them with `./`.
- Store visual assets such as `composerIcon`, `logo`, and `screenshots` under
  `./assets/` when possible.
- Use `skills` for bundled skill folders, `apps` for `.app.json`,
  `mcpServers` for `.mcp.json`, and `hooks` for lifecycle hooks.
- Enabled plugins can include lifecycle hooks alongside skills, MCP servers, and
  connectors.
- If your plugin stores hooks at `./hooks/hooks.json`, you don't need a
  `hooks` entry in `.codex-plugin/plugin.json`; Codex checks that default file
  automatically.

### Bundled MCP servers and lifecycle hooks

`mcpServers` can point to an `.mcp.json` file that contains either a direct
server map or a wrapped `mcp_servers` object.

Direct server map:

```json
{
  "docs": {
    "command": "docs-mcp",
    "args": ["--stdio"]
  }
}
```

Wrapped server map:

```json
{
  "mcp_servers": {
    "docs": {
      "command": "docs-mcp",
      "args": ["--stdio"]
    }
  }
}
```

After installation, users can enable or disable a bundled MCP server and tune
tool approval policy from their Codex config without editing the plugin. Use
`plugins.<plugin>.mcp_servers.<server>` for plugin-scoped MCP server policy:

```toml
[plugins."my-plugin".mcp_servers.docs]
enabled = true
default_tools_approval_mode = "prompt"
enabled_tools = ["search"]

[plugins."my-plugin".mcp_servers.docs.tools.search]
approval_mode = "approve"
```

When your plugin is enabled, Codex can load lifecycle hooks from your plugin
alongside user, project, and managed hooks.

Installing or enabling a plugin doesn't automatically trust its hooks.
Plugin-bundled hooks are non-managed hooks, so Codex skips them until the user
reviews and trusts the current hook definition.

The default plugin hook file is `hooks/hooks.json`:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${PLUGIN_ROOT}/hooks/session_start.py",
            "statusMessage": "Loading plugin context"
          }
        ]
      }
    ]
  }
}
```

If you define `hooks` in `.codex-plugin/plugin.json`, Codex uses that manifest
entry instead of the default `hooks/hooks.json`. The manifest field can be a
single path, an array of paths, an inline hooks object, or an array of inline
hooks objects.

```json
{
  "name": "repo-policy",
  "hooks": ["./hooks/session.json", "./hooks/tools.json"]
}
```

Hook paths follow the same manifest path rules as `skills`, `apps`, and
`mcpServers`: start with `./`, resolve relative to the plugin root, and stay
inside the plugin root.

Plugin hook commands receive the Codex-specific environment variables
`PLUGIN_ROOT` and `PLUGIN_DATA`. `PLUGIN_ROOT` points to the installed plugin
root, and `PLUGIN_DATA` points to the plugin's writable data directory. Codex
also sets `CLAUDE_PLUGIN_ROOT` and `CLAUDE_PLUGIN_DATA` for compatibility with
existing plugin hooks.

Plugin hooks use the same event schema as regular hooks. See
[Hooks](https://learn.chatgpt.com/docs/hooks) for supported events, inputs, outputs, trust review, and
current limitations.

### Publish official public plugins

To publish a plugin for public use, submit it through the plugin submission
portal. See [Submit plugins](https://learn.chatgpt.com/docs/submit-plugins) for the full review and
publishing process.