File Deleted
View Diff
1# Plugin submission errors
2
3Plugins submitted to the public directory are held to a higher standard than
4plugins installed in a workspace. Directory submissions must pass the shared
5package checks and the additional checks for listing fields, review materials,
6MCP tools, skills, assets, and images. This reference also covers shared
7package checks, such as app references, that can appear outside the submission
8portal.
9
10Use the error code returned during submission to find the matching requirement.
11Errors block submission. Warnings don't block submission, but you should review
12them before continuing.
13
14Non-empty values can't contain only whitespace. Supported text excludes control
15characters, Unicode line or paragraph separators, and unsupported invisible
16formatting characters. HTTPS URLs must include a host and contain no embedded
17credentials or unsupported characters.
18
19## Final directory submission
20
21A package can pass upload validation and still fail final directory submission.
22Final submission uses stricter listing limits and checks MCP configuration,
23skill scans, test cases, and policy attestations.
24
25| Field | Final submission rule |
26| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27| Package name | Required; at most 64 characters. Start with an ASCII letter or digit and use only ASCII letters, digits, `_`, and `-`. |
28| Version | Required; use a semantic version of at most 64 characters. |
29| Display name | Required; one line; at most 30 characters. |
30| Short description | Required; one line; at most 30 characters. |
31| Long description | Required; at most 4,000 characters. Line breaks are allowed. |
32| Developer name | Required; one line; at most 80 characters. |
33| Category | Required; choose a supported category listed in the [Listing and interface errors](#listing-and-interface-errors) section. |
34| Capabilities | At most 20. Each capability must be non-empty, one line, and at most 120 characters. |
35| Starter prompts | At most 3. Each prompt must be non-empty, unique after Unicode and whitespace normalization, one line, at most 128 characters, and contain no app `@mention`. |
36| URLs | Required for MCP-backed submissions; optional for skills-only submissions. Website, support, privacy policy, and terms URLs must use HTTPS and be at most 1,024 characters. |
37| Brand colors | Optional six-digit hex colors. The light color must have at least 2:1 contrast against white, and the dark color must have at least 2:1 contrast against `#212121`. |
38
39Every plugin submission also requires:
40
41- Passing safety and security scans for every bundled skill. Scans can take up
42 to 2 hours.
43- A verified developer or business identity and all required policy
44 attestations.
45
46For an MCP-backed plugin, final submission also requires:
47
48- Website, support, privacy policy, and terms URLs that meet the rules above.
49- A demo-recording URL that shows the main use cases and tools across supported
50 platforms.
51- Exactly five positive test cases, three negative test cases, and release
52 notes.
53- A production HTTPS MCP server URL, a completed domain-verification challenge,
54 and a successful, current tool scan.
55- Explicit `readOnlyHint`, `openWorldHint`, and `destructiveHint` values and a
56 justification for each value on every MCP tool.
57- Reviewer-ready demo credentials when the server uses OAuth.
58- Screenshots only when the MCP server provides custom UI. If you add
59 screenshots, provide one PNG or JPEG image for every starter prompt. Each
60 screenshot must be exactly 706 pixels wide and 400–860 pixels tall.
61
62### Final metadata errors
63
64In these error names, `subtitle` means short description and `description`
65means long description.
66
67| Name | Requirement |
68| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
69| `submission_display_name_required` | Display name is required, non-empty, and single-line. |
70| `submission_display_name_too_long` | Display name must be 30 characters or fewer. |
71| `submission_display_name_character_unsupported` | Display name must use supported text and fit on one line. |
72| `submission_subtitle_required` | Short description is required, non-empty, and single-line. |
73| `submission_subtitle_too_long` | Short description must be 30 characters or fewer. |
74| `submission_subtitle_character_unsupported` | Short description must use supported text and fit on one line. |
75| `submission_description_required` | Long description is required and must be non-empty. Line breaks are allowed. |
76| `submission_description_too_long` | Long description must be 4,000 characters or fewer. |
77| `submission_description_character_unsupported` | Long description must use supported text. Line breaks are allowed. |
78| `submission_developer_name_required` | Developer name is required, non-empty, and single-line. |
79| `submission_developer_name_too_long` | Developer name must be 80 characters or fewer. |
80| `submission_developer_name_character_unsupported` | Developer name must use supported text and fit on one line. |
81| `plugin_capability_invalid` | Each capability must be non-empty, use supported text, fit on one line, and be 120 characters or fewer. |
82| `plugin_default_prompt_mention` | Starter prompts must not contain app `@mentions`. |
83| `plugin_default_prompt_duplicate` | Starter prompts must be unique after Unicode and whitespace normalization. |
84
85### MCP and review errors
86
87These errors apply to MCP-backed submissions.
88
89| Name | Requirement |
90| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91| `annotations_required` | Every MCP tool must set `readOnlyHint`, `openWorldHint`, and `destructiveHint` accurately. |
92| `justification_required` | Every MCP tool annotation must include a justification for its read-only, open-world, or destructive behavior. |
93| `scan_required` | MCP tools must have a successful, current scan of the production MCP server. |
94| `domain_verification_required` | The exact verification token must be hosted at the generated `/.well-known/openai-apps-challenge` URL on the MCP host or an allowed parent host, and **Verify Domain** must pass. |
95| `frame_domain_explanation_required` | Every external frame domain reported by the MCP tool scan must have an explanation of why the UI needs it and what content it provides. |
96| `screenshots_not_allowed` | Screenshots are allowed only when the current MCP tool scan reports a UI output template. |
97
98## Archive errors
99
100### Skills-only ZIP upload errors and warnings
101
102**Skills only** uploads accept a plugin manifest and bundled skills. A changed
103package name blocks an update; the other findings require confirmation.
104
105| Name | Requirement |
106| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
107| `plugin_name_mismatch` | The package name in an update must match the existing plugin name. |
108| `plugin_version_unchanged` | A new release must use a different manifest `version`; reusing the published version requires confirmation. |
109| `mcp_configuration_excluded` | Skills-only ZIP uploads must not include `mcpServers` or `.mcp.json`; MCP-backed plugins must use **With MCP**. |
110| `app_configuration_excluded` | Skills-only ZIP uploads must not include `apps` or `.app.json`; plugins with app content must use **With MCP**. |
111| `screenshot_configuration_excluded` | Skills-only ZIP uploads must not include `interface.screenshots`; screenshots require **With MCP** and custom UI. |
112| `claude_format_normalized` | `.claude-plugin/plugin.json` is converted to `.codex-plugin/plugin.json`, with missing interface defaults and normalized text fields added by the portal. |
113| `manifest_normalized` | The portal saves the normalized manifest as `.codex-plugin/plugin.json`; changed fields require confirmation. |
114| `developer_name_defaulted` | `author.name` and `interface.developerName` must match, or the selected verified identity is used for both after confirmation. |
115
116### ZIP structure and limit errors
117
118| Name | Requirement |
119| --------------------------------------------- | ------------------------------------------------------------------------------------------------ |
120| `archive_empty` | Archive must not be empty. |
121| `archive_too_large` | Compressed ZIP must be 100 MB or less. |
122| `archive_format_not_zip` | Archive must be a valid, uncorrupted ZIP file. |
123| `archive_member_path_empty` | Archive entry path must not be empty. |
124| `archive_member_path_has_outer_whitespace` | Archive entry path must not begin or end with whitespace. |
125| `archive_member_path_has_backslash` | Archive entry path must use `/`, not backslashes. |
126| `archive_member_path_absolute` | Archive entry path must be relative to the archive root. |
127| `archive_member_path_has_empty_segment` | Archive entry path must not contain empty segments. |
128| `archive_member_path_has_parent_segment` | Archive entry path must not contain `..` segments. |
129| `archive_member_path_too_deep` | Archive entry path must contain at most 20 segments, including the filename. |
130| `archive_member_path_too_long` | Archive entry path must be within the supported path-length limit. |
131| `archive_member_path_normalization_collision` | Archive entry paths must remain unique after case and Unicode normalization. |
132| `archive_member_type_unsupported` | Archive entries must be regular files or directories. |
133| `archive_member_too_large` | Archive entry must not exceed 100 MiB. |
134| `archive_member_path_duplicate` | Archive entry path must be unique. |
135| `archive_member_path_type_conflict` | A file path cannot also be a directory or contain another archive entry. |
136| `archive_too_many_entries` | Archive must not contain more than 5,000 entries. |
137| `archive_uncompressed_too_large` | Extracted archive must not exceed 512 MiB. |
138| `archive_member_unreadable` | Every archive entry must be readable, must not be encrypted, and must use supported compression. |
139
140## Plugin root errors
141
142| Name | Requirement |
143| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
144| `plugin_root_missing` | The selected path must exist and be a directory containing a plugin. |
145| `archive_plugin_files_missing` | A skills-only ZIP must contain a supported plugin manifest and at least one valid skill at `skills/<skill>/SKILL.md`. |
146| `plugin_root_ambiguous` | ZIP must contain exactly one plugin root, either at the archive root or in one top-level directory. |
147| `plugin_root_has_siblings` | A ZIP with a top-level plugin directory must not contain sibling files. |
148
149## Plugin manifest errors
150
151| Name | Requirement |
152| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
153| `plugin_manifest_missing` | ZIP must contain `.codex-plugin/plugin.json`, `.agent-plugin/plugin.json`, or `.claude-plugin/plugin.json` at the root or in its single top-level directory. |
154| `plugin_manifest_not_file` | Plugin manifest must be a regular JSON file. |
155| `plugin_manifest_unreadable` | Plugin manifest must be readable UTF-8 text. |
156| `plugin_manifest_json_malformed` | Plugin manifest must contain valid JSON; malformed syntax is reported with a line number. |
157| `plugin_manifest_root_not_object` | Plugin manifest must contain a JSON object at the top level. |
158| `codex_manifest_parent_not_directory` | `.codex-plugin` must be a directory. |
159| `codex_manifest_path_not_file` | `.codex-plugin/plugin.json` must be a regular JSON file. |
160| `plugin_id_wrong_type` | `id` must be a string when provided. |
161| `plugin_id_empty` | `id` must be non-empty when provided. |
162| `plugin_name_missing` | `name` is required. |
163| `plugin_name_wrong_type` | `name` must be a string. |
164| `plugin_name_empty` | `name` must be non-empty. |
165| `plugin_name_too_long` | `name` must be 64 characters or fewer. |
166| `plugin_name_format` | `name` must start with an ASCII letter or digit and contain only ASCII letters, digits, `_`, or `-`. |
167| `plugin_version_missing` | `version` is required. |
168| `plugin_version_wrong_type` | `version` must be a string. |
169| `plugin_version_empty` | `version` must be a non-empty semantic-version string, such as `1.0.0`. |
170| `plugin_version_not_semver` | `version` must use semantic versioning, such as `1.0.0`. |
171| `plugin_version_too_long` | `version` must be 64 characters or fewer. |
172| `plugin_description_missing` | `description` is required. |
173| `plugin_description_wrong_type` | `description` must be a string. |
174| `plugin_description_empty` | `description` must be non-empty. |
175| `plugin_description_too_long` | `description` must be 1,024 characters or fewer. |
176| `plugin_description_character_unsupported` | `description` must use supported text. Line breaks are allowed. |
177| `plugin_developer_missing` | `author.name` is required. `interface.developerName` is also required and is reported separately. |
178| `plugin_author_wrong_type` | `author` must be an object. |
179| `plugin_author_name_wrong_type` | `author.name` must be a string. |
180| `plugin_author_name_empty` | `author.name` must be non-empty. |
181| `plugin_author_name_too_long` | `author.name` must be 120 characters or fewer. |
182| `plugin_author_name_character_unsupported` | `author.name` must use supported text. |
183| `plugin_author_email_wrong_type` | `author.email` must be a string when provided. |
184| `plugin_author_email_empty` | `author.email` must be non-empty when provided. |
185| `plugin_author_email_too_long` | `author.email` must be 320 characters or fewer. |
186| `plugin_author_email_character_unsupported` | `author.email` must use supported text. |
187| `plugin_author_url_wrong_type` | `author.url` must be a string when provided. |
188| `plugin_author_url_empty` | `author.url` must be non-empty when provided. |
189| `plugin_author_url_not_https` | `author.url` must be an HTTPS URL. |
190| `plugin_author_url_has_credentials` | `author.url` must not contain credentials. |
191| `plugin_author_url_too_long` | `author.url` must be 2,048 characters or fewer. |
192| `plugin_author_url_character_unsupported` | `author.url` must use supported text. |
193
194## Listing and interface errors
195
196The plugin manifest's `interface` object defines the public listing shown to
197users. It lives in `.codex-plugin/plugin.json` and uses fields such as
198`displayName` and `shortDescription`:
199
200```json
201{
202 "interface": {
203 "displayName": "Example Plugin",
204 "shortDescription": "Summarize documents",
205 "longDescription": "Summarize and organize documents.",
206 "developerName": "Example",
207 "category": "Productivity",
208 "capabilities": ["Summarize documents"]
209 }
210}
211```
212
213The four listing URLs (website, privacy policy, terms, and support) are
214optional for skills-only plugins and required for MCP-backed plugins. Their
215length limit is 2,048 characters for package validation and 1,024 characters
216for final directory submission.
217
218| Name | Requirement |
219| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
220| `plugin_interface_wrong_type` | The plugin manifest's `interface` field must be a JSON object. |
221| `plugin_display_name_wrong_type` | `interface.displayName` must be a string. |
222| `plugin_display_name_empty` | `interface.displayName` is required and must be non-empty. |
223| `plugin_display_name_too_long` | `interface.displayName` must be 80 characters or fewer for package validation and 30 characters or fewer for final directory submission. |
224| `plugin_display_name_character_unsupported` | `interface.displayName` must use supported text. |
225| `plugin_short_description_missing` | `interface.shortDescription` is required, must fit on one line, and must be 240 characters or fewer for package validation and 30 characters or fewer for final directory submission. |
226| `plugin_short_description_wrong_type` | `interface.shortDescription` must be a string. |
227| `plugin_short_description_empty` | `interface.shortDescription` must be non-empty. |
228| `plugin_short_description_too_long` | `interface.shortDescription` must be 240 characters or fewer for package validation and 30 characters or fewer for final directory submission. |
229| `plugin_short_description_character_unsupported` | `interface.shortDescription` must use supported text. |
230| `plugin_long_description_wrong_type` | `interface.longDescription` must be a string. |
231| `plugin_long_description_empty` | `interface.longDescription` is required and must be non-empty. |
232| `plugin_long_description_too_long` | `interface.longDescription` must be 4,000 characters or fewer. |
233| `plugin_long_description_character_unsupported` | `interface.longDescription` must use supported text. Line breaks are allowed. |
234| `plugin_developer_name_wrong_type` | `interface.developerName` must be a string. |
235| `plugin_developer_name_empty` | `interface.developerName` is required and must be non-empty. |
236| `plugin_developer_name_too_long` | `interface.developerName` must be 120 characters or fewer for package validation and 80 characters or fewer for final directory submission. |
237| `plugin_developer_name_character_unsupported` | `interface.developerName` must use supported text. |
238| `plugin_category_wrong_type` | `interface.category` must be a string. |
239| `plugin_category_empty` | `interface.category` must be non-empty when provided; omit it to use `Other`. |
240| `plugin_category_unknown` | `interface.category` must be `Productivity`, `Creativity`, `Developer Tools`, `Business & Operations`, `Data & Analytics`, `Communication`, `Education & Research`, `Security`, `Finance`, `Healthcare`, `Travel`, `Entertainment`, or `Other`. |
241| `plugin_category_character_unsupported` | `interface.category` must use supported text. |
242| `plugin_capabilities_wrong_type` | `interface.capabilities` must be a list of strings. |
243| `plugin_capabilities_too_many` | `interface.capabilities` must contain 20 entries or fewer. |
244| `plugin_capability_wrong_type` | Each `interface.capabilities` entry must be a string. |
245| `plugin_capability_empty` | Each `interface.capabilities` entry must be non-empty when provided. |
246| `plugin_capability_too_long` | Each `interface.capabilities` entry must be 120 characters or fewer. |
247| `plugin_capability_character_unsupported` | Each `interface.capabilities` entry must use supported text. |
248| `plugin_website_url_wrong_type` | `interface.websiteURL` must be a string when provided. |
249| `plugin_website_url_empty` | `interface.websiteURL` must be non-empty when provided. |
250| `plugin_website_url_format` | `interface.websiteURL` must be an HTTPS URL. |
251| `plugin_website_url_too_long` | `interface.websiteURL` must meet the listing URL length limits. |
252| `plugin_privacy_policy_url_wrong_type` | `interface.privacyPolicyURL` must be a string when provided. |
253| `plugin_privacy_policy_url_empty` | `interface.privacyPolicyURL` must be non-empty when provided. |
254| `plugin_privacy_policy_url_format` | `interface.privacyPolicyURL` must be an HTTPS URL. |
255| `plugin_privacy_policy_url_too_long` | `interface.privacyPolicyURL` must meet the listing URL length limits. |
256| `plugin_terms_of_service_url_wrong_type` | `interface.termsOfServiceURL` must be a string when provided. |
257| `plugin_terms_of_service_url_empty` | `interface.termsOfServiceURL` must be non-empty when provided. |
258| `plugin_terms_of_service_url_format` | `interface.termsOfServiceURL` must be an HTTPS URL. |
259| `plugin_terms_of_service_url_too_long` | `interface.termsOfServiceURL` must meet the listing URL length limits. |
260| `plugin_support_url_wrong_type` | `interface.supportURL` must be a string when provided. |
261| `plugin_support_url_empty` | `interface.supportURL` must be non-empty when provided. |
262| `plugin_support_url_format` | `interface.supportURL` must be an HTTPS URL. |
263| `plugin_support_url_too_long` | `interface.supportURL` must meet the listing URL length limits. |
264| `plugin_homepage_wrong_type` | `homepage` must be a string when provided. |
265| `plugin_homepage_empty` | `homepage` must be non-empty when provided. |
266| `plugin_homepage_format` | `homepage` must be an HTTPS URL. |
267| `plugin_homepage_too_long` | `homepage` must be 2,048 characters or fewer. |
268| `plugin_brand_color_wrong_type` | `interface.brandColor` must be a string when provided. |
269| `plugin_brand_color_empty` | `interface.brandColor` must be non-empty when provided. |
270| `plugin_brand_color_format` | `interface.brandColor` must be a six-digit hex color, such as `#1ABCFE`. |
271| `plugin_brand_color_dark_wrong_type` | `interface.brandColorDark` must be a string when provided. |
272| `plugin_brand_color_dark_empty` | `interface.brandColorDark` must be non-empty when provided. |
273| `plugin_brand_color_dark_format` | `interface.brandColorDark` must be a six-digit hex color, such as `#1ABCFE`. |
274| `plugin_brand_color_contrast` | `interface.brandColor` must have at least 2:1 contrast against white. |
275| `plugin_brand_color_dark_contrast` | `interface.brandColorDark` must have at least 2:1 contrast against `#212121`. |
276| `plugin_default_prompt_wrong_type` | `interface.defaultPrompt` must be a string or list of strings. |
277| `plugin_default_prompt_too_many` | `interface.defaultPrompt` must contain at most three prompts. |
278| `plugin_default_prompt_entry_wrong_type` | Each `interface.defaultPrompt` entry must be a string. |
279| `plugin_default_prompt_empty` | Each `interface.defaultPrompt` entry must be non-empty when provided. |
280| `plugin_default_prompt_too_long` | Each `interface.defaultPrompt` entry must be 512 characters or fewer for package validation and 128 characters or fewer for final directory submission. |
281| `plugin_default_prompt_character_unsupported` | Each `interface.defaultPrompt` entry must use supported text and fit on one line. |
282
283## Plugin content errors
284
285| Name | Requirement |
286| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
287| `plugin_skills_path_wrong_type` | `skills` must be a string path for the root `skills/` directory. |
288| `plugin_skills_path_empty` | `skills` must be a non-empty path to the root `skills/` directory when provided. |
289| `plugin_skills_path_unsupported` | `skills` must resolve to the root `skills/` directory. |
290| `plugin_skills_directory_missing` | A declared root `skills/` directory must exist. |
291| `plugin_skills_path_not_directory` | Root `skills/` must be a directory when declared. |
292| `plugin_apps_path_wrong_type` | `apps` must be a string path for the root `.app.json`. |
293| `plugin_apps_path_empty` | `apps` must be a non-empty path to the root `.app.json` when provided. |
294| `plugin_apps_path_unsupported` | `apps` must resolve to the root `.app.json`. |
295| `plugin_apps_file_missing` | A declared root `.app.json` file must exist. |
296| `plugin_apps_path_not_file` | Root `.app.json` must be a regular file when declared. |
297| `plugin_runtime_surface_missing` | A skills-only ZIP must contain at least one valid skill at `skills/<skill>/SKILL.md`; app and MCP references don't satisfy this requirement. |
298
299## Skill errors
300
301| Name | Requirement |
302| ----------------------------------------- | --------------------------------------------------------------------------------------------- |
303| `skill_manifest_missing` | Skill must contain a `SKILL.md` file. |
304| `skill_bundle_too_large` | Each compressed skill bundle must be within the MiB limit reported in the error. |
305| `skill_directory_hidden` | Skill directory names must not begin with `.`. |
306| `skill_manifest_nested` | Each skill directory must be an immediate child of `skills/`. |
307| `skill_manifest_not_regular_file` | `SKILL.md` must be a regular file. |
308| `skill_manifest_unreadable` | `SKILL.md` must be readable. |
309| `skill_manifest_invalid_utf8` | `SKILL.md` must contain valid UTF-8. |
310| `skill_frontmatter_missing` | `SKILL.md` must start with YAML front matter between `---` lines. |
311| `skill_frontmatter_unclosed` | `SKILL.md` YAML front matter must end with `---`. |
312| `skill_frontmatter_yaml_malformed` | `SKILL.md` front matter must contain valid YAML. |
313| `skill_frontmatter_wrong_type` | `SKILL.md` front matter must contain a YAML mapping. |
314| `skill_name_missing` | `name` is required and must not be empty. |
315| `skill_name_wrong_type` | `name` must be a string. |
316| `skill_name_empty` | `name` must be non-empty. |
317| `skill_name_character_unsupported` | Skill front matter `name` must use supported text. |
318| `skill_description_missing` | `description` is required and must not be empty. |
319| `skill_description_wrong_type` | `description` must be a string. |
320| `skill_description_empty` | `description` must be non-empty. |
321| `skill_description_too_long` | `description` must be 1,024 characters or fewer. |
322| `skill_description_character_unsupported` | Skill front matter `description` must use supported text. |
323| `skill_body_empty` | Skill instructions must not be empty. |
324| `skill_identity_too_long` | The combined plugin and skill name (`plugin-name:skill-name`) must be 64 characters or fewer. |
325| `skill_identity_duplicate` | Each skill `name` must be unique within the plugin. |
326
327## Skill agent metadata errors
328
329A bundled skill can define its own `interface` in
330`skills/<skill>/agents/openai.yaml`. This controls how the skill appears to
331users and is separate from the plugin manifest's `interface`. Skill interface
332fields use snake_case:
333
334```yaml
335interface:
336 display_name: "Summarize documents"
337 short_description: "Summarize a document"
338 icon_small: "./assets/icon.png"
339 default_prompt: "Summarize the selected document."
340```
341
342| Name | Requirement |
343| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
344| `skill_agent_not_regular_file` | `agents/openai.yaml` must be a regular file. |
345| `skill_agent_unreadable` | `agents/openai.yaml` must be readable. |
346| `skill_agent_invalid_utf8` | `agents/openai.yaml` must contain valid UTF-8. |
347| `skill_agent_yaml_malformed` | `agents/openai.yaml` must contain valid YAML. |
348| `skill_agent_top_level_wrong_type` | `agents/openai.yaml` must contain a YAML mapping at the top level. |
349| `skill_agent_interface_missing` | `interface` is required in `agents/openai.yaml` when that file is included. |
350| `skill_agent_interface_wrong_type` | `interface` in `agents/openai.yaml` must be a YAML mapping. |
351| `skill_agent_display_name_missing` | `interface.display_name` is required and must not be empty. |
352| `skill_agent_display_name_wrong_type` | `interface.display_name` must be a string. |
353| `skill_agent_display_name_empty` | `interface.display_name` must not be empty. |
354| `skill_agent_short_description_missing` | `interface.short_description` is required and must not be empty. |
355| `skill_agent_short_description_wrong_type` | `interface.short_description` must be a string. |
356| `skill_agent_short_description_empty` | `interface.short_description` must not be empty. |
357| `skill_agent_icon_small_wrong_type` | `interface.icon_small` must be a non-empty relative file path when provided. |
358| `skill_agent_icon_small_empty` | `interface.icon_small` must be a non-empty relative file path when provided, such as `assets/icon.png`. |
359| `skill_agent_icon_large_wrong_type` | `interface.icon_large` must be a non-empty relative file path when provided. |
360| `skill_agent_icon_large_empty` | `interface.icon_large` must be a non-empty relative file path when provided, such as `assets/icon.png`. |
361| `skill_agent_brand_color_wrong_type` | `interface.brand_color` must be a string when provided. |
362| `skill_agent_brand_color_empty` | `interface.brand_color` must be a non-empty six-digit hex color when provided, such as `#1ABCFE`. |
363| `skill_agent_brand_color_format` | `interface.brand_color` must be a six-digit hex color, such as `#1ABCFE`. |
364| `skill_agent_default_prompt_wrong_type` | `interface.default_prompt` must be a string when provided. |
365| `skill_agent_default_prompt_empty` | `interface.default_prompt` must be non-empty when provided. |
366| `skill_agent_policy_wrong_type` | `policy` must be a YAML mapping when provided. |
367| `skill_agent_allow_implicit_invocation_wrong_type` | `policy` may contain only `products` and `allow_implicit_invocation`. `products` must contain `CHAT`, `CODEX`, or both, and `allow_implicit_invocation` must be `true` or `false`. |
368| `skill_agent_dependencies_wrong_type` | `dependencies` must be a YAML mapping; only `tools` is supported. |
369| `skill_agent_dependency_unsupported` | Only `dependencies.tools` is supported in `agents/openai.yaml`. |
370
371## Asset path errors
372
373| Name | Requirement |
374| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
375| `declared_asset_path_wrong_type` | The named asset field must be a file path string. |
376| `declared_asset_path_empty` | The named asset field must not be empty. |
377| `declared_asset_path_has_outer_whitespace` | The named asset field must not begin or end with whitespace. |
378| `declared_asset_path_has_control_character` | The named asset field must not contain characters U+0000–U+001F or U+007F. |
379| `branding_asset_path_missing_root_prefix` | The named asset field must start with `./`. |
380| `declared_asset_path_unsafe` | The named asset field must be a relative path inside the plugin and must not contain an absolute path, drive prefix, or `..` traversal segment. |
381| `declared_asset_path_outside_package` | The named asset field must reference a file inside the plugin. |
382| `declared_asset_file_missing` | The named asset field references a file that does not exist. |
383| `declared_asset_not_regular_file` | The named asset field must reference a file, not a directory or special file. |
384
385## Image errors
386
387Directory branding images must use a supported file type and meet the size and
388dimension limits below. These rules apply to packaged branding assets;
389starter-prompt screenshots use the separate portal limits listed above.
390
391| Name | Requirement |
392| ----------------------------------------- | -------------------------------------------------------------------------- |
393| `plugin_logo_path_missing` | `interface.logo` is required and must reference a square image. |
394| `plugin_composer_icon_path_missing` | `interface.composerIcon` is required and must reference a square image. |
395| `image_file_unreadable` | Image file must be readable. |
396| `image_file_too_large` | Image must not exceed 5 MiB. |
397| `image_file_format_unsupported` | Image filename must end in `.png`, `.jpg`, `.jpeg`, `.webp`, or `.svg`. |
398| `raster_image_decode_failed` | Raster image must be a PNG, JPEG, or WebP file that can be decoded safely. |
399| `raster_image_extension_content_mismatch` | Image filename extension must match the detected image format. |
400| `raster_image_not_square` | Image must be square. |
401| `raster_image_dimensions_too_small` | Image dimensions must be at least 48×48 pixels. |
402| `raster_image_dimensions_too_large` | Image dimensions must not exceed 4,096×4,096 pixels. |
403| `svg_xml_malformed` | SVG must contain valid UTF-8 XML. |
404| `svg_root_element_invalid` | SVG root element must be `<svg>`. |
405| `svg_dimensions_missing` | SVG must define a numeric `viewBox` or numeric `width` and `height`. |
406| `svg_dimensions_not_numeric` | SVG dimensions must be numeric and omit units and percentages. |
407| `svg_dimensions_not_positive` | SVG width and height must be positive finite numbers. |
408| `svg_dimensions_not_square` | SVG dimensions must be square. |
409| `svg_dimensions_too_small` | SVG dimensions must be at least 48×48 pixels. |
410
411## App reference errors
412
413The shared package checks validate `.app.json` when a plugin references apps.
414The submission portal doesn't publish references to existing ChatGPT apps: a
415**Skills only** upload removes `.app.json`, and an MCP-backed submission must
416use **With MCP** and submit the MCP server directly.
417
418For local or workspace packages, the top-level `apps` object maps each app
419alias to an app entry.
420
421| Name | Requirement |
422| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
423| `app_manifest_unreadable` | `.app.json` must be readable UTF-8 text. |
424| `app_manifest_json_malformed` | `.app.json` contains malformed JSON near the reported line. |
425| `app_manifest_wrong_type` | `.app.json` must contain a JSON object at the top level. |
426| `app_entries_missing` | `apps` is required. |
427| `app_entries_wrong_type` | `apps` must be an object. |
428| `app_entry_wrong_type` | Each app entry must be an object. |
429| `app_id_missing` | Each app entry's `id` is required. |
430| `app_id_wrong_type` | Each app entry's `id` must be a string. |
431| `app_id_format` | Each app entry's `id` must begin with `asdk_app_`, `connector_`, or `templated_apps_`, followed by a letter or digit and then only letters, digits, `_`, or `-`. |
432| `app_entry_optional_wrong_type` | Each app entry's `optional` value must be `true` or `false` when provided. |
433| `app_entry_required_wrong_type` | Each app entry's `required` value must be `true` or `false` when provided. |
434| `app_not_eligible` | For a local or workspace package, each referenced app must be a released public Codex app, available connector, or released app template. Directory submissions must use **With MCP** and submit the MCP server directly. |
435
436## Package warnings
437
438These warnings identify package content that validation ignores or normalizes.
439They don't block submission. Review them to confirm the submitted plugin
440contains the expected files and settings.
441
442| Name | Requirement |
443| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
444| `duplicate_app_reference` | Each app ID in `.app.json` must be referenced once; duplicate references are treated as one app. |
445| `undeclared_app_manifest_ignored` | A root `.app.json` is imported only when the plugin-manifest `apps` field is set to `./.app.json`. |
446| `undeclared_mcp_manifest_ignored` | A root `.mcp.json` is imported only when the plugin-manifest `mcpServers` field is set to `./.mcp.json`. |
447| `skill_file_ignored` | Files directly under `skills/` aren't imported as skills; each skill must be in a directory containing `SKILL.md`. |
448| `skill_symlink_ignored` | Symbolic links directly under `skills/` aren't imported as skills; each skill must be a real directory containing `SKILL.md`. |
449| `skill_frontmatter_adjusted` | Skill `name` and `description` are normalized during import by trimming outer whitespace and collapsing internal whitespace. |
450| `skill_metadata_ignored` | Skill interface settings must use the `interface` mapping in `agents/openai.yaml`; `metadata` in `SKILL.md` doesn't configure the interface. |
451
452## Next steps
453
454After resolving all validation errors, return to
455[Submit plugins](https://learn.chatgpt.com/docs/submit-plugins) to complete the submission.