GitHub list of ChatGPT Plugin Directory

How to package and publish a ChatGPT plugin

Package skills and an MCP server as a plugin with plugin.json, test it from a local marketplace, then upload, pass review and publish it to the directory.

Divy ChaurasiaAdvanced 60 minFor builders

A public plugin is published once and listed in the single directory that ChatGPT and Codex share. Publishing means packaging your skills and MCP server into a ZIP, uploading it to the OpenAI Platform, clearing automated checks, passing human review, and then choosing when to go live.

This guide assumes you already have a working MCP server, skills, or both. If not, start with Build your first ChatGPT app or Write a skill for ChatGPT and Codex.

Before you start#

  • Verified identity. Complete individual or business verification in the Platform's organization settings. You can only publish under a verified name; anything else is rejected.
  • Permissions. Organization owners can submit. Other members need the Apps Management Write permission, which an owner grants through organization roles.
  • A public HTTPS endpoint for any MCP server, typically ending in /mcp. Tunnels (ngrok, Secure MCP Tunnel) are for testing only and are not accepted.
  • A project with global data residency. Projects with EU data residency can't currently submit plugins that include an MCP server.
  • Policy pages. For an MCP plugin: a website, support page, privacy policy and terms of service, all on HTTPS.
  • The rules. Read OpenAI's plugin guidelines before you build the listing. Most rejections trace back to them.

1. Choose the shape#

ShapeUse when
Skills onlyInstructions and the model's existing tools are enough.
MCP server onlyYou need tools, but no extra workflow guidance.
Skills and MCP serverSkills should guide the model through your tools.
MCP server with UIA visual component clearly helps part of the workflow.

Start with the smallest shape that covers your use cases. Note one current limit: you can't add an MCP server to a plugin that was first submitted as skills-only, so include the server in your first ZIP if you'll ever need it.

2. Lay out the package#

New plugins should use the portable Agent Plugins layout:

text
acme-notes/
├── plugin.json
├── mcp.json
├── skills/
│   └── get-started/
│       └── SKILL.md
└── assets/
    ├── icon.png
    └── logo.png

Keep plugin.json, mcp.json, skills/ and assets/ at the plugin root. Every path in the manifest is relative to that root and starts with ./.

The older Codex layout (.codex-plugin/plugin.json with .mcp.json) is still supported as a fallback, and it's what every plugin in the openai/plugins repository uses. Look at the Figma and Notion plugins there for complete real examples.

3. Write plugin.json#

Portable identity goes at the root. Everything OpenAI-specific (listing text, icons, review material) goes under extensions.com.openai:

json
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "acme-notes",
  "version": "1.0.0",
  "description": "Search and summarize notes from your Acme workspace.",
  "author": { "name": "Acme", "email": "support@example.com", "url": "https://example.com" },
  "homepage": "https://example.com/notes",
  "license": "MIT",
  "extensions": {
    "com.openai": {
      "interface": {
        "displayName": "Acme Notes",
        "shortDescription": "Find and summarize your notes",
        "longDescription": "Search notes in your Acme workspace and summarize decisions with links to the source. Only accesses notes available to your connected account.",
        "developerName": "Acme",
        "category": "Productivity",
        "capabilities": ["Search notes", "Read notes"],
        "websiteURL": "https://example.com/notes",
        "supportURL": "https://example.com/support",
        "privacyPolicyURL": "https://example.com/privacy",
        "termsOfServiceURL": "https://example.com/terms",
        "defaultPrompt": ["Find my notes about the launch."],
        "composerIcon": "./assets/icon.png",
        "logo": "./assets/logo.png"
      },
      "onboardingSkill": "./skills/get-started/SKILL.md",
      "publication": {
        "release_notes": "Initial release with note search and summaries."
      }
    }
  }
}

Constraints that trip people up:

  • name is kebab-case and stable. Hosts use it as the plugin ID and namespace. Max 64 characters.
  • version must be semver (1.0.0).
  • displayName and shortDescription are at most 30 characters each; longDescription at most 4,000.
  • defaultPrompt: up to three starter prompts, 128 characters each, with no @ mentions.
  • Icons must be square, at least 48×48 pixels, and PNG, JPEG, WebP or SVG up to 5 MiB.
  • Don't put credentials or reviewer instructions in the package. Fields named test_credentials and reviewer_instructions are rejected.

4. Add the MCP server and skills#

Declare the remote server in mcp.json. The portable format requires a transport type, so don't just rename an old .mcp.json:

json
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "notes": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp"
    }
  }
}

A package can declare several MCP servers, but only one can be connected per plugin in the submission portal.

Each skill lives in skills/<name>/SKILL.md with name and description in its YAML front matter. Portable packages discover skills from skills/ automatically, so the manifest doesn't need a skills field.

