Primary navigation

Build plugins

Create, test, and distribute plugins for ChatGPT

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. 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.

Create a plugin with @plugin-creator

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

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.

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

Plugin Creator prompt

$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.

$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 and 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.

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.

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:

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.
mkdir -p my-first-plugin/.codex-plugin

my-first-plugin/.codex-plugin/plugin.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.

  1. Add a skill under skills/<skill-name>/SKILL.md.
mkdir -p my-first-plugin/skills/hello

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

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

Greet the user warmly and ask how you can help.
  1. Add the plugin to a marketplace. Use @plugin-creator to generate one, or follow 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.

Choose an option

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.

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:

{
  "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.

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

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.

{
  "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:

{
  "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:

{
  "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.

  • my-plugin/
    • .codex-plugin/
      • plugin.json Required: plugin manifest
    • skills/
      • my-skill/
        • SKILL.md Optional: skill instructions
    • hooks/
      • hooks.json Optional: lifecycle hooks
    • .app.json Optional: app or connector mappings
    • .mcp.json Optional: MCP server configuration
    • assets/ 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:

{
  "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:

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

Wrapped server map:

{
  "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:

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

{
  "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.

{
  "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 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 for the full review and publishing process.