Codex Manifest Shape: How Codex Manages Add-ins
Deep Research Report | June 26, 2026
Sources: OpenAI official docs, Codex SDK v0.16.1 (HexDocs), HuggingFace Context Course, community guides, GitHub issues
Confidence: High — primary source documentation + SDK type specs + practitioner validation
Executive Summary
OpenAI Codex uses a manifest-first plugin system where every plugin is identified by a single required file: .codex-plugin/plugin.json. This manifest declares the plugin's identity, points to bundled components (skills, MCP servers, app connectors, lifecycle hooks), and provides install-surface metadata for marketplace presentation.
Key facts:
- Only name (kebab-case) is strictly required in the manifest
- Plugins bundle up to 4 component types: Skills (brain), Apps (hands), MCP Servers (nervous system), Hooks (lifecycle)
- Distribution uses Git-native marketplaces — JSON catalogs that can be repo-scoped, personal, or official
- Plugins install into ~/.codex/plugins/cache/$MARKETPLACE/$PLUGIN/$VERSION/
- The system is forward-compatible: unknown keys survive round-trip serialization via extra maps
1. Manifest File: plugin.json
Location & Role
The manifest lives at .codex-plugin/plugin.json — the only file permitted inside the .codex-plugin/ directory. All other components (skills/, hooks/, assets/, .mcp.json, .app.json) live at the plugin root.
The manifest has three jobs: 1. Identify the plugin (name, version, author) 2. Point to bundled components (skills, MCP servers, apps, hooks) 3. Provide marketplace metadata (descriptions, icons, legal links, starter prompts)
Complete Manifest Example
{
"name": "my-plugin",
"version": "0.1.0",
"description": "Bundle reusable skills and app integrations.",
"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 apps",
"longDescription": "Distribute skills and app integrations 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"]
}
}
2. Field Reference
Top-Level Required Fields
| Field | Type | Validation |
|---|---|---|
name |
string | Must be non-empty kebab-case (lowercase, numbers, hyphens only) |
Only
nameis strictly required. Everything else is optional.
Top-Level Optional Fields
| Field | Type | Purpose |
|---|---|---|
version |
string | Semver string (e.g., "1.0.0") — determines cache path |
description |
string | Human-readable summary |
author |
object | {name, email?, url?} |
homepage |
string | Landing page URL |
repository |
string | Source code repo URL |
license |
string | SPDX identifier (e.g., "MIT") |
keywords |
string[] | Discovery tags |
skills |
string | Path to skills directory (must start with ./) |
mcpServers |
string | Path to .mcp.json file |
apps |
string | Path to .app.json file |
hooks |
string/array/object | Path(s) or inline hook definitions |
interface |
object | Install-surface metadata (see §3) |
The interface Object
| JSON Key | Purpose | Constraint |
|---|---|---|
displayName |
Plugin title in marketplace | — |
shortDescription |
One-liner on plugin cards | — |
longDescription |
Detail page description | — |
developerName |
Publisher name | — |
category |
Marketplace category | e.g., "Productivity" |
capabilities |
Capability badges | e.g., ["Read", "Write"] |
websiteURL |
External link | — |
privacyPolicyURL |
Privacy policy | Required for published plugins |
termsOfServiceURL |
Terms of service | — |
defaultPrompt |
Starter prompts in composer | Max 3 entries, each ≤128 chars |
brandColor |
Hex color | e.g., "#10A37F" |
composerIcon |
Icon path | ./assets/icon.png |
logo |
Logo path | ./assets/logo.png |
screenshots |
Screenshot paths | Array of ./assets/* paths |
3. Validation Rules (SDK-Enforced)
The Codex SDK (Elixir, Codex.Plugins.Manifest.parse!/1) enforces these stable rules:
| Rule | Detail |
|---|---|
| Kebab-case name | Non-empty, lowercase + numbers + hyphens only |
./ prefix required |
All component paths and asset paths must start with ./ |
No .. escape |
Paths cannot traverse above the plugin root |
| defaultPrompt limit | At most 3 entries |
| Prompt length cap | Each ≤128 characters after whitespace normalization |
| Deterministic JSON | Writes produce stable JSON with trailing newline |
| Forward-compatible keys | Unknown keys preserved in extra maps, survive round-trips |
# Validation API
{:ok, manifest} = Codex.Plugins.Manifest.parse(data)
Codex.Plugins.Manifest.parse!(data) # raises on error
4. Directory Layout
my-plugin/ ← plugin root
├── .codex-plugin/
│ └── plugin.json ← REQUIRED (only file in this dir)
├── skills/ ← "skills": "./skills/"
│ ├── code-review/
│ │ └── SKILL.md
│ └── deploy/
│ └── SKILL.md
├── hooks/
│ └── hooks.json ← "hooks": "./hooks/hooks.json"
├── .app.json ← "apps": "./.app.json"
├── .mcp.json ← "mcpServers": "./.mcp.json"
└── assets/
├── icon.png
├── logo.png
└── screenshot-1.png
5. Component Wiring: How Add-ins Are Referenced
5.1 Skills (The "Brain" Layer)
Manifest pointer: "skills": "./skills/"
Codex scans the directory for subdirectories containing SKILL.md files. Each skill has YAML frontmatter:
---
name: deploy-kubernetes
description: Deploy containerized apps to Kubernetes clusters.
---
## Workflow
1. Verify kubectl context...
2. Generate manifests...
Activation modes:
- Implicit (default): Auto-loaded when user's task semantically matches the description
- Explicit: Invoked with $skill-name syntax
Config override (config.toml):
[skills.deploy-kubernetes]
enabled = true
invocation = "explicit" # or "implicit"
priority = 10 # higher wins on conflicts
Resolution precedence: REPO > USER > ADMIN > SYSTEM > DEFAULTS
5.2 MCP Servers (The "Nervous System" Layer)
Manifest pointer: "mcpServers": "./.mcp.json"
Two equivalent formats:
// Direct server map
{
"docs": {
"command": "docs-mcp",
"args": ["--stdio"]
}
}
// Wrapped (alternative)
{
"mcp_servers": {
"docs": {
"command": "docs-mcp",
"args": ["--stdio"]
}
}
}
Each server entry: command (required), args (required), env (optional).
Per-server policy override (config.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"
Approval modes: "approve" (auto-execute), "prompt" (ask user), "deny" (block)
5.3 Apps/Connectors (The "Hands" Layer)
Manifest pointer: "apps": "./.app.json"
Defines third-party service connectors with authentication:
{
"apps": [{
"name": "github-connector",
"auth": {
"type": "oauth2",
"client_id": "${GITHUB_CLIENT_ID}",
"authorization_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"scopes": ["repo", "read:org"]
}
}]
}
Auth types: OAuth 2.0, API key
Auth timing: Controlled by marketplace policy — ON_INSTALL or ON_FIRST_USE
5.4 Lifecycle Hooks
Manifest pointer: "hooks": "./hooks/hooks.json" (auto-discovered if not specified)
The hooks field accepts four formats:
1. Single path: "./hooks/hooks.json"
2. Array of paths: ["./hooks/session.json", "./hooks/tools.json"]
3. Inline object
4. Array of inline objects
Hook event schema:
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "python3 ${PLUGIN_ROOT}/hooks/session_start.py",
"statusMessage": "Loading plugin context"
}]
}]
}
}
Supported events: SessionStart, TurnStarted, TurnCompleted, ToolCallRequested, ToolCallCompleted
Environment variables available to hook commands:
| Variable | Description |
|----------|-------------|
| PLUGIN_ROOT | Installed plugin root path |
| PLUGIN_DATA | Plugin's writable data directory |
| CLAUDE_PLUGIN_ROOT | Compatibility alias |
| CLAUDE_PLUGIN_DATA | Compatibility alias |
⚠️ Trust model: Plugin hooks are non-managed — Codex skips them until the user explicitly reviews and trusts the hook definition. Installing a plugin does NOT auto-trust its hooks.
6. Distribution: The Marketplace System
Marketplace JSON Structure
{
"name": "repo-marketplace",
"interface": { "displayName": "Team Plugins" },
"plugins": [
{
"name": "demo-plugin",
"source": {
"source": "local",
"path": "./plugins/demo-plugin"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL",
"products": ["codex-app", "codex-cli"]
},
"category": "Productivity"
}
]
}
Marketplace Scopes
| Tier | Location | Visibility |
|---|---|---|
| Official | OpenAI-hosted | Everyone |
| Repository | $REPO_ROOT/.agents/plugins/marketplace.json |
Repo collaborators |
| Personal | ~/.agents/plugins/marketplace.json |
Current user |
| Legacy | $REPO_ROOT/.claude-plugin/marketplace.json |
Cross-tool compat |
Source Types
| Type | Format | Use Case |
|---|---|---|
local |
"./relative/path" |
Same-repo plugins |
url |
Full HTTPS/SSH URL | Standalone plugin repos |
git-subdir |
URL + subdirectory path | Monorepo plugins |
| GitHub shorthand | owner/repo[@ref] |
Quick references |
Git-backed marketplace entry example:
{
"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"
}
Policy Controls
| Field | Values | Effect |
|---|---|---|
policy.installation |
AVAILABLE, PREINSTALLED, HIDDEN |
Discovery/auto-install behavior |
policy.authentication |
ON_INSTALL, ON_FIRST_USE, NONE |
Auth timing |
policy.products |
string[] | Restrict to specific Codex surfaces |
CLI Commands
# Marketplace management
codex plugin marketplace add owner/repo
codex plugin marketplace add ./local-path
codex plugin marketplace list
codex plugin marketplace upgrade
codex plugin marketplace remove <name>
# Plugin lifecycle
codex plugin install <plugin-name>
codex plugin enable <plugin-id>
codex plugin disable <plugin-id>
codex plugin remove <plugin-id>
codex plugin upgrade <plugin-id>
Install Cache
Plugins install to:
~/.codex/plugins/cache/$MARKETPLACE_NAME/$PLUGIN_NAME/$VERSION/
- Local plugins:
$VERSION = "local" - Codex loads from cache, not source (enables offline use)
- Cache persists after uninstall (supports re-activation)
- Tracks
gitCommitShafor version verification
Plugin State
Controlled via settings files:
- User: ~/.codex/settings.json
- Project: ./.codex/settings.json (git-tracked)
- Local: ./.codex/settings.local.json (gitignored)
{
"enabledPlugins": {
"my-plugin@repo-marketplace": true,
"experimental@personal": false
}
}
7. Runtime Resolution Flow
┌─────────────────────────────┐
│ .codex-plugin/plugin.json │
└──────────────┬──────────────┘
┌───────────┬──────────┼──────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
"skills" "mcpServers" "apps" "hooks" "interface"
"./skills/" "./.mcp.json" ".app" "./hooks/" (metadata)
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐
│ Scan for │ │ Spawn │ │Resolve │ │ Await │
│ SKILL.md │ │ process │ │ creds │ │ user │
│ files │ │ per │ │(OAuth/ │ │ trust │
│ │ │ entry │ │ APIkey)│ │ review │
└────┬─────┘ └────┬────┘ └───┬────┘ └────┬────┘
▼ ▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐
│ Index by │ │Register │ │Register│ │ Execute │
│ name+desc│ │ tools │ │ caps │ │ command │
│ for auto │ │ in agent│ │ as │ │ on │
│ matching │ │ │ │ tools │ │ event │
└──────────┘ └─────────┘ └────────┘ └─────────┘
At install: Files copied to cache → components registered
At runtime: Enabled plugins loaded → skills indexed → MCP servers spawned → apps activated → trusted hooks armed
8. Ecosystem Comparison
| Dimension | Codex | Claude Code | Cursor | VS Code |
|---|---|---|---|---|
| Model | Manifest-first | Manifest-first | IDE-native | Extension API |
| Manifest | .codex-plugin/plugin.json |
.claude-plugin/plugin.json |
N/A | package.json |
| Distribution | Git repos + cache | Git repos + cache | Central store | .vsix packages |
| Components | Skills + MCP + Apps + Hooks | Skills + MCP + Hooks + LSP | Extensions | Extensions |
| Offline | ✅ | ✅ | ❌ | ✅ |
| Cross-tool MCP | ✅ | ✅ | ✅ (consumer) | ❌ |
Key insight: MCP is the universal interop layer. A well-built MCP server works across Codex, Claude Code, and Cursor. The wrapping layer (marketplace metadata, skill definitions) is tool-specific, but the functional infrastructure is portable.
9. Minimal → Published Progression
Absolute Minimum (Valid)
{ "name": "my-plugin" }
Minimal Functional
{
"name": "my-plugin",
"version": "1.0.0",
"description": "Reusable greeting workflow",
"skills": "./skills/"
}
Published-Quality
All fields from §1 example, with particular attention to:
- interface.privacyPolicyURL and termsOfServiceURL (required for official directory)
- interface.defaultPrompt (max 3 × 128-char starter prompts)
- interface.logo and screenshots (marketplace presence)
- author with at least name
- keywords for discoverability
10. Key Takeaways
- Manifest-first is declarative — no code runs at install time; JSON is inspectable and portable
- Only
nameis required — start minimal, add complexity as needed - Three-layer architecture separates concerns: Skills (knowledge), Apps (auth), MCP (tools)
- Trust is explicit — hooks don't auto-execute; users must review and approve
- Forward-compatible — unknown keys survive SDK round-trips via
extramaps - Git-native distribution — plugins live in repos; no compilation, no build step
- MCP is the portability layer — same servers work across Codex, Claude Code, and Cursor
Compiled from 3 parallel research tracks. Individual agent files available in artifacts/research/codex-manifest-shape/.
Primary sources: OpenAI Codex Docs, Codex SDK HexDocs, HuggingFace Context Course