After building your skills and, when needed, an MCP server, assemble those parts into the plugin people will install. Packaging gives the plugin a stable identity and tells ChatGPT and Codex which skills, MCP server connections, and other resources belong together.
Every plugin has a .codex-plugin/plugin.json manifest. Depending on the
plugin’s architecture, its folder can also include:
- A
skills/directory containing the workflows you built. - An
.app.jsonfile that references a registered MCP server connection. The filename is a compatibility identifier; the underlying primitive is the MCP server. - An
.mcp.jsonfile for an MCP server distributed with the plugin. - Optional assets and lifecycle hooks.
UI and authentication remain part of the MCP server integration you built in the preceding steps; the plugin manifest connects that integration to the rest of the package.
Public plugins are published once to the universal plugin directory shared by ChatGPT and Codex. Local and repo marketplaces are separate authoring, testing, and team-distribution sources, and their availability can vary by surface.
Use @plugin-creator for the fastest path, or create the manifest and folder
structure manually. Both approaches produce the same plugin structure.
For complete public examples, inspect Figma, Notion, and Build web apps.
Package 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 with an MCP server
You can also use the plugin-creator skill to test a plugin that includes an MCP server. The plugin still needs a local folder and manifest, and you first register the MCP server connection in ChatGPT developer mode.
First, enable developer mode in ChatGPT:
- Open ChatGPT.
- Open Settings.
- Select Security and login.
- Turn on Developer mode.
Then register the MCP server in developer mode:
- Go to ChatGPT Plugins.
- Select the plus button.
- Complete the modal with your MCP server URL and connection details.
- After ChatGPT creates the connection, copy its technical ID from the browser
URL. It starts with
plugin_asdk_app.
Give that plugin_asdk_app... ID to @plugin-creator in Work mode in ChatGPT
or $plugin-creator in Codex. For example, in Work mode:
Plugin Creator prompt
@plugin-creator create a plugin for ChatGPT and Codex using my MCP server.
Use plugin_asdk_app_6a4c0062f3b88191855c0a80eac5d53d and name it Acme Support.
Include a personal marketplace entry so I can test it locally.The plugin-creator skill will create the plugin folder, create the required
.codex-plugin/plugin.json, and add MCP server wiring for the plugin. If you ask
it to create a personal marketplace entry, the plugin appears under your local
source in the Plugins Directory for testing.
After the plugin-creator skill creates the plugin:
- Review
.app.jsonand confirm the registered MCP server mapping points at the correctplugin_asdk_app...ID. - Review
.codex-plugin/plugin.jsonand make sure its compatibilityappsfield points to./.app.json. - Add any bundled skills under
skills/if the plugin should include repeatable workflows alongside the MCP server. - If the skill created a personal marketplace entry, refresh ChatGPT and install the plugin from your local source in the Plugins Directory. Then test it in a new chat.
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 mode or Codex in the ChatGPT desktop app, each marketplace appears as a
selectable source in the Plugins 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 plugin to show in the marketplace
picker. Then restart the ChatGPT desktop app. After that, open the Plugins
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.
- 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. Plugin hosts use it as the plugin
identifier and component namespace.
- 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.
- Add the plugin to a marketplace. Use
@plugin-creatorto generate one, or follow Build your own curated plugin list to wire the plugin into a local marketplace manually.
From there, you can add MCP server configuration 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.
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-pluginStep 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.
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.
mkdir -p ~/.codex/plugins
cp -R /absolute/path/to/my-plugin ~/.codex/plugins/my-pluginStep 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.
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, add it from the ChatGPT desktop app. Select ChatGPT and switch to Work mode, or select Codex, then open Plugins. You can then share it with other members of your ChatGPT workspace.
- Open Plugins in the ChatGPT desktop app.
- Go to Created by you and open the plugin details page.
- Select Share.
- Add workspace members or workspace groups, or copy a share link.
- Choose who has access, then send the invitation or link.
People you share with can find the plugin under Shared with you in the Plugins Directory. Sharing a local plugin with your workspace doesn’t publish it to the universal public Plugins Directory shared by ChatGPT and Codex. 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 ChatGPT
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
nameto identify the marketplace. - Use
interface.displayNamefor the marketplace title shown in the ChatGPT desktop app. - Add one object per plugin under
pluginsto build a curated list that ChatGPT shows under that marketplace title. - Point each plugin entry’s
source.pathat the plugin directory you want the local host to load. For repo installs, that often lives under./plugins/. For personal installs, a common pattern is./.codex/plugins/<plugin-name>. - Keep
source.pathrelative to the marketplace root, start it with./, and keep it inside that root. - For local entries,
sourcecan also be a plain string path such as"./plugins/my-plugin". - Always include
policy.installation,policy.authentication, andcategoryon each plugin entry. - Use
policy.installationvalues such asAVAILABLE,INSTALLED_BY_DEFAULT, orNOT_AVAILABLE. - Use
policy.authenticationto decide whether auth happens on install or first use.
The marketplace controls where the local host 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 local marketplaces work
A plugin marketplace is a JSON catalog of plugins. These local sources are separate from the universal public directory and support authoring, testing, and private distribution.
The ChatGPT desktop app can read marketplace files from:
- 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. ChatGPT installs
plugins into
~/.codex/plugins/cache/$MARKETPLACE_NAME/$PLUGIN_NAME/$VERSION/. For local
plugins, $VERSION is local, and ChatGPT loads the installed copy from that
cache path rather than directly from the marketplace entry.
You can enable or disable each plugin individually. ChatGPT 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 maps registered MCP server connections, an .mcp.json file that
configures bundled 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: registered MCP server 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, 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 MCP servers.",
"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 MCP servers",
"longDescription": "Distribute skills and MCP servers 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, anddescriptionidentify the plugin.author,homepage,repository,license, andkeywordsprovide publisher and discovery metadata.skills,mcpServers, andhookspoint to bundled components relative to the plugin root. The compatibilityappsfield points to registered MCP server mappings.interfacecontrols how install surfaces present the plugin.
Use the interface object for install-surface metadata:
displayName,shortDescription, andlongDescriptioncontrol the title and descriptive copy.developerName,category, andcapabilitiesadd publisher and capability metadata.websiteURL,privacyPolicyURL, andtermsOfServiceURLprovide external links.defaultPrompt,brandColor,composerIcon,logo, andscreenshotscontrol 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, andscreenshotsunder./assets/when possible. - Use
skillsfor bundled skill folders,mcpServersfor.mcp.json, andhooksfor lifecycle hooks. Use the compatibilityappsfield only for registered MCP server mappings in.app.json. - Enabled plugins can include lifecycle hooks alongside skills and MCP servers.
- If your plugin stores hooks at
./hooks/hooks.json, you don’t need ahooksentry 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 on Learn 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. After publication, the plugin is listed in the universal directory shared by ChatGPT and Codex. See Submit plugins for the full review and publishing process.