Warning: ZIPs that contain app references (an apps field or .app.json) or lifecycle hooks can't currently be submitted. Those are fine for local and workspace plugins, but for public submission, declare the server URL in mcp.json.

Scaffolding with plugin-creator#

The built-in $plugin-creator skill in Codex (or @plugin-creator in a ChatGPT Work chat) can scaffold a plugin and a local marketplace entry for testing. It generates the older .codex-plugin/plugin.json layout, and it can wire in an MCP server you registered in developer mode by its plugin_asdk_app... ID. That wiring uses .app.json, so convert to mcp.json before a public submission.

5. Test it from a local marketplace#

Install and test the complete plugin before uploading. Create $REPO_ROOT/.agents/plugins/marketplace.json (or ~/.agents/plugins/marketplace.json for a personal one):

json
{
  "name": "local-repo",
  "interface": { "displayName": "Local plugins" },
  "plugins": [
    {
      "name": "acme-notes",
      "source": { "source": "local", "path": "./plugins/acme-notes" },
      "policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" },
      "category": "Productivity"
    }
  ]
}

source.path is relative to the marketplace root, not to .agents/plugins/. Restart the ChatGPT desktop app, pick your marketplace in the Plugin Directory, install the plugin, and run your test prompts in a new chat. After each change, restart the app so it picks up the new files.

Check that each skill triggers on the right requests and stays quiet on the wrong ones, that skills call the right tools, and that sign-in works after install. The Plugin Eval plugin can help benchmark skills locally.

6. Upload the ZIP#

Zip the plugin folder. The archive must contain exactly one plugin root, either at the top level or in a single top-level directory with no sibling files:

bash
zip -r acme-notes.zip acme-notes -x '*.DS_Store'

Then:

  1. Open Plugins on the OpenAI Platform (platform.openai.com/plugins) and select Upload new or existing plugin.
  2. Choose your verified Developer identity. The directory shows this name.
  3. Select Upload plugin and choose the ZIP. If validation fails, fix the reported errors and upload again.

7. Clear the automated checks#

The plugin page has two areas:

  • Metadata & Skills shows findings for the uploaded package. Use Copy issues, fix the package, and select Upload plugin to fix issues. Required skill scans must finish before you can submit.
  • MCPs is where you connect the server. Select it, choose Connect, confirm the URL and auth, and complete domain verification. To verify, serve the exact challenge token as plain text (not JSON) at https://<your-host>/.well-known/openai-apps-challenge. Wait for the tool scan, then read Issues, fix them on your server, and select Rescan.

8. Submit for review#

Plugins with an MCP server need review material. You can include most of it in plugin.json under extensions.com.openai.review, or fill it in under Review information > Review details:

  • Exactly five positive test cases: prompt, expected tools, expected result.
  • Exactly three negative test cases: prompts where the plugin should decline or ask for clarification.
  • A video walkthrough URL showing the test cases.
  • Release notes.
  • Reviewer credentials, entered in the dashboard only: a demo account with sample data that works immediately, without MFA, email or SMS codes, or private network access.

Then select the draft, choose Submit for review, and complete the policy attestations. Track progress under Review status; feedback arrives by email. Only one review can be active per plugin. To appeal a rejection, reply to the rejection email.

9. Publish and keep it current#

Approval doesn't publish anything. Open the approved version and select Publish plugin.

After that:

  • Server changes are picked up automatically. OpenAI scans your MCP server daily, and eligible tool updates go live once they pass checks. Select Rescan to check sooner. New tools stay unavailable until approved, so keep your server compatible with the currently approved schemas.
  • Metadata or skill changes need a new ZIP with a bumped version, which goes through checks and review again.
  • Changing the MCP server URL isn't supported in the update flow; contact support.

Published plugins are found by direct link or by searching their exact name. Placement on the directory's main pages is chosen by OpenAI and can't be requested.

What gets rejected#

From OpenAI's guidelines and review FAQ, the common reasons are:

  • Reviewers can't connect, or the test credentials need MFA or have expired.
  • Test cases don't produce the expected results on every supported surface.
  • Tool results return data your privacy policy doesn't disclose, or include debug fields such as session, trace or request IDs.
  • readOnlyHint, destructiveHint or openWorldHint don't match what a tool does. Set all three explicitly on every tool.
  • Vague tool descriptions, or descriptions that steer the model away from other plugins.
  • Generic names, or names with "MCP", "MCP Server" or "Plugin" appended.
  • Demo or trial plugins, ads, or selling digital goods and subscriptions.
  • Unofficial connectors to someone else's service.
  • Collecting restricted data such as payment card details, health records, government IDs or passwords.

Next steps#

Keep reading