SpyBara
Go Premium

Documentation 2026-10-14 23:59 UTC to 2026-10-15 23:00 UTC

29 files changed +3,860 −708. View all changes and history on the product overview
2026
Fri 30 23:59 Thu 29 23:59 Wed 28 23:00 Tue 27 23:58 Mon 26 23:58 Fri 23 23:59 Thu 22 23:57 Tue 20 23:02 Mon 19 23:02 Sat 17 23:00 Fri 16 23:02 Thu 15 23:00 Wed 14 23:59 Tue 13 23:01 Mon 12 23:01 Fri 9 23:02 Thu 8 23:01 Wed 7 23:57 Tue 6 23:02 Mon 5 23:57 Fri 2 23:58 Thu 1 22:59
Details

38 ],38 ],

39 },39 },

40 {40 {

41 title: "Feature setup",41 title: "Feature Configuration",

42 description:42 description:

43 "Choose a setup task, then review its access and security requirements.",43 "Choose a setup task, then review its access and security requirements.",

44 pages: [44 pages: [


56 href: "/codex/space/collaboration#for-workspace-admins",56 href: "/codex/space/collaboration#for-workspace-admins",

57 icon: "userLock",57 icon: "userLock",

58 },58 },

59 {

60 title: "Sites administration",

61 description:

62 "Enable sites, configure connections, and review hosting, access controls, and data responsibilities.",

63 href: "/codex/enterprise/sites",

64 icon: "shieldCheck",

65 },

59 {66 {

60 title: "Set up teams and Team Tasks",67 title: "Set up teams and Team Tasks",

61 description:68 description:


84 href: "/codex/enterprise/cloud-local-access#how-to-set-up-local-computer-access",91 href: "/codex/enterprise/cloud-local-access#how-to-set-up-local-computer-access",

85 icon: "settings",92 icon: "settings",

86 },93 },

87 {

88 title: "Set up Sites and connected plugins",

89 description:

90 "Enable tenant and plugin access so Sites can use visitors' connected accounts.",

91 href: "/codex/enterprise/sites#enable-plugin-use-in-sites",

92 icon: "connect",

93 },

94 ],94 ],

95 },95 },

96 {96 {

app/features.md +46 −7

Details

33 href: "/codex/mobile",33 href: "/codex/mobile",

34 icon: "connect",34 icon: "connect",

35 },35 },

36 {

37 title: "Sites",

38 description:

39 "Create, save, and publish interactive websites and apps in ChatGPT.",

40 href: "/codex/sites",

41 icon: "workspace",

42 },

43 {36 {

44 title: "Build plugins",37 title: "Build plugins",

45 description:38 description:


114 href: "/codex/features/voice",107 href: "/codex/features/voice",

115 icon: "chat",108 icon: "chat",

116 },109 },

110 {

111 title: "Composer Predictions",

112 description: "Get suggestions for your next message in Codex.",

113 href: "/codex/features/composer-predictions",

114 icon: "chat",

115 },

117 {116 {

118 title: "Plugins",117 title: "Plugins",

119 description:118 description:


169 },168 },

170 ],169 ],

171 },170 },

171 {

172 title: "Sites",

173 description: "Build, connect, publish, and manage websites in ChatGPT.",

174 pages: [

175 {

176 title: "Overview",

177 description:

178 "Explore what you can build and find availability and limits.",

179 href: "/codex/sites",

180 icon: "workspace",

181 },

182 {

183 title: "Create a site",

184 description: "Describe, build, and refine a website in ChatGPT.",

185 href: "/codex/sites/creating-a-site",

186 icon: "workspace",

187 },

188 {

189 title: "Data and connections",

190 description:

191 "Save records, connect apps, and add sign-in to your site.",

192 href: "/codex/sites/data-and-connections",

193 icon: "workspace",

194 },

195 {

196 title: "Publish and share",

197 description:

198 "Review your site, choose its audience, and invite editors.",

199 href: "/codex/sites/publishing-and-sharing",

200 icon: "workspace",

201 },

202 {

203 title: "Manage a site",

204 description:

205 "Review analytics, change settings, and manage site versions.",

206 href: "/codex/sites/managing-a-site",

207 icon: "workspace",

208 },

209 ],

210 },

172 {211 {

173 title: "ChatGPT dots",212 title: "ChatGPT dots",

174 description: "Set up your dot and manage its ongoing work.",213 description: "Set up your dot and manage its ongoing work.",

Details

149 149 

150Including an app does not grant new access. Your workspace’s app and action settings, connection permissions, and access in the source service still apply. Teammates who use the plugin also need the required app access and connection setup.150Including an app does not grant new access. Your workspace’s app and action settings, connection permissions, and access in the source service still apply. Teammates who use the plugin also need the required app access and connection setup.

151 151 

152### Connect a custom MCP server

153 

154If an existing app doesn’t cover your workflow, [connect your tools with a custom MCP server](https://learn.chatgpt.com/docs/extend/mcp#build-an-mcp-plugin). Use Plugin Creator and Sites to build a plugin that connects to your system.

155 

152## Test your plugin156## Test your plugin

153 157 

154Install the plugin if prompted, then start a new conversation. Select it with an **@ mention** and give it a familiar task. For Document Summary, provide a document you know well and ask for a summary.158Install the plugin if prompted, then start a new conversation. Select it with an **@ mention** and give it a familiar task. For Document Summary, provide a document you know well and ask for a summary.

Details

2331 type: "table",2331 type: "table",

2332 description: "Managed requirements for agent-driven Browser Use.",2332 description: "Managed requirements for agent-driven Browser Use.",

2333 },2333 },

2334 {

2335 key: "browser_use.allow_webmcp",

2336 type: "boolean",

2337 description:

2338 "Set to `true` to opt the desktop app's built-in browser into site tools (WebMCP). Setting it to `false` or omitting it leaves normal feature availability checks in place; `false` does not enforce a block. User preferences and site permissions still apply.",

2339 },

2334 {2340 {

2335 key: "browser_use.allow_history_access",2341 key: "browser_use.allow_history_access",

2336 type: "boolean",2342 type: "boolean",

Details

2331 type: "table",2331 type: "table",

2332 description: "Managed requirements for agent-driven Browser Use.",2332 description: "Managed requirements for agent-driven Browser Use.",

2333 },2333 },

2334 {

2335 key: "browser_use.allow_webmcp",

2336 type: "boolean",

2337 description:

2338 "Set to `true` to opt the desktop app's built-in browser into site tools (WebMCP). Setting it to `false` or omitting it leaves normal feature availability checks in place; `false` does not enforce a block. User preferences and site permissions still apply.",

2339 },

2334 {2340 {

2335 key: "browser_use.allow_history_access",2341 key: "browser_use.allow_history_access",

2336 type: "boolean",2342 type: "boolean",

Details

588to restrict it for that origin. This controls approval handling; it doesn't588to restrict it for that origin. This controls approval handling; it doesn't

589disable model safety monitoring.589disable model safety monitoring.

590 590 

591To enable [Site tools](https://learn.chatgpt.com/docs/webmcp) for an eligible Enterprise workspace,

592set `browser_use.allow_webmcp = true`. This managed opt-in applies to the

593desktop app's built-in browser. Setting it to `false` or omitting it leaves

594normal feature availability checks in place; `false` does not enforce a block.

595User preferences and site permissions still apply.

596 

591For native apps, set a default access policy and identify permitted apps. For597For native apps, set a default access policy and identify permitted apps. For

592example, this macOS policy allows Calculator and prevents saved approvals:598example, this macOS policy allows Calculator and prevents saved approvals:

593 599 

enterprise/sites.md +270 −174

Details

2 2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 4 

5Workspace admins can enable Sites, choose which plugins they can use, and manage5For current workspace setup, permissions, and availability, see

6published Sites. To build or use a Site, see [Sites](https://learn.chatgpt.com/docs/sites).6 [Managing ChatGPT Sites for your workspace](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace)

7 in the Help Center.

7 8 

9<span

10 id="what-costs-and-limits-should-we-plan-for"

11 data-localization-body-anchor

12/>

8 13 

9 14 

10 {"For help with workspace access, publishing, and sharing, see "}

11 [{"Managing ChatGPT Sites for your workspace"}](https://help.openai.com/articles/20001338)

12 {" in the Help Center."}

13

14 15 

16Use this guide to decide who can create, change, and use sites in your workspace, and how your organization reviews and maintains them. To build or use a site, see [Sites](https://learn.chatgpt.com/docs/sites).

15 17 

16## Before you begin18## Before you begin

17 19 

18Sign in as a workspace owner or admin and select the workspace you want to20Sign in to the workspace you administer. Decide who should create and publish sites, which audiences are appropriate, and who will handle review, support, and removal requests.

19manage. If tenant policy blocks access to connected apps, a tenant admin must

20enable that access. A tenant is the organization-level admin scope that can

21govern multiple workspaces.

22 21 

23Sites is in public beta. Available controls depend on your plan, admin role,22Start with a specific use case. For example, an onboarding site that displays approved reference material needs a different review from a request tracker that collects employee information or a dashboard that uses connected apps. Agree on:

24 and feature availability. Review the [Sites

25 limits](https://learn.chatgpt.com/docs/sites#understand-limits-and-unsupported-uses), including data

26 residency limitations, before choosing what data to use.

27 23 

28These controls apply at different levels:24- The intended audience and whether external or public access is appropriate.

25- The information the site displays, collects, stores, or sends to another service.

26- The people responsible for the site's content, application behavior, and ongoing support.

27- The review required before first publication and after material changes.

29 28 

30| Control | What it governs | Where to manage it |29## How sites work

31| --------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |

32| Sites access | Who can create and publish Sites | Workspace settings, including **Permissions & roles** where available |

33| Site sharing | Who can visit an individual Site | The Site's sharing controls or **Sites** in workspace administration |

34| Plugin permission for Sites | Which plugins can be used in Sites across the workspace | **Admin** > **Plugins** > the plugin > **Allow site visitors to use this plugin** |

35| Tenant connector access | Whether Sites can request access to connected apps | Tenant administration: **External access** > **ChatGPT Sites** > **Connectors** |

36| Network access | Which external destinations Sites can contact | Workspace administration: **Sites** > **Network access**, where available |

37 30 

38## Enable Sites and choose sharing permissions

39 31 

40<WorkflowSteps>

41 32 

421. Open your workspace settings and review the Sites permissions. Where

43 **Permissions & roles** is available, enable Sites for the roles that need

44 to create and publish them.

452. Review public publishing separately. In Enterprise workspaces, public

46 publishing is off by default and requires an admin to enable it. You can use

47 Sites internally without enabling public publishing.

483. Review external invitations separately from public publishing. Enterprise

49 admins manage **Allow members to invite external visitors to sites** under

50 **Workspace settings** > **Permissions & roles**. Business workspaces don't

51 have a separate external-invitation permission toggle.

524. Ask a member of the intended group to create a Site, keep its audience

53 limited, and verify that they can publish it and share it with the intended

54 visitors.

55 33 

56</WorkflowSteps>

57 34 

58A new Site is limited to its owner and workspace admins until its access35 

59changes. Visitor access and editor access are separate. See36 

60[Control access and secrets](https://learn.chatgpt.com/docs/sites#control-access-and-secrets) for37 

61sharing options and [Collaborate on a Site](https://learn.chatgpt.com/docs/sites#collaborate-on-a-site)38 

62for editor permissions.39 

63 40 

64<a id="enable-bring-your-own-plugin"></a>41 

65 42A site can contain pages, application code, and visitor information. Review what it publishes and collects before sharing it. For the building and publishing workflow, see [Creating and managing ChatGPT Sites](https://help.openai.com/en/articles/20001339).

66## Enable plugin use in Sites43 

67 44### What you can build

68Plugins in Sites let a Site use each visitor's own connected45 

69accounts and permissions. Sharing a Site doesn't share the builder's46Sites can support reference pages, forms, trackers, and interactive dashboards. The review should follow what the application does. A reference page needs a content and audience review. A form also needs a decision about which submissions to collect and who can read them. A connected dashboard needs a review of the accounts, permissions, and information used by its connections.

70connections. Visitors sign in with ChatGPT, review the requested access, and47 

71choose which connections to allow. Connected features require membership in48### Information stored or exposed by a site

72the workspace that owns the Site.49 

73 50Information from prompts, conversation context, and uploaded or referenced files can become part of a site. Check the resulting pages, sample records, downloads, and generated content for information that should not reach the intended audience.

74Each plugin has a separate **Allow site visitors to use this plugin** setting. When an admin51 

75hasn't saved a choice for that plugin, the default depends on the workspace52Distinguish information copied into a site from information retrieved through a connection. For example, an uploaded spreadsheet can become a shared snapshot. A visitor using their own connected account may have different access from the person who built the site. Test the resulting behavior; the creator's preview alone does not establish what another visitor can see.

76plan:53 

77 54For the builder workflow, see [Data and connections](https://learn.chatgpt.com/docs/sites/data-and-connections).

78- **Business and Education:** Available plugins are allowed in Sites by default.55 

79- **Enterprise:** Plugins are off for Sites by default. An admin must allow56For hosting and processing providers, see OpenAI's [sub-processor list](https://openai.com/policies/sub-processor-list/).

80 each plugin the workspace needs.57 

81 58## Enable sites and permissions

82A saved admin choice overrides the default. Allowing a plugin in Sites59 

83doesn't override ordinary plugin permissions or tenant restrictions.60 

84 61 

85### Allow individual plugins in Sites62<span

86 63 id="enable-sites-and-choose-sharing-permissions"

871. Open **Admin** > **Plugins** in the intended workspace.64 data-localization-body-anchor

882. Open the plugin you want to allow, such as Notion.65/>

893. In its **Sites** section, review **Allow site visitors to use this plugin**. If it's off and you66 

90 want to allow the plugin, turn it on and select **Save**. If it's already on,67 

91 no change is needed.68 

924. Review each plugin that the Site needs. Confirm that the plugin, its69 

93 connected app, and the required actions are also available to the intended70 

94 members through your [plugin controls](https://learn.chatgpt.com/docs/enterprise/apps-and-connectors).71 

95 72 

96If **Allow site visitors to use this plugin** is disabled because tenant73 

97 connector access is off, follow the **External access** link on the plugin74 

98 page when available. A tenant admin can enable **Connectors** for **ChatGPT75 

99 Sites**. If you don't have tenant admin access, contact an admin of the tenant76 

100 named in the notice.77Review three levels of control: workspace permissions, the site's sharing and editing permissions, and permissions implemented by the application.

101 78 

102**Allow site visitors to use this plugin** is one setting per plugin for the whole workspace. It79 

103doesn't assign access by role, connect accounts for members, or replace80 

104visitor consent and the connected service's permissions. It also doesn't81![Three levels of control: workspace permissions govern creation and publishing, site permissions govern viewing and editing, and application code governs access to records and actions.](<https://developers.openai.com/images/codex/sites/controls.webp>)

105provide separate Sites-specific read and write switches. Review the approved82 

106actions and permissions before enabling a plugin.83 

107 84 

108<a id="enable-tenant-access-to-connected-apps"></a>85### Workspace admin controls

109 86 

110### Check tenant access if plugins are blocked87Workspace permissions govern who can create and publish sites. Review public publishing and external invitations separately: allowing a named external viewer and making a site available to anyone on the internet are different decisions.

111 88 

112Use these steps when the plugin page says tenant connector access is off.89For configuration steps and current role permissions, follow the [workspace administration guidance](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace).

113If access is already on, no tenant change is needed. Turning it on doesn't90 

114enable individual plugins that a workspace admin has disabled.91### Site owner and collaborator controls

115 92 

1161. Follow the plugin page's **External access** link, or open [Admin Console](https://admin.openai.com)93Use the site's sharing controls to decide who can view it and who can help maintain it.

117 and select the tenant that governs the workspace.94 

1182. Open **External access**, find **ChatGPT Sites**, and enable **Connectors**95#### Audience and sharing

119 if it's off.96 

1203. Return to the workspace's plugin settings and review **Allow site visitors to use this plugin**97A site's audience controls who can open it. Choose an audience that matches the information and activities the site provides. Review the full set of access grants when narrowing access. Removing an individual invitation may still leave access through a group, workspace-wide sharing, or public publishing.

121 for each plugin your team needs.98 

122 99Check the site from the intended visitor experience after changing access. For the sharing workflow, see [Publishing and sharing](https://learn.chatgpt.com/docs/sites/publishing-and-sharing).

123**External access** belongs to the tenant view. The workspace view has its own100 

124**Sites** settings. If you can't find **External access**, check the selected101#### Editors and collaboration

125admin scope and whether you have tenant admin access. Workspaces without a102 

126tenant don't have this tenant-level control. If the plugin page says connector103Owners can invite eligible workspace members as editors. Owners publish first; editors can publish later updates. Owners manage access and owner-only settings.

127access isn't available for the tenant yet, changing the plugin's setting104 

128won't enable it.105Agree on how editors request and record review before publishing changes. An editor can publish later versions without a separate owner-approval step, so an organization's review process must account for who has edit access.

106 

107### Application permissions

108 

109Site access and access to individual records are separate. For private application data, enforce record access in server-side code and test with ordinary visitor accounts.

110 

111For example, a request tracker might let every workspace member open the site while allowing each person to see only their own requests. The application needs to check that rule when returning a record or accepting an action. Hiding a page element does not enforce the rule. Test with separate accounts that should and should not have access to the same record.

112 

113## Configure connections

114 

115 

116 

117 

118 

119<span

120 id="enable-tenant-access-to-connected-apps"

121 data-localization-body-anchor

122/>

123 

124 

125 

126 

127<span

128 id="check-tenant-access-if-plugins-are-blocked"

129 data-localization-body-anchor

130/>

131 

132 

133 

134 

135<span

136 id="allow-an-additional-destination-for-one-site"

137 data-localization-body-anchor

138/>

139<span

140 id="credentials-and-connected-service-access"

141 data-localization-body-anchor

142/>

143 

144Creating or sharing a site does not itself authorize connected-app access. Review organization approval, plugin settings, and each visitor's connected account and permissions in the [Help Center connected-app guidance](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace).

145 

146Include connections in the site's review. Identify which services it uses, what information it requests, and who should authorize that access. Keep the site's audience decision separate from the connected service's permissions.

147 

148Keep credentials out of prompts, attachments, and visible site content. For questions about network access, contact your workspace admin.

129 149 

130### Verify the visitor experience150### Verify the visitor experience

131 151 

132Ask a builder to create a Site using an approved plugin and share it within152Keep connected sites private to their workspace. Before sharing a connected site more widely within that workspace, test with another eligible workspace member who uses their own connected account. Confirm that the site shows information appropriate to that person's existing permissions.

133the workspace. Have another member sign in, review access, and connect their153 

134own account. Confirm that the Site shows data that member is allowed to154Also check the experience when a visitor does not grant the requested connection access. Explain which features need the connection and make sure the site does not present missing data as a successful result. Follow the current Help Center guidance for supported connections and access requirements.

135access. Also test continuing without granting plugin access: features that155 

136require a connection won't work.156## Review and manage sites

157 

137 158 

138For the builder workflow, see

139[Bring user data to your Site with plugins](https://learn.chatgpt.com/docs/sites#bring-user-data-to-your-site-with-plugins).

140 159 

141## Configure network access

142 160 

143Where **Network access** is available, use it to control the external

144destinations a Site can contact. This setting is separate from plugin

145authorization: allowing a hostname doesn't grant access to a connected app.

146 161 

147### Set the workspace policy162<span

163 id="workspace-visibility-and-audit-records"

164 data-localization-body-anchor

165/>

166<span

167 id="publishing-approval-and-network-controls"

168 data-localization-body-anchor

169/>

148 170 

1491. Open **Sites** in workspace administration.

1502. Find **Network access** and open its editor.

1513. Choose **Allow all destinations** or **Allow only listed destinations**.

1524. For a restricted policy, enter one hostname per line under **Allowed

153 destinations**. Use an exact hostname such as `api.example.com`, or

154 `*.example.com` to allow its subdomains. The wildcard doesn't include the

155 bare `example.com` hostname; add that separately if needed.

1565. Select **Save**, reopen the editor, and confirm the saved destinations.

157 Test the Site's required network requests.

158 171 

159Don't enter URLs, paths, IP addresses, or ports. The editor supports up to

1601,024 destination rules. A `*` rule allows all HTTP destinations. Restricted

161policies block raw TCP connections, including when the list contains `*`.

162 172 

163### Allow an additional destination for one Site

164 173 

165To allow a destination for one Site without broadening workspace-wide access:

166 174 

1671. Find the Site in workspace administration and open **More actions** >175### Saved versions and publishing

168 **Network access**.

1692. Review the inherited policy.

1703. Enter the approved hostnames under **Additional allowed destinations** and

171 select **Save**.

1724. Reopen the dialog to confirm the saved list, then test the Site.

173 176 

174These exceptions add to the inherited policy; they don't replace it or177Editing and saving a version are separate from publishing it. Review the updated preview, then publish when it's ready for visitors. Sending editing instructions does not immediately change the published site.

175restrict destinations it already allows. With an unrestricted inherited

176policy, exceptions don't further change access. If the combined destination

177limit is exceeded, the saved exceptions aren't applied; shorten the list and

178save again.

179 178 

180## Manage workspace Sites

181 179 

182Open **Sites** in workspace administration to find Sites by URL and review

183their owners. Use the controls available to your role to:

184 180 

185- **Manage access:** Review or change the Site's audience.181![Recommended workflow: edit and save, review and test, explicitly publish the version, then check the live site as a visitor. Review is an organizational step, not a product approval gate.](<https://developers.openai.com/images/codex/sites/publishing.webp>)

186- **Copy source clone command:** Get the command to inspect a Site's source182 

187 code, where available.183 

188- **Transfer ownership:** Assign a new owner when responsibility changes.184 

189- **Suspend:** Stop access to a Site while you investigate an issue. Use185Use this workflow for the first publication and later updates:

190 **Reinstate** when you're ready to make it available again.186 

191- **Delete Site:** Permanently remove a Site when it's no longer needed.187<WorkflowSteps>

188 

1891. Describe the change and review the saved version.

1902. Check the content, audience, application permissions, and connections affected by the change.

1913. Complete your organization's review, then have the owner or an eligible editor publish the version. The owner must complete the first publication.

1924. Open the published site as an intended visitor and confirm that the expected content and behavior are available.

193 

194</WorkflowSteps>

192 195 

193To stop use of a particular plugin in Sites, turn off its **Allow site visitors196The review step is a recommended organizational process. It does not add a product approval gate.

194to use this plugin** setting and verify the affected Site's behavior. If another allowed

195plugin includes the same connector, turning off one plugin doesn't block that

196shared connector through the other plugin. Review all relevant plugins when

197removing connector access.

198 197 

199## Troubleshoot access198### Shared responsibilities

199 

200Successful generation or publication does not establish that a site meets your organization's requirements. Assign responsibility for reviewing the content, access rules, generated application code, and connected services. Use reviewers with the appropriate expertise for the site's data and use case.

201 

202### Review before publishing

203 

204Review the intended audience, generated code, visitor submissions, and connections before publishing. Test the experience for intended and unintended visitors. Repeat the review after material changes.

205 

206For example, a team dashboard can look correct for its creator while exposing an uploaded customer list to a broader audience than intended. A request form can collect the right fields while allowing one visitor to read another visitor's submission. Review both what appears on the page and how the application handles data and actions.

207 

208Before publishing, check:

209 

210- **Content:** Review generated text, images, links, uploaded files, and example data for accuracy and appropriate sharing.

211- **Audience:** Confirm who should be able to open the site, including external viewers and public visitors where applicable.

212- **Records and actions:** Verify that visitors can read and change only the information they have permission to access.

213- **Forms and submissions:** Decide what to collect, where it goes, who can see it, and how visitors can ask questions about it.

214- **Connections and credentials:** Review the services used, requested access, and any information exposed in pages, downloads, or error messages.

215- **Visitor experience:** Use separate visitor accounts to check the expected paths and denied-access cases.

216 

217### Ongoing maintenance

218 

219Give each production site an owner and a reporting route for access, data, or service issues. Plan ownership handoffs and retirement with your workspace admin.

220 

221Revisit the review when the site gains a new audience, editor, data source, connection, form, or application feature. Periodically check whether the audience and editor list still match the site's purpose, whether its content is current, and whether its connections still work for intended visitors.

222 

223### Troubleshoot access

224 

225Start with the affected person's account, role, and attempted action. A creator's successful preview does not confirm that another person can view, edit, publish, or use a connection.

200 226 

201| Symptom | What to check |227| Symptom | What to check |

202| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |228| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

203| A member can't create or publish a Site | Confirm the selected workspace, Sites availability, and the member's workspace permissions. |229| A member cannot create or publish a site | Confirm the active workspace, feature availability, and permissions for that member's role. |

204| A plugin works in ChatGPT but is unavailable to a Site | Check **Allow site visitors to use this plugin**, including any saved admin choice, ordinary plugin permissions, and the member's connected account. If tenant policy blocks access, check **External access**. |230| A visitor cannot open a site | Check the site's audience and whether the visitor is signed in with the account that received access. |

205| The plugin's Sites toggle is disabled | Review the notice beside the toggle. If tenant connector access is off, follow **External access** or contact an admin of the tenant named in the notice. |231| An editor cannot publish | Check edit access, workspace permissions, and whether the owner has completed the first publication. |

206| A visitor can open the Site but can't use connected features | Confirm membership in the Site's workspace, the selected account, visitor consent, and permissions in the connected service. An external invitation doesn't grant workspace membership. |232| A connection works for the creator but not another visitor | Confirm that the site is private to its workspace and the visitor belongs to that workspace. Check the visitor's connected account, service permissions, authorization, and the applicable organization and plugin settings. |

207| The Site can't reach an external service | Check the hostname against the inherited network policy and any Site-specific exceptions. |233| A person can still view a site after an invitation is removed | Review remaining group, workspace-wide, or public access. |

208| A network policy save conflicts or can't be confirmed | Select **Refresh and review**, inspect the latest saved policy, then make the intended change. Refresh replaces the unsaved draft. |234| A published site does not show an expected edit | Confirm that the intended version was published, then open the live site to check the result. |

209| An admin setting is missing or read-only | Check the selected tenant or workspace, your admin role, and feature availability. The tenant and workspace views expose different controls. |235 

210 236To review available workspace controls or troubleshoot access, follow the [Help Center guidance](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace).

211When asking an admin for help, include the Site URL, workspace, plugin or237 

212destination needed, and the exact error. Contact the admin directly to request238## Security and privacy

213access.239 

240 

241 

242 

243 

244 

245 

246<span

247 id="certifications-encryption-and-customer-managed-keys"

248 data-localization-body-anchor

249/>

250 

251 

252<span

253 id="applicable-terms-and-data-processing-agreements"

254 data-localization-body-anchor

255/>

256 

257 

258 

259 

260 

261Check current [hosting, residency, and data requirements](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace) against your organization's policies before choosing what information to store in a site.

262 

263### Visitor privacy and personal information

264 

265Before adding a form, sign-in feature, or other way to collect visitor information, decide why the site needs that information and how the site will use it. Review who can access submissions and whether the site will send information to another service or display it to other visitors.

266 

267Plan the appropriate privacy notice and a contact route for visitor questions. Use the resources below with your organization's privacy reviewers to determine what the notice and handling process should cover.

268 

269For responsibilities and privacy guidance, review these resources:

270 

271- Review [ChatGPT Sites Terms](https://openai.com/policies/chatgpt-sites-terms/).

272- Review [Operator responsibilities](https://help.openai.com/en/articles/20001337-understanding-responsibilities-for-your-chatgpt-sites).

273- Review [Data-protection responsibilities](https://help.openai.com/en/articles/20001340).

274- Review [Privacy-policy guidance](https://help.openai.com/en/articles/20001341).

275 

276 

277 

278<span

279 id="what-happens-when-a-site-is-deleted-or-an-owner-leaves"

280 data-localization-body-anchor

281/>

282 

283<ToggleSection title="How should I plan an ownership handoff or site retirement?">

284 

285Before an owner changes roles or leaves, identify who will maintain the site and use the current administration guidance for available handoff options. For retirement, plan how to restrict access and handle information collected by the site. Confirm deletion and retention requirements before promising a recovery or removal timeline.

286 

287</ToggleSection>

288 

289<span

290 id="how-long-is-site-data-retained-including-backups"

291 data-localization-body-anchor

292/>

293 

294<ToggleSection title="Where can I confirm retention and deletion requirements?">

295 

296For retention or deletion questions, [contact OpenAI Support](https://help.openai.com/en/articles/6614161-how-can-i-contact-support).

297 

298</ToggleSection>

299 

300<span

301 id="how-do-i-report-an-access-issue-or-incident"

302 data-localization-body-anchor

303/>

304 

305<ToggleSection title="How do I report an access issue?">

306 

307For access questions, [contact OpenAI Support](https://help.openai.com/en/articles/6614161-how-can-i-contact-support). Include the site URL, workspace, affected role, attempted action, time, and error details. Omit secrets and access tokens.

308 

309</ToggleSection>

extend/mcp.md +171 −5

Details

2 2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 4 

5Model Context Protocol (MCP) connects models to tools and context. Use it to5Model Context Protocol (MCP) lets ChatGPT and Codex use tools and information from other systems. An MCP server describes the available tools and handles their requests—for example, looking up a ticket or retrieving documentation.

6give ChatGPT or Codex access to third-party documentation, or to let it

7interact with developer tools like your browser or Figma.

8 6 

9ChatGPT web can use remote MCP-backed tools supplied by plugins. Local Codex7Choose the path that fits your task:

10clients can also connect directly to MCP servers and share their configuration.8 

9- **Build a connection:** Use [Plugin Creator and Sites](#build-an-mcp-plugin) to create a plugin for your team's system.

10- **Use an existing server:** [Connect it to ChatGPT or Codex](#connect-an-existing-mcp-server), with setup options for web, desktop, CLI, and IDE.

11 

12 

13 

14![A question about ticket 123 passes from ChatGPT through the MCP server to the ticketing system. The result returns through the server, and ChatGPT reports that the ticket is open.](<https://developers.openai.com/images/codex/custom-mcp/how-a-request-works-poster.webp>)

15 

16 

17 

18_Example: checking a ticket in your team's support system._

19 

20A plugin packages an MCP connection for use in ChatGPT. Plugins can also contain skills with reusable instructions. If you only need instructions, see [Build plugins](https://learn.chatgpt.com/docs/build-plugins).

21 

22 

23 

24 

25## Build an MCP plugin with Plugin Creator and Sites

26 

27Use Plugin Creator to create and edit your plugin, and [Sites](https://help.openai.com/en/articles/20001547-hosting-a-plugin-with-chatgpt-sites) to host its MCP server.

28 

29 

30 

31 

32 

33 

34### Before you start

35 

36Check the [Plugins directory](https://chatgpt.com/plugins) first. If you need a new connection, gather your system's API documentation and an approved way to connect. Ask the system owner if you need help.

37 

38The example below creates a **Support Ticket Lookup** plugin. Replace **[ticketing system]** with your system's name.

39 

40<ToggleSection title="Using ChatGPT Enterprise?" open>

41 

42Ask your workspace owner or admin to check **Workspace settings → Permissions & roles**:

43 

44- **Use plugins** to create, edit, and use plugins. Editing also requires edit access to the plugin.

45- **Sites** access to create and publish your Site.

46- **Upload plugins** and **Create plugins with MCPs** for the Site owner who publishes the MCP-backed plugin.

47 

48**Use plugins** is on by default; the two MCP publishing permissions are off by default. See [Sites access](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace) and [plugin permissions](https://help.openai.com/en/articles/20001547-hosting-a-plugin-with-chatgpt-sites).

49 

50</ToggleSection>

51 

52 

53 

54 

55### 1. Create your plugin

56 

57Start a new Work task, select **@Plugin Creator**, and attach your API documentation. Describe the task:

58 

59 

60 

61**Create your plugin:**

62 

63```text

64Create a Support Ticket Lookup plugin with an MCP server hosted on Sites, using the attached [ticketing system] API docs. Let me look up a ticket's status, owner, and latest update by ticket number. Make it read-only and tell me what's needed to connect it.

65```

66 

67Still arranging API access? Ask Plugin Creator to use sample data for now.

68 

69 

70 

71 

72### 2. Connect your system

73 

74In the same conversation, ask Plugin Creator to connect the server to your system's API and walk you through sign-in. Use your organization's approved authentication method, and keep passwords, API keys, and other secrets out of chat.

75 

76Need help? Send the Plugin Creator conversation and API documentation to your system owner or developer so they can help set up access.

77 

78<ToggleSection title="Connection and hosting details">

79 

80Sites handles sign-in between ChatGPT and your MCP server. Your server still needs an approved connection to your system's API, which controls the records and actions it can access.

81 

82For another host, follow [Build an MCP server](https://developers.openai.com/plugins/build/mcp-server), then [Connect and test your plugin](https://developers.openai.com/plugins/deploy/connect-chatgpt). See the [authentication guide](https://developers.openai.com/plugins/build/auth) for sign-in setup.

83 

84</ToggleSection>

85 

86 

87 

88 

89 

90 

91### 3. Publish and install

92 

93Ask Plugin Creator to publish the server with Sites and give you the plugin link, keeping your current access settings. The [Site owner must publish](https://learn.chatgpt.com/docs/sites/publishing-and-sharing#collaborate-on-a-site) to create its plugin; editors should ask the owner to do this.

94 

951. Open the plugin card, or find it under **Plugins → Personal → Created by you**.

962. Follow **Install** and **Connect**, and complete any required sign-in.

97 

98 

99 

100 

101### 4. Use your plugin

102 

103Start a new Work task, choose your plugin with **@**, and ask **“What's the status of ticket 123?”** using a ticket number from your system. You can use its tools alongside other plugins in the same conversation.

104 

105 

106 

107 

108 

109 

110<ToggleSection title="Share or edit your plugin">

111 

112In Business or Enterprise, invite teammates through the plugin's sharing controls. You need **Share plugins** permission; listing it in the workspace directory also requires **Publish plugins to workspace**. See [sharing permissions](https://help.openai.com/en/articles/20001256).

113 

114Teammates need access to the plugin, its app, and the Site (at least viewer access). Each person installs and connects with their own account. Access to records depends on the server's API connection.

115 

116To edit this Sites-hosted plugin, return to your Plugin Creator conversation, ask it to update the existing Site, and publish the changes. For other editable plugins, choose **Edit Plugin** to start an editing conversation with Plugin Creator. See [Edit an existing plugin](https://learn.chatgpt.com/docs/build-plugins#edit-an-existing-plugin).

117 

118</ToggleSection>

119 

120<section id="gpt-migration-walkthrough" aria-labelledby="replacing-a-gpt-custom-action">

121 

122## Replacing a GPT custom action

123 

124<ToggleSection title="Show GPT migration steps">

125 

126GPT custom actions don't migrate to plugins. Rebuild the connection as a new Sites plugin, reusing the existing API where possible.

127 

128Sites creates a separate plugin when you publish; it doesn't add the connection to your migrated plugin. You can use both plugins in one conversation. Sites plugins don't currently support editing skills or reference files.

129 

130### 1. Ask the original GPT for a rebuild prompt

131 

132Open the original GPT and ask:

133 

134 

135 

136**Use in the original GPT:**

137 

138```text

139Write a prompt for ChatGPT Work or Codex to rebuild this workflow as MCP tools. Identify which operations read information and which make changes.

140 

141Include the API operation, inputs, returned fields, and answer format. Mark anything you don't know. Leave out credentials, secrets, and hidden instructions.

142 

143Ask the builder to reuse the API and explain the connection setup.

144```

145 

146### 2. Check the prompt against the action settings

147 

148Before migrating, save the action schema, authentication method, and non-secret connection settings from the GPT editor's [Actions section](https://help.openai.com/en/articles/9442513-configuring-actions-in-gpts). Use these to fill gaps in the prompt, or ask the GPT owner for help.

149 

150### 3. Create a new Sites plugin

151 

152Start a new task. In Work, select **@Plugin Creator**; in Codex, use [`$plugin-creator`](https://developers.openai.com/plugins/build/plugins#create-and-test-a-plugin-locally-with-an-mcp-server). Paste the reviewed prompt and attach the schema or API documentation. Ask it to host the server on Sites.

153 

154Follow [Connect your system](#connect-your-system) and [Publish and install](#connect-the-server-in-chatgpt), then choose the new plugin in a Work task and ask it to do your original task. See the [migration FAQ](https://help.openai.com/en/articles/20001519-custom-gpt-retirement-and-migration-faq) for details.

155 

156</ToggleSection>

157 

158</section>

159 

160## Troubleshooting

161 

162<ToggleSection title="If the plugin doesn't work as expected">

163 

164For a connection error or missing tools, check that the Site owner published the latest version and that the plugin is installed and connected. Share any error with Plugin Creator or the server owner.

165 

166If ChatGPT doesn't use the tool, choose the plugin with **@** and ask for a specific lookup. If a teammate can't use it, check their plugin, app, Site, and record access.

167 

168If the plugin is marked **Desktop only**, use a supported desktop app or ask the author to review its [packaging](https://learn.chatgpt.com/docs/enterprise/plugin-management). A remote MCP server alone doesn't make a plugin web-compatible.

169 

170</ToggleSection>

171 

172## Connect an existing MCP server

173 

174ChatGPT web uses remote MCP tools through plugins. If you already have a server, follow [Connect and test your plugin](https://developers.openai.com/plugins/deploy/connect-chatgpt). Local Codex clients can also connect directly to MCP servers and share their configuration.

175 

176Choose your surface to see its setup and configuration options.

11 177 

12<a id="supported-mcp-features"></a>178<a id="supported-mcp-features"></a>

13 179 

features.md +46 −7

Details

33 href: "/codex/mobile",33 href: "/codex/mobile",

34 icon: "connect",34 icon: "connect",

35 },35 },

36 {

37 title: "Sites",

38 description:

39 "Create, save, and publish interactive websites and apps in ChatGPT.",

40 href: "/codex/sites",

41 icon: "workspace",

42 },

43 {36 {

44 title: "Build plugins",37 title: "Build plugins",

45 description:38 description:


114 href: "/codex/features/voice",107 href: "/codex/features/voice",

115 icon: "chat",108 icon: "chat",

116 },109 },

110 {

111 title: "Composer Predictions",

112 description: "Get suggestions for your next message in Codex.",

113 href: "/codex/features/composer-predictions",

114 icon: "chat",

115 },

117 {116 {

118 title: "Plugins",117 title: "Plugins",

119 description:118 description:


169 },168 },

170 ],169 ],

171 },170 },

171 {

172 title: "Sites",

173 description: "Build, connect, publish, and manage websites in ChatGPT.",

174 pages: [

175 {

176 title: "Overview",

177 description:

178 "Explore what you can build and find availability and limits.",

179 href: "/codex/sites",

180 icon: "workspace",

181 },

182 {

183 title: "Create a site",

184 description: "Describe, build, and refine a website in ChatGPT.",

185 href: "/codex/sites/creating-a-site",

186 icon: "workspace",

187 },

188 {

189 title: "Data and connections",

190 description:

191 "Save records, connect apps, and add sign-in to your site.",

192 href: "/codex/sites/data-and-connections",

193 icon: "workspace",

194 },

195 {

196 title: "Publish and share",

197 description:

198 "Review your site, choose its audience, and invite editors.",

199 href: "/codex/sites/publishing-and-sharing",

200 icon: "workspace",

201 },

202 {

203 title: "Manage a site",

204 description:

205 "Review analytics, change settings, and manage site versions.",

206 href: "/codex/sites/managing-a-site",

207 icon: "workspace",

208 },

209 ],

210 },

172 {211 {

173 title: "ChatGPT dots",212 title: "ChatGPT dots",

174 description: "Set up your dot and manage its ongoing work.",213 description: "Set up your dot and manage its ongoing work.",

Details

1# Composer Predictions

2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 

5Composer predictions provides autocomplete-style suggestions for your next message in the Codex desktop app. After Codex responds, it uses the current conversation to suggest a follow-up prompt in the message box. Press **Tab** to accept the suggested message as an editable draft, or type your own message.

6 

7 

8 

9> Illustration: Codex responds, a prediction appears in the message box, and pressing Tab accepts it as a draft without sending it.

10 

11 

12 

13To use composer predictions, make sure you've updated the [Codex desktop app](https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan) and have signed in to your personal ChatGPT Pro account.

14 

15## Tab to accept

16 

171. Send a message in a supported thread. When Codex finishes responding, look for a suggestion in the message box.

182. **Press Tab to accept the prediction.** The suggested text becomes an editable

19 draft in the message box.

203. Review the text, make any changes, then send it when you’re ready.

21 

22To write something else, start typing your own message. You can keep typing without waiting for a prediction. Accepting a prediction adds text to the message box. It does not send the message for you.

23 

24## How predictions use context

25 

26Predictions use the conversation in your current thread. If context from past work or connected apps is already in that thread, it can inform a prediction. Predictions don’t look up memories or retrieve information from connected apps on their own. If you have memory enabled, memory context already in your thread can personalize predictions. You can use predictions with memory off.

27 

28## Availability and controls

29 

30Composer predictions is currently in beta for users aged 18 and older on personal ChatGPT Pro plans in supported regions. It’s available in local and SSH threads in the Codex desktop app with GPT-6 Astra and GPT-6.1 Sol. Predictions are enabled by default for eligible users. You can turn them on or off using the **Show predictions** toggle in **Settings → General → Composer**.

31 

32Generating predictions doesn’t count toward your Codex usage limits or consume credits. When you send a message, including an accepted prediction, normal Codex usage limits and billing apply.

33 

34For setup and troubleshooting, see [Composer predictions in Codex](https://help.openai.com/en/articles/20001601-composer-predictions-in-codex).

mcp.md +171 −5

Details

2 2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 4 

5Model Context Protocol (MCP) connects models to tools and context. Use it to5Model Context Protocol (MCP) lets ChatGPT and Codex use tools and information from other systems. An MCP server describes the available tools and handles their requests—for example, looking up a ticket or retrieving documentation.

6give ChatGPT or Codex access to third-party documentation, or to let it

7interact with developer tools like your browser or Figma.

8 6 

9ChatGPT web can use remote MCP-backed tools supplied by plugins. Local Codex7Choose the path that fits your task:

10clients can also connect directly to MCP servers and share their configuration.8 

9- **Build a connection:** Use [Plugin Creator and Sites](#build-an-mcp-plugin) to create a plugin for your team's system.

10- **Use an existing server:** [Connect it to ChatGPT or Codex](#connect-an-existing-mcp-server), with setup options for web, desktop, CLI, and IDE.

11 

12 

13 

14![A question about ticket 123 passes from ChatGPT through the MCP server to the ticketing system. The result returns through the server, and ChatGPT reports that the ticket is open.](<https://developers.openai.com/images/codex/custom-mcp/how-a-request-works-poster.webp>)

15 

16 

17 

18_Example: checking a ticket in your team's support system._

19 

20A plugin packages an MCP connection for use in ChatGPT. Plugins can also contain skills with reusable instructions. If you only need instructions, see [Build plugins](https://learn.chatgpt.com/docs/build-plugins).

21 

22 

23 

24 

25## Build an MCP plugin with Plugin Creator and Sites

26 

27Use Plugin Creator to create and edit your plugin, and [Sites](https://help.openai.com/en/articles/20001547-hosting-a-plugin-with-chatgpt-sites) to host its MCP server.

28 

29 

30 

31 

32 

33 

34### Before you start

35 

36Check the [Plugins directory](https://chatgpt.com/plugins) first. If you need a new connection, gather your system's API documentation and an approved way to connect. Ask the system owner if you need help.

37 

38The example below creates a **Support Ticket Lookup** plugin. Replace **[ticketing system]** with your system's name.

39 

40<ToggleSection title="Using ChatGPT Enterprise?" open>

41 

42Ask your workspace owner or admin to check **Workspace settings → Permissions & roles**:

43 

44- **Use plugins** to create, edit, and use plugins. Editing also requires edit access to the plugin.

45- **Sites** access to create and publish your Site.

46- **Upload plugins** and **Create plugins with MCPs** for the Site owner who publishes the MCP-backed plugin.

47 

48**Use plugins** is on by default; the two MCP publishing permissions are off by default. See [Sites access](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace) and [plugin permissions](https://help.openai.com/en/articles/20001547-hosting-a-plugin-with-chatgpt-sites).

49 

50</ToggleSection>

51 

52 

53 

54 

55### 1. Create your plugin

56 

57Start a new Work task, select **@Plugin Creator**, and attach your API documentation. Describe the task:

58 

59 

60 

61**Create your plugin:**

62 

63```text

64Create a Support Ticket Lookup plugin with an MCP server hosted on Sites, using the attached [ticketing system] API docs. Let me look up a ticket's status, owner, and latest update by ticket number. Make it read-only and tell me what's needed to connect it.

65```

66 

67Still arranging API access? Ask Plugin Creator to use sample data for now.

68 

69 

70 

71 

72### 2. Connect your system

73 

74In the same conversation, ask Plugin Creator to connect the server to your system's API and walk you through sign-in. Use your organization's approved authentication method, and keep passwords, API keys, and other secrets out of chat.

75 

76Need help? Send the Plugin Creator conversation and API documentation to your system owner or developer so they can help set up access.

77 

78<ToggleSection title="Connection and hosting details">

79 

80Sites handles sign-in between ChatGPT and your MCP server. Your server still needs an approved connection to your system's API, which controls the records and actions it can access.

81 

82For another host, follow [Build an MCP server](https://developers.openai.com/plugins/build/mcp-server), then [Connect and test your plugin](https://developers.openai.com/plugins/deploy/connect-chatgpt). See the [authentication guide](https://developers.openai.com/plugins/build/auth) for sign-in setup.

83 

84</ToggleSection>

85 

86 

87 

88 

89 

90 

91### 3. Publish and install

92 

93Ask Plugin Creator to publish the server with Sites and give you the plugin link, keeping your current access settings. The [Site owner must publish](https://learn.chatgpt.com/docs/sites/publishing-and-sharing#collaborate-on-a-site) to create its plugin; editors should ask the owner to do this.

94 

951. Open the plugin card, or find it under **Plugins → Personal → Created by you**.

962. Follow **Install** and **Connect**, and complete any required sign-in.

97 

98 

99 

100 

101### 4. Use your plugin

102 

103Start a new Work task, choose your plugin with **@**, and ask **“What's the status of ticket 123?”** using a ticket number from your system. You can use its tools alongside other plugins in the same conversation.

104 

105 

106 

107 

108 

109 

110<ToggleSection title="Share or edit your plugin">

111 

112In Business or Enterprise, invite teammates through the plugin's sharing controls. You need **Share plugins** permission; listing it in the workspace directory also requires **Publish plugins to workspace**. See [sharing permissions](https://help.openai.com/en/articles/20001256).

113 

114Teammates need access to the plugin, its app, and the Site (at least viewer access). Each person installs and connects with their own account. Access to records depends on the server's API connection.

115 

116To edit this Sites-hosted plugin, return to your Plugin Creator conversation, ask it to update the existing Site, and publish the changes. For other editable plugins, choose **Edit Plugin** to start an editing conversation with Plugin Creator. See [Edit an existing plugin](https://learn.chatgpt.com/docs/build-plugins#edit-an-existing-plugin).

117 

118</ToggleSection>

119 

120<section id="gpt-migration-walkthrough" aria-labelledby="replacing-a-gpt-custom-action">

121 

122## Replacing a GPT custom action

123 

124<ToggleSection title="Show GPT migration steps">

125 

126GPT custom actions don't migrate to plugins. Rebuild the connection as a new Sites plugin, reusing the existing API where possible.

127 

128Sites creates a separate plugin when you publish; it doesn't add the connection to your migrated plugin. You can use both plugins in one conversation. Sites plugins don't currently support editing skills or reference files.

129 

130### 1. Ask the original GPT for a rebuild prompt

131 

132Open the original GPT and ask:

133 

134 

135 

136**Use in the original GPT:**

137 

138```text

139Write a prompt for ChatGPT Work or Codex to rebuild this workflow as MCP tools. Identify which operations read information and which make changes.

140 

141Include the API operation, inputs, returned fields, and answer format. Mark anything you don't know. Leave out credentials, secrets, and hidden instructions.

142 

143Ask the builder to reuse the API and explain the connection setup.

144```

145 

146### 2. Check the prompt against the action settings

147 

148Before migrating, save the action schema, authentication method, and non-secret connection settings from the GPT editor's [Actions section](https://help.openai.com/en/articles/9442513-configuring-actions-in-gpts). Use these to fill gaps in the prompt, or ask the GPT owner for help.

149 

150### 3. Create a new Sites plugin

151 

152Start a new task. In Work, select **@Plugin Creator**; in Codex, use [`$plugin-creator`](https://developers.openai.com/plugins/build/plugins#create-and-test-a-plugin-locally-with-an-mcp-server). Paste the reviewed prompt and attach the schema or API documentation. Ask it to host the server on Sites.

153 

154Follow [Connect your system](#connect-your-system) and [Publish and install](#connect-the-server-in-chatgpt), then choose the new plugin in a Work task and ask it to do your original task. See the [migration FAQ](https://help.openai.com/en/articles/20001519-custom-gpt-retirement-and-migration-faq) for details.

155 

156</ToggleSection>

157 

158</section>

159 

160## Troubleshooting

161 

162<ToggleSection title="If the plugin doesn't work as expected">

163 

164For a connection error or missing tools, check that the Site owner published the latest version and that the plugin is installed and connected. Share any error with Plugin Creator or the server owner.

165 

166If ChatGPT doesn't use the tool, choose the plugin with **@** and ask for a specific lookup. If a teammate can't use it, check their plugin, app, Site, and record access.

167 

168If the plugin is marked **Desktop only**, use a supported desktop app or ask the author to review its [packaging](https://learn.chatgpt.com/docs/enterprise/plugin-management). A remote MCP server alone doesn't make a plugin web-compatible.

169 

170</ToggleSection>

171 

172## Connect an existing MCP server

173 

174ChatGPT web uses remote MCP tools through plugins. If you already have a server, follow [Connect and test your plugin](https://developers.openai.com/plugins/deploy/connect-chatgpt). Local Codex clients can also connect directly to MCP servers and share their configuration.

175 

176Choose your surface to see its setup and configuration options.

11 177 

12<a id="supported-mcp-features"></a>178<a id="supported-mcp-features"></a>

13 179 

Details

343 343 

344| If your custom GPT… | What to review |344| If your custom GPT… | What to review |

345| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |345| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

346| Uses custom actions | Custom actions don’t transfer. Rebuild needed integrations with a supported connector or custom MCP connection, then test them before your team switches. |346| Uses custom actions | Custom actions don’t transfer. Rebuild needed integrations with a supported connector or [custom MCP connection](https://learn.chatgpt.com/docs/extend/mcp#replacing-a-gpt-custom-action), then test them before your team switches. |

347| Depends on a model, detailed instructions, or a strict format | The selected model doesn’t carry over; Enterprise defaults apply. Compare familiar prompts and results. Check that the right skill is selected and follows your instructions and format, since behavior can differ. |347| Depends on a model, detailed instructions, or a strict format | The selected model doesn’t carry over; Enterprise defaults apply. Compare familiar prompts and results. Check that the right skill is selected and follows your instructions and format, since behavior can differ. |

348| Relies on files, tools, or previous chat context | Check that files, templates, and examples are present and used correctly; transfer and retrieval may differ. Test required tools, such as web search, images, data analysis, or file creation. GPT capability settings don’t guarantee the same setup. Conversation starters and previous chats may not copy, so save useful prompts and identify needed context. |348| Relies on files, tools, or previous chat context | Check that files, templates, and examples are present and used correctly; transfer and retrieval may differ. Test required tools, such as web search, images, data analysis, or file creation. GPT capability settings don’t guarantee the same setup. Conversation starters and previous chats may not copy, so save useful prompts and identify needed context. |

349| Is shared with teammates or people outside your workspace | Record the intended audience and verify they can install and use the replacement. Show users how to install or select it. Check the replacement’s sharing settings. Check access for external users: public GPTs in affected Enterprise workspaces are included, but public sharing and timelines for other plans may differ. |349| Is shared with teammates or people outside your workspace | Record the intended audience and verify they can install and use the replacement. Show users how to install or select it. Check the replacement’s sharing settings. Check access for external users: public GPTs in affected Enterprise workspaces are included, but public sharing and timelines for other plans may differ. |


568 568 

569 <ToggleSection title="What should I do about GPTs that use custom actions?">569 <ToggleSection title="What should I do about GPTs that use custom actions?">

570 570 

571Custom actions do not transfer through the migration workflow. The person maintaining that workflow will need to rebuild the integration using a supported connector or a custom MCP server.571Custom actions do not transfer through the migration workflow. The person maintaining that workflow will need to rebuild the integration using a supported connector or a custom MCP server. See [Replacing a GPT custom action](https://learn.chatgpt.com/docs/extend/mcp#replacing-a-gpt-custom-action) for the steps and a prompt you can adapt.

572 572 

573Identify these GPTs early so the appropriate technical and security teams have time to review the replacement and its permissions.573Identify these GPTs early so the appropriate technical and security teams have time to review the replacement and its permissions.

574 574 

plugins.md +5 −1

Details

9directory, so the same public plugins are discoverable from their supported9directory, so the same public plugins are discoverable from their supported

10surfaces.10surfaces.

11 11 

12 

13 

14[Watch: Plugins in ChatGPT](https://www.youtube.com/watch?v=pKwRNdDtai0)

15 

12Plugins work in Chat and Work across ChatGPT on the web, desktop, and mobile,16Plugins work in Chat and Work across ChatGPT on the web, desktop, and mobile,

13and in Codex in the ChatGPT desktop app. Codex CLI also has a plugin browser17and in Codex in the ChatGPT desktop app. Codex CLI also has a plugin browser

14for Codex environments. The IDE extension doesn't support plugins.18for Codex environments. The IDE extension doesn't support plugins.


188 192 

189Use plugins in Sites to build a Site that uses each193Use plugins in Sites to build a Site that uses each

190visitor's own connected accounts and permissions. See [Bring user data to your Site194visitor's own connected accounts and permissions. See [Bring user data to your Site

191with plugins](https://learn.chatgpt.com/docs/sites#bring-user-data-to-your-site-with-plugins) for setup and visitor195with plugins](https://learn.chatgpt.com/docs/sites/data-and-connections#bring-user-data-to-your-site-with-plugins) for setup and visitor

192access requirements.196access requirements.

193 197 

194<ContentModeSwitch group="codex-surface" id="app">198<ContentModeSwitch group="codex-surface" id="app">

Details

149 149 

150Including an app does not grant new access. Your workspace’s app and action settings, connection permissions, and access in the source service still apply. Teammates who use the plugin also need the required app access and connection setup.150Including an app does not grant new access. Your workspace’s app and action settings, connection permissions, and access in the source service still apply. Teammates who use the plugin also need the required app access and connection setup.

151 151 

152### Connect a custom MCP server

153 

154If an existing app doesn’t cover your workflow, [connect your tools with a custom MCP server](https://learn.chatgpt.com/docs/extend/mcp#build-an-mcp-plugin). Use Plugin Creator and Sites to build a plugin that connects to your system.

155 

152## Test your plugin156## Test your plugin

153 157 

154Install the plugin if prompted, then start a new conversation. Select it with an **@ mention** and give it a familiar task. For Document Summary, provide a document you know well and ask for a summary.158Install the plugin if prompted, then start a new conversation. Select it with an **@ mention** and give it a familiar task. For Document Summary, provide a document you know well and ask for a summary.

Details

29 29 

30## Configure Codex Security Review30## Configure Codex Security Review

31 31 

32Repository settings are shared. Changing them affects the pull requests selected

33by the repository's **Review** policy, not just your own pull requests. Personal

34preferences apply to your pull requests when the repository follows personal

35preferences.

36 

321. Go to [Codex settings](https://chatgpt.com/settings/code-review) and choose a repository.371. Go to [Codex settings](https://chatgpt.com/settings/code-review) and choose a repository.

332. Under **Review security vulnerabilities**, turn on **Auto security review**.382. Under **Review security vulnerabilities**, turn on **Auto security review**.

343. Use **Review** to choose which pull requests get Codex Security Review:393. Use **Review** to choose which pull requests get Codex Security Review:


43 - **Whenever code review runs** requires Code Review and runs Codex Security48 - **Whenever code review runs** requires Code Review and runs Codex Security

44 Review alongside it.49 Review alongside it.

45 50 

51Connecting GitHub or enabling personal reviews for another contributor doesn't

52request an extra review of your pull requests. Repository and personal settings

53determine whether an automatic review runs; they don't create one review per

54connected user. Later pushes can trigger another review according to **Trigger**,

55and you can request additional reviews manually.

56 

46## Add threat-model context57## Add threat-model context

47 58 

48You can configure a threat model to give Codex context about your application's59You can configure a threat model to give Codex context about your application's

Details

30## 2. Connect GitHub30## 2. Connect GitHub

31 31 

32Confirm that [Codex cloud](https://learn.chatgpt.com/docs/cloud) is set up for your workspace. In the32Confirm that [Codex cloud](https://learn.chatgpt.com/docs/cloud) is set up for your workspace. In the

33plugin, select **Scan**. If prompted, select **Connect GitHub** and sign in with33plugin, select **Scan**. If prompted, select **Connect GitHub** and sign in

34your own GitHub account. Grant access to the repositories you want to scan.34with your own GitHub account. Each person who starts scans needs their own

35connection and access to the repository.

35 36 

36Codex Security Cloud doesn't support workspace connections.37Codex Security Cloud doesn't support workspace connections.

37 38 

38If a repository is missing, check its GitHub connection and permissions.39If a repository is missing, use **Configure access** in the repository picker to

40check the GitHub app's organization and repository access. If you can't grant

41access yourself, ask your GitHub administrator to add the repository.

39 42 

40## 3. Start a repository scan43## 3. Start a repository scan

41 44 


110many days of history to review, and pause or enable monitoring.113many days of history to review, and pause or enable monitoring.

111Select **Save** to apply changes.114Select **Save** to apply changes.

112 115 

116The scan owner, people granted edit access, and Codex Security administrators

117can change monitoring settings. Changes, including pausing monitoring, apply to

118that repository's shared configuration in the workspace, not just to the person

119making the change. If you can't edit it, ask the scan owner or a Codex Security

120administrator.

121 

113Review and edit the generated threat model under **Project context**, then122Review and edit the generated threat model under **Project context**, then

114select **Save**. See [Improving the threat model](https://learn.chatgpt.com/docs/security/threat-model)123select **Save**. See [Improving the threat model](https://learn.chatgpt.com/docs/security/threat-model)

115for guidance.124for guidance.

sites.md +117 −494

Details

2 2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 4 

5Sites is in public beta and is available with ChatGPT Plus, Pro, Business,5Sites lets you build, host, and share a website by describing it in ChatGPT or Codex. You can get started without writing any code. You can also host web apps, games, or a compatible project you've already built, without a separate deployment setup.

6 Enterprise and Edu plans. Plan-specific usage limits apply across all Sites

7 during the beta. ChatGPT shows the current limits and notifies you as you

8 approach one. Reaching a limit can prevent you from creating a Site, adding

9 storage, or keeping a high-usage Site public, but you can still edit and

10 manage existing Sites.

11 6 

12Sites lets ChatGPT create, host, refine, and share websites, web apps, and games.7If you're part of an organization, Sites can give your team a place to find answers, explore results from a spreadsheet, or walk through a project kickoff. Start with the documents and data you already have, and ask for changes as you go.

13Use Sites when you want to turn a prompt or compatible existing project into a

14hosted experience without setting up a separate deployment workflow.

15 8 

16<ContentModeSwitch group="codex-surface" id="app">9<figure class="not-prose my-6" id="sites-intro-video">

17 10

18Open **Sites** in the ChatGPT desktop app. You can start a site from a prompt or

19from a compatible local project, then return to the Sites view to manage it.

20 11 

21</ContentModeSwitch>12[Watch: Build and publish web apps directly in ChatGPT](https://www.youtube.com/watch?v=8zH_O74hZBw)

22 13 

23<ContentModeSwitch group="codex-surface" id="web">14</figure>

24 15 

25Use Sites in ChatGPT on the web to create and manage hosted sites. Select

26**More** > **Sites**, or go directly to

27[Sites in ChatGPT](https://chatgpt.com/sites), to find Sites you've created.

28 16 

29</ContentModeSwitch>

30 17 

31<ContentModeSwitch group="codex-surface" id="cli">

32 18 

33Sites doesn't have a standalone Codex CLI management view. Use ChatGPT web or

34the desktop app to create, save, deploy, and manage a Sites project. You can

35still use Codex CLI to edit and test a local project before publishing it.

36 19 

37</ContentModeSwitch>

38 20 

39<ContentModeSwitch group="codex-surface" id="ide">21 {

22 "For current availability, pricing, plan information, and usage limits, visit our "

23 }

24 [{"Help Center"}](https://help.openai.com/en/articles/20001339)

25 {"."}

40 26

41Sites doesn't have a standalone IDE extension management view. Use ChatGPT web

42or the desktop app for Sites operations, and use the IDE extension to edit and

43test the local source project.

44 27 

45</ContentModeSwitch>

46 28 

47Every Sites deployment URL is a production deployment. If you want to review a29<a id="explore-site-types"></a>

48 build before it becomes live, ask ChatGPT to save a version without deploying

49 it.

50 30 

51## Get started with Sites31## Explore ways to use sites

52 32 

53In ChatGPT, include the word "website" in your prompt or mention `@Sites` to33The examples below are just inspiration for what you can build with Sites. Explore a few common patterns, combine their features, or try an idea of your own.

54start the Sites workflow explicitly.

55 34 

56<WorkflowSteps variant="headings">

57 35 

581. Describe the Site

59 36 

60 Describe the audience, purpose, required behavior, and information the Site37### Resource hub

61 should use.

62 38 

632. Review the Site39A resource hub could group documents and links by topic, with search and short descriptions to help people find what they need. Each result might open the original document or page.

64 40 

65 Review the generated content and behavior. Check that the Site uses the41#### Example use cases

66 intended information and handles data as expected.

67 42 

683. Refine the Site43- New employee onboarding portal

44- Team policy and procedure library

45- Product launch resource center

46- Customer training and support hub

69 47 

70 Describe the changes you want. Add relevant files or visual context when48![Illustrative employee resource hub with search, topic groups, document cards, and a welcome guide.](<https://developers.openai.com/images/codex/sites/hub.webp>)

71 they will help ChatGPT make the change.

72 49 

734. Manage and share the Site50#### How you might build it

74 51 

75 Return to **Sites** to reopen or refine the Site. When it's ready, choose who521. **What to share** Provide documents or links with titles, short descriptions, and categories. Say who will use the hub and which resources to feature.

76 can visit it and share the resulting link.

77 53 

78</WorkflowSteps>542. **What to ask for** Ask for resource cards, search across titles and descriptions, and category filters. Specify what each card shows, whether it opens a source link or a page in the hub, and how the list should be updated. Define what visitors see when a search has no matches.

79 55 

80<ContentModeSwitch group="codex-surface" id="web">563. **What to check** Search for a known item, combine a search with a filter, and try a search with no matches. Check that an intended visitor can open the linked resources and browse on a phone.

81 57 

82In the preview, select **Edit**. Under **Describe website edits**, describe the58### Dashboard

83changes you want. Use **Screenshot** or **Add files and more** when additional

84context would help.

85 59 

86</ContentModeSwitch>60A dashboard could combine summary numbers, charts, and tables in one view. Filters might let people explore a date range, team, or category and compare the results.

87 61 

62#### Example use cases

88 63 

64- Marketing campaign performance

65- Sales pipeline and forecast

66- Customer support volume and response times

67- Product adoption and engagement

89 68 

90 {"For help creating and managing Sites, see "}69![Illustrative marketing dashboard with period filters, performance summaries, channel comparisons, and a campaign table.](<https://developers.openai.com/images/codex/sites/dashboard.webp>)

91 [{"Creating and managing ChatGPT Sites"}](https://help.openai.com/articles/20001339)

92 {" in the Help Center."}

93 70 

71#### How you might build it

94 72 

731. **What to share** Share a spreadsheet or sample of your data. Explain what each row and column means, and define the metrics and units.

95 74 

96## Prompt Sites for common tasks752. **What to ask for** Ask for summary numbers, charts, and a detail table with date or category filters. Specify which views should update together and how new data should be added or refreshed. Define what to show when a filter has no results. Keep missing values distinct from zero, and show a rate as unavailable if it would require dividing by zero.

97 76 

98For a new website, dashboard, or internal tool, include the audience, core773. **What to check** Compare totals and calculations with the source data before and after filtering. Try a filter with no results, a missing value, and a rate that would require dividing by zero. Confirm each follows the rules you specified.

99experience, and required information:

100 78 

79### Tracker

101 80 

81A tracker could keep tasks or items in a table, with owners, dates, and status labels. People might filter what they see and update the items they have permission to change.

102 82 

103**Prompt:**83#### Example use cases

104 84 

105```text85- Product launch tasks

106Build a project request dashboard for my operations team. Let team members submit requests, see who owns each one, update the status, and filter the list. Require people to sign in with their workspace account, and keep the request data saved between visits.86- Project milestones and dependencies

107```87- Editorial calendar and content reviews

88- Customer onboarding progress

108 89 

109<ContentModeSwitch group="codex-surface" ids="app,cli,ide">90![Illustrative launch tracker with a task table, owners, due dates, status, and blockers.](<https://developers.openai.com/images/codex/sites/tracker.webp>)

110 91 

111For an existing project, ask Sites to prepare and publish the current app:92#### How you might build it

112 93 

941. **What to share** Share sample items with fields such as name, owner, date, and status. Explain your status labels and any dependencies between items.

113 95 

962. **What to ask for** Ask for a table or board, filters by owner and status, and a way to add or edit items. Define when an item should be marked overdue or blocked. Say where updates should be saved and who should be able to view or change them.

114 97 

115**Prompt:**983. **What to check** Add or edit an item, then reload to confirm the change was saved. Try each status and an overdue date. If access differs by person, use separate accounts to check who can view and edit items.

116 99 

117```text100### Request form

118Deploy this project with Sites. Check whether it is compatible, make any required changes, and give me the deployment URL.

119```

120 101 

121</ContentModeSwitch>102A request form could collect details through fields, choices, and attachments. You might ask it to check required answers, save submissions for reviewers, and show a confirmation explaining what happens next.

122 103 

123When a site needs durable application data or uploaded files, say so in the104#### Example use cases

124request:

125 105 

106- Creative briefs and design requests

107- Event and workshop support requests

108- IT and software access requests

109- Customer feedback submissions

126 110 

111![Illustrative creative request form with project name, brief, deliverables, due date, attachments, and a submit action.](<https://developers.openai.com/images/codex/sites/intake.webp>)

127 112 

128**Prompt:**113#### How you might build it

129 114 

130```text1151. **What to share** Share a form template or list the details to collect. Mark required fields and list allowed choices and attachments.

131Add player scores and avatar uploads to this game. Keep the scores and uploaded avatars between visits.

132```

133 116 

134Browse the [Sites showcase](https://developers.openai.com/showcase) for deployed internal apps and the full1172. **What to ask for** Ask for checks on required fields and accepted formats, with messages explaining what to fix. Specify which answers trigger follow-up questions. Say where submissions and attachments should be saved, who can review them, and what the confirmation should tell the person who submitted the form.

135 prompts used to create them.

136 118 

137## Review Site analytics1193. **What to check** Submit a complete form and confirm the saved request is available to the intended reviewers. Leave a required field blank and try an invalid entry to check that the form explains what to fix. If you included uploads, confirm reviewers can open the saved attachment.

138 120 

139Sites records traffic automatically, so you can see how people use a deployed121### Calculator

140Site without adding an analytics SDK. The analytics view shows total unique

141visitors and page views, plus both metrics over time. Change the date range or

142granularity to inspect a different period.

143 122 

144<ContentModeSwitch group="codex-surface" id="app">123A calculator could turn editable inputs into an estimate using formulas you provide. People might change the numbers, compare scenarios, and see the units and assumptions beside each result.

145 124 

146Open **Sites**, find the Site, then select **More actions** > **Analytics**.125#### Example use cases

147 126 

148</ContentModeSwitch>127- Event budget scenarios

128- Team staffing and workload estimates

129- Workshop duration and group sizes

130- Project timeline estimates

149 131 

150<ContentModeSwitch group="codex-surface" id="web">132![Illustrative capacity planner with team size and workload inputs, capacity estimates, assumptions, and scenario comparisons.](<https://developers.openai.com/images/codex/sites/calculator.webp>)

151 133 

152Go to [Sites in ChatGPT](https://chatgpt.com/sites), find the Site, then select134#### How you might build it

153**More actions** > **Analytics**.

154 135 

155</ContentModeSwitch>1361. **What to share** Share the formula, units, assumptions, and a worked example with its expected result. Explain which inputs people can change and which values should stay fixed.

156 137 

157<ContentModeSwitch group="codex-surface" ids="cli,ide">1382. **What to ask for** Ask for labeled input fields or sliders, starting values, and allowed ranges. Show results with their units and assumptions. Define what happens for zero, blank, and out-of-range inputs, including when to show an error or an unavailable result. If people should compare scenarios, specify which results to show side by side.

158 139 

159Sites doesn't have a standalone analytics view in the CLI or IDE extension. Open1403. **What to check** Enter your worked example and compare the result. Change one input to check that the output updates correctly. Try zero, blank, and out-of-range values, and confirm each shows the response you specified.

160the Site in ChatGPT on the web or in the desktop app to review its analytics.

161 141 

162</ContentModeSwitch>142### Guided process

163 143 

144A guided process could break a task into steps, with questions that change based on earlier answers. People might go back, save progress if needed, and finish with a brief, checklist, or other output you define.

164 145 

146#### Example use cases

165 147 

166> Illustration: Interactive Sites analytics dashboard showing unique visitors and page views over seven days.148- Employee onboarding checklist

149- Project kickoff planning

150- Campaign brief development

151- Vendor review and approval steps

167 152 

153![Illustrative project kickoff guide with numbered steps, focused planning questions, a progress indicator, and a next-step action.](<https://developers.openai.com/images/codex/sites/process.webp>)

168 154 

155#### How you might build it

169 156 

170Analytics is currently available for Sites that aren't owned by an Enterprise1571. **What to share** Share a checklist or outline of the process. Put the steps in order, mark required answers, and explain where a choice should lead to different questions. Include an example of the finished output.

171 workspace.

172 158 

173## Add Sign in with ChatGPT1592. **What to ask for** Ask for one step at a time, a progress indicator, and a way to go back without losing answers. Define the final output, what happens when a required answer is blank, and whether answers that no longer apply should be removed or flagged. If progress should be saved, say where and who can return to it.

174 160 

175Public Sites can remain open to everyone while offering optional Sign in with1613. **What to check** Try the main path and each branch. Leave a required answer blank, then change an earlier choice. Check that questions and the output update, and answers that no longer apply are removed or flagged. If you requested saved progress, reload to confirm it resumes.

176ChatGPT for identity-aware features, such as saved progress, personalized views,

177or records that belong to a specific person. Workspace-restricted Sites already

178use ChatGPT identity to enforce their sharing settings.

179 162 

180Ask Sites to add the sign-in experience:163Illustrated examples with fictional content.

181 164 

182 165 

183 166 

184**Prompt:**167## Sites use cases

185 168 

186```text169Here are some example use cases you can try with Sites.

187Add Sign in with ChatGPT to this public Site. Keep the Site available to signed-out visitors. Show a Sign in with ChatGPT action when someone is signed out. After they sign in, greet them with their full name when available, or their email address otherwise. Add a Sign out action, and keep authorization decisions in server-side code.

188```

189 170 

190<ToggleSection title="How it works">171- [Manage creative requests from brief to delivery](https://learn.chatgpt.com/use-cases/collect-and-schedule-team-requests): Turn your creative brief template into a request app where colleagues submit work, designers...

172- [Build an onboarding portal](https://learn.chatgpt.com/use-cases/build-personal-onboarding-plan): Turn your team's first-week plan into daily activities, resource links, and a practice task...

173- [See which campaigns bring in leads](https://learn.chatgpt.com/use-cases/compare-campaign-performance): Turn weekly campaign exports into a dashboard where your team can compare lead volume and...

174- [Build a hackathon site](https://learn.chatgpt.com/use-cases/build-workshop-practice-site): Turn your event brief and agenda into a site that guides participants through registration...

191 175 

192Sites handles the sign-in and sign-out flows through platform-provided paths,176## Understand limits and unsupported uses

193then returns the visitor to your Site:

194 

195```html

196<a href="/signin-with-chatgpt">Sign in with ChatGPT</a>

197<a href="/signout-with-chatgpt">Sign out</a>

198```

199 

200After a visitor signs in, Sites forwards their identity to the server through

201these request headers:

202 

203- `oai-authenticated-user-email` contains the authenticated email address.

204- `oai-authenticated-user-full-name` may contain a non-empty profile name. Treat

205 it as optional and fall back to the email address.

206 

207Keep authorization decisions in server-side code, and don't depend on

208name-split headers.

209 

210</ToggleSection>

211 

212<a id="load-visitor-data-via-plugins"></a>

213 

214## Bring user data to your Site with plugins

215 

216Use plugins in Sites to build a Site that loads

217data from each Site viewer's own connected apps. For example, an issue dashboard

218can show your assigned issues to you and your teammate's assigned issues to

219them. Each visitor signs in with ChatGPT and chooses which connected accounts

220and access to allow.

221 

222Using plugins in Sites requires a workspace where the feature is enabled and a

223 Site that is private to that workspace. Connected features require membership

224 in the Site's workspace and remain subject to each visitor's existing app

225 permissions. Admins can follow [Enable plugin use in

226 Sites](https://learn.chatgpt.com/docs/enterprise/sites#enable-plugin-use-in-sites) to review

227 individual plugin permissions and check tenant restrictions.

228 

229### Build a Site with connected data

230 

231<WorkflowSteps>

232 

2331. Ask ChatGPT Work or Codex to build a Site. Name the plugin connector and what it should do, or ask which plugins are available.

2342. Try the preview with real data. The agent uses your available connections

235 while building and previewing the Site.

2363. Ask the agent to publish the Site for your workspace and

237 share its link with teammates.

238 

239</WorkflowSteps>

240 

241For example:

242 

243 

244 

245**Prompt:**

246 

247```text

248Build a Site using my connected issue tracker that shows issues assigned to me, grouped by priority. Add project filters, links to the original issues, and a Refresh button.

249```

250 

251GPT-6 Astra · Medium

252 

253You can also build a document finder that links to original documents or a

254project overview that combines tickets and messages.

255 

256### Data loading

257 

258On page load, Sites reads connected data using the visitor's connection and permissions. Sites manages caching and reloading; ask ChatGPT Work or Codex to add a manual refresh action if needed.

259 

260By default, Sites uses a **Load more** action to fetch another page when the connector returns a cursor.

261 

262Sites uses `getContext()` to find a plugin's actions. It returns cached tool names, descriptions, and schemas; it doesn't refresh discovery.

263 

264### Write to an app

265 

266If your Site needs to write to an app, ask for write access. Write actions must be enabled and configured, and require visitor consent and an explicit user action.

267 

268### Let visitors choose their connections

269 

270To use connected features, a visitor signs in with ChatGPT, reviews the app

271access the Site requests, chooses the connected account and access to allow,

272and returns to the Site. Sharing the Site doesn't grant access to your

273connected accounts. The Site receives data from the apps each visitor allows.

274 

275Visitors can continue without granting app access, but features that need it

276won't work. If an app is unavailable, check that it's connected in the right

277workspace and allowed by workspace settings.

278 

279## Understand projects, versions, and deployments

280 

281A Site is a persistent hosted output that you can reopen, refine, configure,

282and share from **Sites** in ChatGPT.

283 

284<ContentModeSwitch group="codex-surface" ids="app,cli,ide">

285 

286A Sites project links a local source project to hosting managed through Sites.

287Sites stores that linkage and optional storage binding names in

288`.openai/hosting.json`. A newly created local starter can begin without a

289`project_id`; Sites adds one after it provisions the hosted project.

290 

291For example, a provisioned site that uses a relational database binding and no

292file storage can contain:

293 

294```json

295{

296 "project_id": "<project-id>",

297 "d1": "DB",

298 "r2": null

299}

300```

301 

302</ContentModeSwitch>

303 

304<ContentModeSwitch group="codex-surface" id="web">

305 

306A Site appears in your Sites list even after the ChatGPT Work chat that created it ends.

307You don't need a local project or manifest to start a Site on the web. A Site is

308separate from a ChatGPT Project.

309 

310</ContentModeSwitch>

311 

312<ContentModeSwitch group="codex-surface" ids="app,cli,ide">

313 

314Sites publishing has two separate stages:

315 

3161. **Save a version.** ChatGPT builds a deployable version. For a local source

317 project, ChatGPT associates the version with the Git commit used for the

318 build. Use this stage when you want a reviewable deployment candidate.

3192. **Deploy a version.** ChatGPT publishes a saved version and reports the

320 production URL when deployment succeeds. Use this only when you intend for

321 the selected audience to access the site.

322 

323Ask ChatGPT to list or inspect saved versions when you need to identify a

324previous deployment candidate.

325 

326</ContentModeSwitch>

327 

328## Choose a supported site shape

329 

330For new projects, the Sites workflow can start with its recommended Site

331starter. For an existing project, ask ChatGPT to confirm that the project can

332produce compatible deployment artifacts before you request a deployment.

333 

334Tell ChatGPT about the product behavior you need so it can select the appropriate

335site shape:

336 

337| Site need | What to ask Sites for |

338| -------------------------------------------------------------- | ----------------------------------------------------------------------------- |

339| Content-led website or landing page | A Site with no persistent application state unless the experience requires it |

340| Saved records, user progress, or game scores | D1, a relational database for durable structured data |

341| Images, documents, audio, video, or other uploads | R2, object storage for files |

342| Uploaded files with searchable metadata | D1 for metadata and R2 for file contents |

343| Internal site that needs the current workspace user's identity | Workspace-authenticated user identity |

344| Public sign-in or an external identity provider | An authentication-enabled Site |

345 

346Don't request durable storage for temporary presentation state, such as a

347theme choice or a dismissed banner. Do request it for product data that people

348expect the hosted site to remember.

349 

350## Control access and secrets

351 

352A new Site is limited to its owner and workspace admins until you change its

353access. Keep access limited while you review the content, data handling, and

354expected audience.

355 

356Depending on your account and workspace settings, sharing options can include:

357 

358- **Owner and workspace admins**

359- **Selected active users or groups**, where supported

360- **Invited external viewers**, when external invitations are available

361- **Anyone in the workspace**, where supported

362- **Anyone on the internet**, only when public publishing is enabled

363 

364Visitor access lets people open the Site; it doesn't give them editing access.

365In Enterprise workspaces, public publishing is off by default and must be

366enabled by an admin.

367 

368For limited sharing, invited visitors must sign in with the account that

369received access. A public Site is available without ChatGPT workspace access. A

370Site's audience setting and any sign-in feature built into the Site are separate

371controls.

372 

373For example:

374 

375 

376 

377**Prompt:**

378 

379```text

380Change this Site's access to everyone in my workspace after showing me the current Site and confirming its URL.

381```

382 

383### Invite people outside your workspace

384 

385External invitations let you give named people access to a Site without making

386it public. You can invite viewers outside your workspace, or share a private

387Site from a personal account. The feature is rolling out to Sites users on

388Plus, Pro, Business, and Enterprise plans.

389 

390<WorkflowSteps>

391 

3921. Open a Site you own and select **Share**.

3932. To keep the Site private, set **Who has access** to **Only those invited**.

3943. Enter the viewer's email address under **Search for people or groups**, or

395 **Enter an email address** for a personal Site, then select the recipient.

3964. Review the audience and the recipient's **Viewer** access, then select

397 **Invite**.

3985. Confirm that the viewer appears in the saved access list. Share the Site's

399 link and ask them to sign in with the account that received access.

400 

401</WorkflowSteps>

402 

403External viewers can open and use the Site. They don't become workspace

404members or Site editors, and can't edit or publish the Site. The invitation

405grants access to this Site; review its content and connected data before sharing.

406 

407In Enterprise, admins manage **Allow members to invite external visitors to

408sites** under **Workspace settings > Permissions & roles**. This permission

409is separate from permission to publish Sites publicly.

410Business workspaces don't have a separate external-invitation permission

411toggle; Sites must be enabled, and the feature must be available to the account.

412If the invitation option is missing, check the selected account, Site

413ownership, workspace permissions, and rollout availability.

414 

415To remove a viewer, open the Site's sharing controls and remove their access.

416Also check the remaining audience settings: removing one invitation doesn't

417remove access the person has through public, workspace, or group sharing.

418 

419### Collaborate on a Site

420 

421Site collaboration requires a workspace. When the feature is available, a Site

422owner can invite active members of the same workspace as editors.

423 

424Editors can read the Site's live database data. Invite only people you trust

425with the Site's code and data.

426 

427<WorkflowSteps>

428 

4291. Open the Site and select **Share**.

4302. Under **Add people or groups**, find and select a workspace member. They

431 are added as a visitor.

4323. Open **Can view** next to that person and choose **Can edit**. Access saves

433 automatically. The Site appears under **Shared with you** in the member's

434 Sites view.

4354. The editor can open the Site, make changes, save versions, and publish

436 updates after the owner has published the Site for the first time.

437 

438</WorkflowSteps>

439 

440The Site owner manages editor access and can promote an existing visitor to

441editor, change an editor to **Can view**, or remove their access. Co-editing

442doesn't add a separate workspace permission toggle.

443 

444Editors can't change the Site's audience, invite or remove other people, manage

445settings or analytics, restore an earlier version, or transfer ownership. An

446editor also can't perform the Site's first publish; the owner must publish the

447Site before editors can publish later updates.

448 

449Editor access is separate from visitor access. The steps above first add the

450person as a visitor, then grant editing access. Promoting a visitor to editor

451doesn't change the Site's audience setting.

452 

453### Configure runtime environment values

454 

455Open **Sites**, then open the Site's settings to add, update, or remove hosted

456environment variables and secrets. Keep secret values out of prompts, attached

457files, and Site content.

458 

459<ContentModeSwitch group="codex-surface" id="web">

460 

461Go to [Sites in ChatGPT](https://chatgpt.com/sites), find the Site, then select

462**More actions** > **Settings**.

463 

464</ContentModeSwitch>

465 

466<ContentModeSwitch group="codex-surface" ids="app,cli,ide">

467 

468Don't store these values in `.openai/hosting.json`. Keep local `.env` and

469`.env.example` files aligned with the keys needed for local development, and

470don't commit secret values.

471 

472When you add, update, or remove hosted environment values, ask ChatGPT to

473redeploy the approved saved version so the next deployment uses the updated

474configuration.

475 

476</ContentModeSwitch>

477 

478## Change a Site URL

479 177 

480Where URL editing is available, Site owners can change the ChatGPT-hosted URL178Sites hosts web experiences in its supported runtime. Some frameworks, private networks, databases, background services, and hosting patterns aren't supported.

481for an existing Site without creating another deployment.

482 179 

4831. Open **Sites**, find the Site, and open its settings.180HTTP, HTTPS, and WebSockets are supported. Raw inbound and outbound TCP connections aren't supported.

4842. Find the Site URL and select **Change URL**.

4853. Enter an available name. It must contain at least five characters, start

486 with a lowercase letter, and use only lowercase letters, numbers, and single

487 hyphens. It can't end with a hyphen or contain consecutive hyphens.

4884. Confirm the change and wait while Sites updates the address.

489 181 

490The URL change doesn't create another deployment. The previous address182| Resource | Storage limit per site |

491redirects to the new one, including routes and query parameters.183| ----------------- | ---------------------- |

184| D1 database | 10 GB |

185| R2 object storage | No fixed storage limit |

492 186 

493Changing the ChatGPT-hosted URL doesn't add, remove, or change a custom domain.187For current residency coverage and workspace requirements, see [Managing ChatGPT Sites for your workspace](https://help.openai.com/en/articles/20001338-managing-chatgpt-sites-for-your-workspace) in the Help Center.

494Custom domains are a separate, existing feature; use the custom-domain

495settings when that feature is available.

496 188 

497## Connect a custom domain189You can sell goods or services and collect payments through a third-party payment processor. Payment-card data must be collected and processed solely by that provider, not by your site or OpenAI. Other financial transactions remain prohibited. You're responsible for the integration, fulfillment, refunds, customer support, and applicable taxes. See the [Sites Terms](https://openai.com/policies/chatgpt-sites-terms/) for requirements.

498 190 

499Where custom domains are available, you can connect an apex domain or subdomain191Don't use sites to process Protected Health Information; target children under 13 or the applicable age of digital consent; distribute malware; enable phishing; impersonate people or organizations; or otherwise violate OpenAI policies.

500that you already own. Sites doesn't register domains for you, so you must be

501able to change the domain's DNS records. Custom domains aren't available in

502Enterprise workspaces at launch.

503 192 

504To connect a domain:193See [Creating and managing ChatGPT sites](https://help.openai.com/en/articles/20001339) for current limits and policy links.

505 194 

5061. Open the Site's settings and select **Add domain**.195## Next up

5072. Enter the apex domain or subdomain you want to use.

5083. Copy the DNS records and values Sites provides, then add them through your

509 domain provider.

5104. Wait a few minutes, then return to the Site's settings and refresh the domain

511 status.

512 196 

513You can also ask ChatGPT to help point the domain at your Site. If browsing or

514computer use is enabled, ChatGPT can help you navigate your domain provider

515after you sign in.

516 197 

517## Review before you share

518 198 

519Before you share a Site:199 [Create a site

520 200 

521- Review its content, generated text and images, links, uploaded files, forms,

522 and interactive behavior.

523- Confirm that it doesn't expose confidential or sensitive information, secret

524 values, or third-party content you don't have the right to share.

525- Test the Site from the intended visitor experience, including its access and

526 sign-in behavior.

527- Review features that collect personal information or other visitor content.

528 Decide whether the Site should collect, share, or publish that information.

529- If the Site uses Sign in with ChatGPT, explain what visitor information it

530 receives and how it uses that information.

531- If the Site collects or processes personal data, comply with

532 [applicable privacy and data-protection laws](https://help.openai.com/en/articles/20001340).

533- Choose the narrowest sharing option that fits the intended audience.

534- Open the shared Site and confirm that the intended audience can visit it.

535 201 

536<ContentModeSwitch group="codex-surface" id="app">

537 202 

538For a Site built from a local project, also review the source changes and any203 <TextPrompt />

539database migrations in the Codex [review pane](https://learn.chatgpt.com/docs/code-review?surface=app).

540 204

541</ContentModeSwitch>

542 205 

543## Take down or delete a Site206 Describe, build, and refine a website in ChatGPT or Codex.](https://learn.chatgpt.com/docs/sites/creating-a-site)

207 [Data and connections

544 208 

545To remove access without deleting a Site, open its sharing settings and restrict

546access to yourself or selected people. Confirm that the previous audience can no

547longer open it.

548 209 

549To permanently delete a Site:

550 210 

5511. Open **Sites** and locate the Site.211 <Mcp />

5522. Select **Delete site** and follow the instructions in the prompt.

5533. Enter the Site slug, then select **Permanently delete**.

554 212

555Deleting a Site permanently removes it. You can't restore a deleted Site.

556 213 

557## Understand limits and unsupported uses214 Choose storage, add sign-in, and connect apps to your site.](https://learn.chatgpt.com/docs/sites/data-and-connections)

215 [Publish and share

558 216 

559Sites hosts web experiences that run in the supported Sites runtime. Some

560frameworks, private networks, databases, background services, and hosting

561patterns aren't supported.

562 217 

563HTTP, HTTPS, and WebSockets are supported. Raw inbound and outbound TCP

564connections aren't.

565 218 

566Each Site has these storage limits:219 <Globe />

567 

568| Resource | Limit |

569| ------------------- | ---------------------- |

570| D1 database storage | 10 GB |

571| R2 object storage | No fixed storage limit |

572 220

573Sites doesn't support data residency or inference residency at launch. This

574includes deployed Sites, Site code, D1 and R2 data and file storage, generated

575artifacts, and logs.

576 221 

577Don't use Sites to process Protected Health Information or payment-card data;222 Review your site, choose its audience, and collaborate with editors.](https://learn.chatgpt.com/docs/sites/publishing-and-sharing)

578target children under 13 or the applicable age of digital consent; enable223 [Manage a site

579financial transactions; distribute malware; enable phishing; impersonate people

580or organizations; or otherwise violate OpenAI policies. See

581[Creating and managing ChatGPT Sites](https://help.openai.com/en/articles/20001339)

582for the current limits and policy links.

583 224 

584## Related documentation

585 225 

586<ContentModeSwitch group="codex-surface" id="app">

587 226 

588- [ChatGPT desktop app](https://learn.chatgpt.com/docs/app) introduces app navigation, projects, and chats.227 <Folder />

589- [Review and ship changes](https://learn.chatgpt.com/docs/code-review?surface=app) explains how to inspect source

590 changes before publishing them.

591 228

592</ContentModeSwitch>

593 229 

594<ContentModeSwitch group="codex-surface" ids="cli,ide">230 Review analytics, change domains and settings, and manage site versions.](https://learn.chatgpt.com/docs/sites/managing-a-site)

595 231 

596- [Projects and chats](https://learn.chatgpt.com/docs/projects) explains how folder and workspace

597 context carries across chats.

598- [Review and ship changes](https://learn.chatgpt.com/docs/code-review) explains the review workflow for

599 each Codex client.

600- [Sandboxing](https://learn.chatgpt.com/docs/sandboxing) explains the local execution boundary.

601 232 

602</ContentModeSwitch>

603 233 

604<ContentModeSwitch group="codex-surface" id="web">

605 234 

606- [Open Sites in ChatGPT](https://chatgpt.com/sites) to return to Sites you've

607 created.

608- [Projects and chats](https://learn.chatgpt.com/docs/projects?surface=web) explains how to keep

609 related chats and source files together.

610- [Work with files](https://learn.chatgpt.com/docs/artifacts-viewer?surface=web) explains how to review

611 generated files in ChatGPT web.

612 235 

613</ContentModeSwitch>236<SitesReferenceNavigation />

sites/creating-a-site.md +156 −0 created

Details

1# Create a site

2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 

5Start by describing what you want to build in ChatGPT or Codex. Then review the first version and refine it as you go.

6 

7## Get started with sites

8 

9Open ChatGPT on the web or in the desktop app. Include the word "website" in your prompt or mention `@Sites` to start the sites workflow. For example, you could ask for a project request dashboard for an operations team:

10 

11 

12 

13**Prompt:**

14 

15```text

16Build a project request dashboard website for my operations team. Let team members submit requests, see who owns each one, update the status, and filter the list. Require people to sign in with their workspace account, and keep the request data saved between visits.

17```

18 

19**For ChatGPT Enterprise users:** A workspace admin must enable sites for your

20 workspace or role before you can create one. If you don't see sites, ask your

21 admin about access. [See the Sites administration

22 guide](https://learn.chatgpt.com/docs/enterprise/sites).

23 

24The dashboard is one example. You can adapt the following flow to the site you want to build.

25 

26<WorkflowSteps variant="headings">

27 

281. Describe the site

29 

30 Tell ChatGPT who the site is for and what you'd like people to do. Share the documents or data it needs. For a request dashboard, you might include sample requests, owners, and status labels.

31 

322. Review the site

33 

34 Try the actions you asked for, and check the content and results against your source material. For the dashboard, you might submit a request, change its status, or filter by owner. Confirm how the site uses and saves information.

35 

363. Refine the site

37 

38 Tell ChatGPT what you'd like to change, and share a screenshot or reference site if it would help. For the dashboard, you might ask: “Put the owner and status filters above the request list.”

39 

404. Manage and share the site

41 

42 Return to **sites** to reopen or edit the site. Before publishing, follow the [review and sharing checks](https://learn.chatgpt.com/docs/sites/publishing-and-sharing#review-before-you-share). Choose who can visit and check the published link as an intended visitor.

43 

44</WorkflowSteps>

45 

46<ToggleSection title="Find sites and edit in the preview">

47 

48In the ChatGPT desktop app, open **sites**. You can start a site from a prompt or

49from a compatible local project, then return to the sites view to manage it.

50 

51On the web, select **More** > **sites**, or go directly to [sites in ChatGPT](https://chatgpt.com/sites), to find sites you've created.

52In the preview, select **Edit**. Under **Describe website edits**, describe the

53changes you want. Use **Screenshot** or **Add files and more** when more

54context would help.

55 

56Sites doesn't have a standalone management view in the Codex CLI or IDE

57extension. Use ChatGPT on the web or in the desktop app to create, save, deploy,

58and manage a site. You can still use the CLI or IDE extension to edit and test

59a local project before publishing it.

60 

61</ToggleSection>

62 

63Every sites deployment URL is a production deployment. If you want to review a

64 build before it becomes live, ask ChatGPT to save a version without deploying

65 it.

66 

67## Save records and progress

68 

69Saving a site version preserves its content, design, and behavior. Visitor requests, status updates, uploaded files, and personal progress need their own storage. Tell ChatGPT or Codex what to save and who should be able to read or change it.

70 

71For shared or personal records, ask for access rules enforced on the server. Check that updates stay saved after reload, and use separate accounts to confirm each person's access. Hiding a button doesn't protect the underlying record.

72 

73For supplied files, specify whether the site should use a copy or refresh from the source. Check when the information was last updated.

74 

75For storage and sign-in options, see [Data and connections](https://learn.chatgpt.com/docs/sites/data-and-connections#choose-storage-and-sign-in-features).

76 

77<a id="describe-your-site"></a>

78 

79<a id="build-on-and-edit-your-site"></a>

80 

81## Tips for building your site

82 

83- **Use your source material.** Attach the files, links, definitions, and formulas the site should use. Ask ChatGPT or Codex to flag missing information instead of filling it in.

84- **Describe empty and error states.** Explain what visitors should see when a search has no matches, a form is incomplete, or a connected app is unavailable. Include a way to retry or correct the problem.

85- **Share a visual reference.** Attach a screenshot or link to a page, and point out the details you want to use, such as compact cards or search placement. Include any brand colors, fonts, or writing guidelines.

86- **Plan for phones.** Describe how cards, tables, and forms should fit a narrow screen. Check that navigation, filters, and form controls remain usable without horizontal scrolling.

87- **Label sample data.** If the final content isn't ready, ask for sample records labeled as examples. Replace them before sharing the site.

88- **Make focused changes.** Ask for one change at a time and say what to keep. For example, move a filter without changing the saved records, access rules, or existing actions. Try the affected interaction after each change.

89 

90The [Sites showcase](https://developers.openai.com/showcase) shares example web apps you could build with Sites.

91 

92### Example prompts

93 

94For a resource hub, start with a short prompt:

95 

96 

97 

98**Prompt:**

99 

100```text

101Build a resource hub website where project leads can search our kickoff resources by task. Use the resource list I attach. Save a version without deploying it so I can review it.

102```

103 

104You could expand that request with visitor actions, search rules, and design preferences:

105 

106 

107 

108**Prompt:**

109 

110```text

111Build a resource hub website for project leads preparing a kickoff. Use the resource list I attach, with its titles, descriptions, task labels, and source links.

112 

113Visitor actions:

114 

115- Choose a task, such as preparing an agenda.

116- Search within that task's resources.

117- Select a result to open its source link.

118 

119Search rules:

120 

121- When the search is empty, show all resources for the selected task.

122- If no task is selected, show the full list.

123- If there are no matches, offer a way to clear the filters and show the help contact from my list.

124- Flag entries with missing titles or links for my review.

125- Search filters can reset when the visitor returns.

126 

127Design preferences:

128 

129- Use a white background and dark text. Keep descriptions to one sentence, with a layout that works on a phone.

130- If I provide a reference screenshot, use it for the search placement and compact resource cards.

131- If I provide a brand guide, use its accent color.

132 

133Save a version without deploying it so I can review the layout and try the search.

134```

135 

136For an existing project, ask ChatGPT to check compatibility and prepare it for publishing:

137 

138 

139 

140**Prompt:**

141 

142```text

143Deploy this project with sites. Check whether it is compatible, make any required changes, and give me the deployment URL.

144```

145 

146When a site needs to remember records or uploaded files, include that in your request:

147 

148 

149 

150**Prompt:**

151 

152```text

153Add player scores and avatar uploads to this game. Keep the scores and uploaded avatars between visits.

154```

155 

156<SitesReferenceNavigation />

sites/data-and-connections.md +139 −0 created

Details

1# Data and connections

2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 

5Your site might need to save requests, remember progress, or show information from connected apps. Tell ChatGPT what to save, where the information comes from, and who should have access.

6 

7<a id="choose-a-supported-site-shape"></a>

8 

9## Choose storage and sign-in features

10 

11For new projects, the sites workflow can start with its recommended site

12starter. For an existing project, ask ChatGPT to confirm that the project can

13produce compatible deployment artifacts before you request a deployment.

14 

15Describe what the site should do so ChatGPT can choose the required storage and identity features:

16 

17| Site need | What to ask sites for |

18| -------------------------------------------------------------- | ----------------------------------------------------------------------------- |

19| Content-led website or landing page | A site with no persistent application state unless the experience requires it |

20| Saved records, user progress, or game scores | D1, a relational database for durable structured data |

21| Images, documents, audio, video, or other uploads | R2, object storage for files |

22| Uploaded files with searchable metadata | D1 for metadata and R2 for file contents |

23| Internal site that needs the current workspace user's identity | Workspace-authenticated user identity |

24| Public sign-in or an external identity provider | An authentication-enabled site |

25 

26Don't request durable storage for temporary presentation state, such as a

27theme choice or a dismissed banner. Do request it for product data that people

28expect the hosted site to remember.

29 

30## Add Sign in with ChatGPT

31 

32Public sites can remain open to everyone while offering optional Sign in with

33ChatGPT for identity-aware features, such as saved progress, personalized views,

34or records that belong to a specific person. Workspace-restricted sites already

35use ChatGPT identity to enforce their sharing settings.

36 

37Ask sites to add the sign-in experience:

38 

39 

40 

41**Prompt:**

42 

43```text

44Add Sign in with ChatGPT to this public site. Keep the site available to signed-out visitors. Show a Sign in with ChatGPT action when someone is signed out. After they sign in, greet them with their full name when available, or their email address otherwise. Add a Sign out action, and keep authorization decisions in server-side code.

45```

46 

47<ToggleSection title="How it works">

48 

49Sites handles the sign-in and sign-out flows through platform-provided paths,

50then returns the visitor to your site:

51 

52```html

53<a href="/signin-with-chatgpt">Sign in with ChatGPT</a>

54<a href="/signout-with-chatgpt">Sign out</a>

55```

56 

57 

58After a visitor signs in, sites forwards their identity to the server through

59these request headers:

60 

61- `oai-authenticated-user-email` contains the authenticated email address.

62- `oai-authenticated-user-full-name` may contain a non-empty profile name. Treat

63 it as optional and fall back to the email address.

64 

65Keep authorization decisions in server-side code, and don't depend on

66name-split headers.

67 

68</ToggleSection>

69 

70<a id="load-visitor-data-via-plugins"></a>

71 

72## Bring user data to your site with plugins

73 

74Use plugins in sites to build a site that loads

75data from each site viewer's own connected apps. For example, an issue dashboard

76can show your assigned issues to you and your teammate's assigned issues to

77them. Each visitor signs in with ChatGPT and chooses which connected accounts

78and access to allow.

79 

80Plugin use must be enabled in the workspace, and the site must be private to

81 that workspace. Visitors need to belong to the workspace and use their

82 existing app permissions. Admins can review individual plugin permissions and

83 tenant restrictions in [Enable plugin use in

84 sites](https://learn.chatgpt.com/docs/enterprise/sites#enable-plugin-use-in-sites).

85 

86### Build a site with connected data

87 

88<WorkflowSteps>

89 

901. Ask ChatGPT Work or Codex to build a site. Name the plugin connector and what it should do, or ask which plugins are available.

912. Try the preview with real data. The agent uses your available connections

92 while building and previewing the site.

933. Ask the agent to publish the site for your workspace and

94 share its link with teammates.

95 

96</WorkflowSteps>

97 

98For example:

99 

100 

101 

102**Prompt:**

103 

104```text

105Build a site using my connected issue tracker that shows issues assigned to me, grouped by priority. Add project filters, links to the original issues, and a Refresh button.

106```

107 

108You can also build a document finder that links to original documents or a

109project overview that combines tickets and messages.

110 

111### Data loading

112 

113On page load, sites reads connected data using the visitor's connection and permissions. Sites manages caching and reloading; ask ChatGPT Work or Codex to add a manual refresh action if needed.

114 

115By default, sites uses a **Load more** action to fetch another page when the connector returns a cursor.

116 

117<ToggleSection title="Technical reference: Plugin discovery">

118 

119Sites uses `getContext()` to find a plugin's actions. It returns cached tool names, descriptions, and schemas; it doesn't refresh discovery.

120 

121</ToggleSection>

122 

123 

124 

125 

126For supported connected-app actions and availability, see [Creating and using ChatGPT Sites](https://help.openai.com/en/articles/20001339-creating-and-using-chatgpt-sites) in the Help Center.

127 

128### Let visitors choose their connections

129 

130To use connected features, a visitor signs in with ChatGPT, reviews the app

131access the site requests, chooses the connected account and access to allow,

132and returns to the site. Sharing the site doesn't grant access to your

133connected accounts. The site receives data from the apps each visitor allows.

134 

135Visitors can continue without granting app access, but features that need it

136won't work. If an app is unavailable, check that it's connected in the right

137workspace and allowed by workspace settings.

138 

139<SitesReferenceNavigation />

sites/managing-a-site.md +119 −0 created

Details

1# Manage a site

2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 

5Return to your site in ChatGPT to review its traffic, change its settings, or manage its saved versions.

6 

7## Review site analytics

8 

9Sites records traffic automatically, without an analytics SDK. The analytics view shows total unique visitors and page views, plus both metrics over time. Change the date range or granularity to compare periods.

10 

11Open **sites** in the ChatGPT desktop app, or go to [sites in ChatGPT](https://chatgpt.com/sites) on the web. Find the site, then select **More actions** > **Analytics**.

12 

13 

14 

15> Illustration: Interactive sites analytics dashboard showing unique visitors and page views over seven days.

16 

17 

18 

19Analytics is currently available for sites that aren't owned by an Enterprise

20 workspace.

21 

22## Change a site URL

23 

24Site owners can manage the site's name and URL in its settings. For the current

25controls and instructions, see [Creating and using ChatGPT Sites](https://help.openai.com/en/articles/20001339-creating-and-using-chatgpt-sites) in the Help Center.

26 

27## Connect a custom domain

28 

29Where custom domains are available, you can connect an apex domain or subdomain

30that you already own. Sites doesn't register domains for you, so you must be

31able to change the domain's DNS records.

32 

33Custom domains aren't available in Enterprise workspaces at launch.

34 

35To connect a domain:

36 

371. Open the site's settings and select **Add domain**.

382. Enter the apex domain or subdomain you want to use.

393. Copy the DNS records and values sites provides, then add them through your

40 domain provider.

414. Wait a few minutes, then return to the site's settings and refresh the domain

42 status.

43 

44You can also ask ChatGPT to help point the domain at your site. If browsing or

45computer use is enabled, ChatGPT can help you navigate your domain provider

46after you sign in.

47 

48## Configure runtime environment values

49 

50Open **sites**, then open the site's settings to add, update, or remove hosted

51environment variables and secrets. Keep secret values out of prompts, attached

52files, and site content.

53 

54Go to [sites in ChatGPT](https://chatgpt.com/sites), find the site, then select

55**More actions** > **Settings**.

56 

57If you're working with a local source project, also follow this file guidance:

58 

59Don't store these values in `.openai/hosting.json`. Keep local `.env` and

60`.env.example` files aligned with the keys needed for local development, and

61don't commit secret values.

62 

63Follow the configuration instructions shown in the site's settings. For current

64owner controls, see [Creating and using ChatGPT Sites](https://help.openai.com/en/articles/20001339-creating-and-using-chatgpt-sites) in the Help Center.

65 

66## Take down or delete a site

67 

68To remove access without deleting a site, open its sharing settings and restrict

69access to yourself or selected people. Confirm that the previous audience can no

70longer open it.

71 

72To permanently delete a site:

73 

741. Open **sites** and locate the site.

752. Select **Delete site** and follow the instructions in the prompt.

763. Enter the site slug, then select **Permanently delete**.

77 

78Deleting a site permanently removes it. You can't restore a deleted site.

79 

80## Understand projects, versions, and deployments

81 

82For an existing local project, ask sites to check compatibility and prepare it for publishing.

83 

84A site stays available in **sites** in ChatGPT, where you can reopen, refine, configure, and share it.

85 

86For a local source project, a sites project links your source to hosting managed through sites.

87Sites stores that linkage and optional storage binding names in

88`.openai/hosting.json`. A newly created local starter can begin without a

89`project_id`; sites adds one after it provisions the hosted project.

90 

91For example, a provisioned site that uses a relational database binding and no

92file storage can contain:

93 

94```json

95{

96 "project_id": "<project-id>",

97 "d1": "DB",

98 "r2": null

99}

100```

101 

102 

103A site appears in your sites list even after the ChatGPT Work chat that created it ends.

104You don't need a local project or manifest to start a site on the web. A site is

105separate from a ChatGPT Project.

106 

107Sites publishing has two separate stages:

108 

1091. **Save a version.** ChatGPT builds a deployable version. For a local source

110 project, ChatGPT associates the version with the Git commit used for the

111 build. Use this stage when you want a reviewable deployment candidate.

1122. **Deploy a version.** ChatGPT publishes a saved version and reports the

113 production URL when deployment succeeds. Use this only when you intend for

114 the selected audience to access the site.

115 

116Ask ChatGPT to list or inspect saved versions when you need to identify a

117previous deployment candidate.

118 

119<SitesReferenceNavigation />

Details

1# Publish and share

2 

3> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 

5Review your site before publishing it, then choose who can visit and who can help edit it.

6 

7<a id="review-and-share-your-site"></a>

8 

9## Review before you share

10 

11Before you share a site:

12 

13<WorkflowSteps variant="headings">

14 

151. ### Try the site

16 - Review its content, generated text and images, links, uploaded files, forms,

17 and interactive behavior.

18 - Test the site from the intended visitor experience, including its access and

19 sign-in behavior.

20 

21 For a site built from a local project, also review the source changes and any

22 database migrations in the Codex [review pane](https://learn.chatgpt.com/docs/code-review?surface=app).

23 

242. ### Check data and access

25 - Confirm that it doesn't expose confidential or sensitive information, secret

26 values, or third-party content you don't have the right to share.

27 - Review features that collect personal information or other visitor content.

28 Decide whether the site should collect, share, or publish that information.

29 - If the site uses Sign in with ChatGPT, explain what visitor information it

30 receives and how it uses that information.

31 - If the site collects or processes personal data, comply with

32 [applicable privacy and data-protection laws](https://help.openai.com/en/articles/20001340).

33 

343. ### Choose the audience

35 - Choose the narrowest sharing option that fits the intended audience.

36 - Open the shared site and confirm that the intended audience can visit it.

37 

38</WorkflowSteps>

39 

40<a id="control-access-and-secrets"></a>

41 

42Keep access limited while you review the content, data handling, and expected audience.

43 

44Use the sharing options shown for your site to choose who can visit. Visitor access lets people open the site; it doesn't give them editing access.

45 

46For limited sharing, invited visitors must sign in with the account that

47received access. A public site is available without ChatGPT workspace access. A

48site's audience setting and any sign-in feature built into the site are separate

49controls.

50 

51For sharing controls and site management instructions, see [Creating and managing ChatGPT sites](https://help.openai.com/articles/20001339) in the Help Center.

52 

53<a id="update-your-site"></a>

54 

55## Publish an update

56 

57Open your site in ChatGPT and describe the changes you want. Review the result, save a version, and deploy it. Check the changes at the published URL.

58 

59Review the site code and any data the site saves or displays. Test the published

60site with an account from its intended audience.

61 

62## Choose who can visit

63 

64A new site is limited to its owner and workspace admins until you change its access. Depending on your account and workspace settings, the sharing options can include:

65 

66- **Owner and workspace admins**

67- **Selected active users or groups**, where supported

68- **Invited external viewers**, when external invitations are available

69- **Anyone in the workspace**, where supported

70- **Anyone on the internet**, only when public publishing is enabled

71 

72In Enterprise workspaces, public publishing is off by default. An admin must

73 enable it before eligible members can publish publicly. Visitor access lets

74 someone open a site; it doesn't give them editing access. [See the Sites

75 administration guide](https://learn.chatgpt.com/docs/enterprise/sites#enable-sites-and-permissions).

76 

77For example, you could ask:

78 

79 

80 

81**Prompt:**

82 

83```text

84Change this site's access to everyone in my workspace after showing me the current site and confirming its URL.

85```

86 

87## Invite people outside your workspace

88 

89External invitations let you share a site with named viewers without making it public. You can invite people outside your workspace or share a private site from a personal account.

90 

91External invitations are rolling out to sites users on Plus, Pro, Business,

92 and Enterprise plans. If the invitation option is missing, check the selected

93 account, site ownership, workspace permissions, and rollout availability.

94 

95<WorkflowSteps>

96 

971. Open a site you own and select **Share**.

982. To keep the site private, set **Who has access** to **Only those invited**.

993. Enter the viewer's email address under **Search for people or groups**, or

100 **Enter an email address** for a personal site, then select the recipient.

1014. Review the audience and the recipient's **Viewer** access, then select

102 **Invite**.

1035. Confirm that the viewer appears in the saved access list. Share the site's

104 link and ask them to sign in with the account that received access.

105 

106</WorkflowSteps>

107 

108External viewers can open and use the site. They don't become workspace

109members or site editors, and can't edit or publish the site. The invitation

110grants access to this site; review its content and connected data before sharing.

111 

112In Enterprise, admins manage **Allow members to invite external visitors to sites** under **Workspace settings > Permissions & roles**. This permission is separate from permission to publish sites publicly. Business workspaces don't have a separate external-invitation toggle; sites must be enabled and the feature must be available to the account.

113 

114To remove a viewer, open the site's sharing controls and remove their access.

115Also check the remaining audience settings: removing one invitation doesn't

116remove access the person has through public, workspace, or group sharing.

117 

118## Collaborate on a site

119 

120Site collaboration requires a workspace. When the feature is available, a site owner can invite active members of the same workspace as editors.

121 

122Invite only people you trust to edit the site's code and content.

123 

124<WorkflowSteps>

125 

1261. Open the site and select **Share**.

1272. Under **Add people or groups**, find and select a workspace member. They

128 are added as a visitor.

1293. Open **Can view** next to that person and choose **Can edit**. Access saves

130 automatically. The site appears under **Shared with you** in the member's

131 sites view.

1324. The editor can open the site, make changes, save versions, and publish

133 updates after the owner has published the site for the first time.

134 

135</WorkflowSteps>

136 

137The site owner manages editor access and can promote an existing visitor to

138editor, change an editor to **Can view**, or remove their access. Co-editing

139doesn't add a separate workspace permission toggle.

140 

141Editors can't change the site's audience, invite or remove other people, manage

142settings or analytics, or transfer ownership. An

143editor also can't perform the site's first publish; the owner must publish the

144site before editors can publish later updates.

145 

146Editor access is separate from visitor access. The steps above first add the

147person as a visitor, then grant editing access. Promoting a visitor to editor

148doesn't change the site's audience setting.

149 

150<SitesReferenceNavigation />

Details

36 36 

37Sites is a managed hosting service in ChatGPT. Ask ChatGPT to create an app, and it can build the project, run it for testing, deploy it, and return a URL you can share.37Sites is a managed hosting service in ChatGPT. Ask ChatGPT to create an app, and it can build the project, run it for testing, deploy it, and return a URL you can share.

38 38 

39Sites is in public beta for eligible paid plans. It isn't available on Free or Go, or in the EEA, Switzerland, or the United Kingdom at launch, and rollout or workspace settings can affect access.39Sites availability depends on your plan, rollout, and workspace settings. Check [Sites availability and usage limits](https://developers.openai.com/codex/sites#availability-and-usage-limits) before you start.

40 40 

41The scope ranges from static sites to full-stack JavaScript or TypeScript web apps. That makes Sites a good fit for focused internal tools: onboarding dashboards, training hubs, searchable resource libraries, lightweight workflow apps, and reporting views.41The scope ranges from static sites to full-stack JavaScript or TypeScript web apps. That makes Sites a good fit for focused internal tools: onboarding dashboards, training hubs, searchable resource libraries, lightweight workflow apps, and reporting views.

42 42 


77 77 

78Keep structured metadata in D1 and larger file objects in R2. A read-only resource page or small static site may not need either one.78Keep structured metadata in D1 and larger file objects in R2. A read-only resource page or small static site may not need either one.

79 79 

80Sites doesn't support data or inference residency. Don't use it to process protected health information or payment-card data, or to enable financial transactions. Review the [Sites data and usage restrictions](https://help.openai.com/en/articles/20001339-creating-and-managing-chatgpt-sites) before storing sensitive information.80Review the [Sites limits and unsupported uses](https://developers.openai.com/codex/sites#understand-limits-and-unsupported-uses) before storing sensitive information. The guide links to current residency requirements and explains restrictions on protected health information and payments through third-party processors.

81 81 

82## Manage and share your projects82## Manage and share your projects

83 83 

Details

1---

2name: Build an onboarding portal

3tagline: Help new hires find their next task and practice the work they'll be doing.

4summary: Turn your team's first-week plan into daily activities, resource links,

5 and a practice task, with saved progress so each new hire can pick up where

6 they left off.

7bestFor:

8 - Managers onboarding several people into the same role.

9 - People teams maintaining first-week plans and resources for new hires.

10starterPrompt:

11 title: Build an onboarding portal

12 body: >-

13 Use @Sites to build an onboarding portal for [role or team] from our

14 first-week plan. The end-of-week goal is [for example, draft a customer

15 kickoff agenda].

16 

17 

18 Sources:

19 

20 - First-week plan: [attached file or document link].

21 

22 - Product and team resources: [specific document or folder links].

23 

24 - Practice-task instructions and example: [links].

25 

26 - Contacts and their responsibilities: [supplied list].

27 

28 

29 First confirm which sources you can read. List the tasks by day with their

30 source links, instructions, and contacts. Ask about missing or conflicting

31 details; don't invent tasks, people, or company policies.

32 

33 

34 Build a welcome page, a day-by-day checklist, a resource list, helpful

35 contacts, and the end-of-week practice task. For each task, show what to do,

36 why it matters, what a completed task looks like, its resource link, and who

37 can help. Include estimated times only where the plan provides them. Link

38 the practice task to its instructions, approved example, and review

39 criteria. Use the sequence in the supplied plan. Add a progress bar and a

40 Continue where you left off section. Treat completion as a record of

41 activities, not a score of job readiness.

42 

43 

44 Require sign-in and save each person's task completion between visits. Keep

45 progress private to that person and enforce record access on the server.

46 Keep shared task content separate from individual progress. Give tasks

47 stable identities so an instruction or link update doesn't erase completion

48 records.

49 

50 

51 Use a reviewed copy of the plan and links to original resources. Explain

52 that source-document changes need a site update. Don't copy restricted

53 resource contents into a page for a broader audience or write completion

54 data back into source documents. If a source can't be read, ask for an

55 accessible copy or link.

56 

57 

58 Test a new user, partial progress, a completed plan, and a second account.

59 Confirm that progress survives reload, remains private, and isn't reset by

60 an instruction edit. Flag resource links that still need a new hire's access

61 check. Keep the site private to our workspace. Show a saved preview, test

62 results, and open questions before publishing.

63 suggestedEffort: medium

64relatedLinks:

65 - label: Save records and progress

66 url: /codex/sites/creating-a-site#save-records-and-progress

67 - label: Add Sign in with ChatGPT

68 url: /codex/sites/data-and-connections#add-sign-in-with-chatgpt

69---

70 

71> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

72 

73## Before you start

74 

75Turn your first-week plan into a place where new hires can see what to do today, find the right resources, and pick up where they left off. Ask for a portal that brings daily tasks, helpful contacts, and an end-of-week practice task together, with progress saved for each person.

76 

77Choose a role and name something a new hire should be able to do by the end of the week. That goal helps you decide which resources and activities belong in the plan. Gather the materials your team already uses:

78 

79| Source | Example | What it contributes |

80| --------------- | ----------------------------------------------------------------------------- | ------------------------------------ |

81| First-week plan | A document or spreadsheet with day, task, instructions, resource, and contact | The daily checklist |

82| Team resources | A product overview, team directory, and role-specific templates | Links and context for each task |

83| Practice task | Instructions, review criteria, and an approved example | A clear goal for the end of the week |

84 

85Make tasks concrete enough that someone can tell when they're done. “Learn the product” could become “Read the product overview and bring two questions to your buddy meeting.” Include an estimated time if your plan provides one, and explain which activities need another person's help.

86 

87### Example: Preparing for a customer kickoff

88 

89In this hypothetical plan for a customer success manager, the week's activities build toward drafting a kickoff agenda:

90 

91- **Get oriented:** Read the product overview and meet a buddy to discuss questions.

92- **See the work:** Watch an approved sample kickoff and note the questions the host asks.

93- **Try the task:** Draft an agenda using the team's template, then discuss it with a manager.

94 

95For another role, substitute a relevant task, such as preparing a campaign brief or handling a sample support request. Use your approved plan to set the activities and sequence.

96 

97## Connect your sources

98 

99 

100 

101 

1021. Attach the onboarding plan in the build conversation. For documents in a connected app, ask ChatGPT which connections are available, then give it the exact plan, resource, or folder links. Complete any connection step it requests.

103 For example, point to “Customer success: first week” and “Kickoff agenda template” in Google Drive or SharePoint, if the relevant connection is available. Name the exact files instead of asking it to search all company documents.

1042. Ask ChatGPT to list the tasks it found, grouped by day, with the source link and contact for each. Correct gaps before it builds the site. A document link identifies a source; confirm that ChatGPT can read it when you need its contents.

1053. Use the plan as a reviewed copy in the site and keep links to original resources. A later edit to the source document won't automatically update that copy. To revise the plan, return to the build conversation with the updated source.

1064. If resources should load from a connected app when someone visits, request that separately. Use a workspace-private site, and test with the new hire's own app permissions. Follow [data and connections](https://developers.openai.com/codex/sites/data-and-connections#build-a-site-with-connected-data) for the connection flow.

107 

108 

109 

110 

111Open the resource links with a colleague who has the same access as a new hire. Fix inaccessible materials before sharing the site. Keep individual completion records in the site; they don't need to be written back into the onboarding document.

112 

113## How to use it

114 

115 

116 

117 

1181. Put the next action first

119 

120 Use the starter prompt with your plan and resource links. Review the first day as someone who has just joined. They should know which task to start, why it matters, what to open, and who can help. Ask ChatGPT to replace vague task labels with actions from the plan and put the relevant resource beside each task.

121 

1222. Work toward the practice task

123 

124 Follow the week's activities through to the practice task. The new hire should be able to find the instructions, an approved example, and what their manager will look for. In the sample kickoff plan, that might mean checking that the agenda states the meeting's purpose, includes questions for the customer, and leaves time to agree on next steps.

125 

126 Mark an activity complete, reload, and return in another session to check that progress is saved. Use a second account to verify that it starts with its own progress and can't access the first person's record. A completed checklist records activities; use the practice task and conversation to understand where the person still needs help.

127 

1283. Welcome the next hire

129 

130 Review the audience and publish the site for your team. Include its link in your welcome message, and explain how the new hire can use it each day. Ask which instructions or resources were hard to follow during the end-of-week conversation. When the plan changes, ask ChatGPT to preserve completed tasks and explain how new or removed tasks affect progress before publishing the update.

131 

132 

133 

134 

135## Take it further

136 

137### Refresh the plan from its source

138 

139Update the checklist when the onboarding plan changes, while preserving completed tasks and making changes to existing progress explicit.

140 

141 

142 

143**Prompt:**

144 

145```text

146Read the updated onboarding plan at [document link or attached file]. Compare it with the site's current tasks and show additions, removals, and changed instructions. Keep existing task identities and saved completion records wherever the task is the same. Ask me how to handle removed or substantially changed tasks. Update only the reviewed plan and resource links, then show a preview before publishing.

147```

148 

149### Put the next task first

150 

151Help each new hire resume their first unfinished task without hiding the rest of the week's plan.

152 

153 

154 

155**Prompt:**

156 

157```text

158Add a Continue where you left off section showing the signed-in person's first unfinished task, its day, instructions, resource link, and contact. Keep the full week available. Show a completion message when all tasks are done. Test a new user, a partially completed plan, and a completed plan, and confirm that each person's saved progress remains private.

159```

Details

1---

2name: Build a hackathon site

3tagline: Give participants one place to find the agenda, register, and share

4 what they built.

5summary: Turn your event brief and agenda into a site that guides participants

6 through registration and demo submission, then keeps their projects available

7 in a gallery.

8bestFor:

9 - Organizers running a hackathon for their team or organization.

10 - Participants registering a team and submitting a demo.

11starterPrompt:

12 title: Build a hackathon site

13 body: >-

14 Use @Sites to build a hackathon site for [hackathon name] using the event

15 brief and agenda below. The participants belong to our workspace.

16 

17 

18 Sources:

19 

20 - Event brief and organizer contact: [attached file or document link].

21 

22 - Agenda: [file or spreadsheet link and tab].

23 

24 - Event date and time zone: [date and time zone].

25 

26 - Team rules and submission requirements: [document link].

27 

28 - Submission deadline and late-submission rule: [confirmed details].

29 

30 - Project gallery visibility: [when submitted projects should appear and who

31 can see them].

32 

33 - Judging criteria and participant resources: [links or none].

34 

35 

36 First confirm which sources you can read and summarize the schedule,

37 deadline, team rules, and required submission fields. Ask about gaps or

38 conflicting times. Use only the supplied event details. Leave project choice

39 to participants; don't suggest project ideas or example use cases.

40 

41 

42 Build an event overview, agenda, participant instructions, resource list,

43 and organizer contact section. Make it easy to find the next session, team

44 registration, and project submission. Show the event date and time zone with

45 the schedule and exact submission deadline. Keep the deadline and help

46 contact near the submission form. Link to original resources and explain

47 that this is a reviewed copy of the event information.

48 

49 

50 Let participants register a team with its name and members, then return to

51 add a project title, short description, and demo link. Distinguish saved

52 drafts from submitted projects and explain any missing required fields. Show

53 submitted projects in a gallery according to the supplied visibility rule.

54 Require sign-in and enforce team membership on the server so members can

55 edit only their own team's entry. Ask how membership should be confirmed

56 before enabling team edits; don't use a typed name as proof of identity.

57 Save registrations and submissions between visits.

58 

59 

60 Keep event information, team registrations, and project records separate so

61 agenda edits preserve submissions. Validate required fields and demo links.

62 Follow the supplied deadline rule; ask if it is missing. Don't edit source

63 documents or send invitations.

64 

65 

66 Test with two fictional teams: register, submit, edit, reload, and try to

67 edit the other team's entry directly. Check the deadline behavior and mobile

68 layout. Identify and remove test records before the event. Keep the site

69 private to our workspace and show a saved preview, test results, and

70 unresolved questions before publishing.

71 suggestedEffort: medium

72relatedLinks:

73 - label: Save registrations and submissions

74 url: /codex/sites/creating-a-site#save-records-and-progress

75 - label: Choose who can visit

76 url: /codex/sites/publishing-and-sharing#choose-who-can-visit

77---

78 

79> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

80 

81## Before you start

82 

83Give hackathon participants a link they can use before, during, and after the event. Build a site where they can find the schedule, register a team, submit a demo, and explore what others made. Organizers can use the same project gallery to run demos and share the results afterward.

84 

85Gather the information you would normally send participants:

86 

87| Source | Example | What it contributes |

88| ----------------- | ----------------------------------------------------------------------------------- | --------------------------------------- |

89| Event brief | A document with event date, time zone, location, and organizer contact | The event overview and help information |

90| Agenda | A spreadsheet with start time, end time, session, and location or meeting link | The schedule |

91| Participant guide | Team instructions, rules, judging criteria, submission deadline, and resource links | Instructions for joining and submitting |

92 

93Decide what participants need to submit and when projects should appear in the gallery. For example, you might collect a title, a short description of what the team built, and a demo link. Use the event's rules to decide whether entries appear immediately or when submissions close.

94 

95### Example: A one-day team hackathon

96 

97In this hypothetical event, participants meet at 9:00, form teams at 9:30, submit projects by 16:00, and begin demos at 16:30. All times use the event's stated time zone. A participant opens the site to find the morning meeting link, registers their team, then returns later to add the project description and demo. During presentations, the organizer opens each submitted project from the gallery.

98 

99Replace the sample times with your confirmed schedule. Participants choose what to build; the site gives them the information and steps they need to take part.

100 

101## Connect your sources

102 

103 

104 

105 

1061. Attach the event brief and agenda in the build conversation. If they're in a connected app, ask ChatGPT which connections are available, then provide the exact document links and spreadsheet tab name. Complete any connection step it requests.

107 For example, supply the “Team hackathon brief” document and “Agenda” spreadsheet tab from Google Drive or SharePoint, if the relevant connection is available. Use ordinary links for the meeting room and participant resources.

1082. Ask ChatGPT to summarize the schedule, deadline, team rules, and required submission fields. Confirm the event date and time zone. Resolve conflicting deadlines before using the starter prompt.

1093. Build from a reviewed copy of these details, with links to original resources. Keep team registrations and project submissions in the site so they remain available between visits. Changes to the source agenda need a separate site update unless you add a live connection.

1104. If you need the agenda to load from a connected source, ask ChatGPT to verify that the connection can read it. Keep the site private to your workspace and test with a participant's own account. Follow [data and connections](https://developers.openai.com/codex/sites/data-and-connections#build-a-site-with-connected-data).

111 

112 

113 

114 

115Check that participants can open meeting links, instructions, and demo links with their own accounts. You can start with attached files and ordinary resource links; a live connection isn't required for registration and submissions.

116 

117## How to use it

118 

119 

120 

121 

1221. Confirm the event clock and instructions

123 

124 Use the starter prompt with your event materials. Open the preview and compare it with the approved brief. Check the date, time zone, session times, submission deadline, and links. A participant should be able to find where to go next, how to join, and what to submit without asking an organizer. Keep the deadline and help contact near the submission instructions.

125 

1262. Register a team and submit a demo

127 

128 Register a fictional test team, then return to add a project title, description, and demo link. Check the full path from registration to a submitted entry: can members tell what's saved, what's still missing, and whether their project has been submitted? Confirm that it appears in the gallery according to your event's visibility rule.

129 

130 Edit the entry and reload to confirm the changes were saved. Test with a second team account and confirm it can't edit the first team's entry, including through a direct link. Check what happens at the submission deadline and remove test entries before the event.

131 

1323. Use the site during and after the hackathon

133 

134 Review the audience and publish the site for your participants. Add the link to the event invitation and ask a colleague to find the agenda and submission instructions without your help. Before the event, use their feedback to clarify any confusing steps.

135 

136 During demos, use the gallery to open each project and see the team's description alongside its demo link. After the event, keep the gallery available to the intended audience so people can revisit projects they missed. Check demo links from a participant's account, and update event information without replacing the saved submissions.

137 

138 

139 

140 

141## Take it further

142 

143### Update the agenda without losing submissions

144 

145Change session times or locations without overwriting registered teams, submitted projects, or their access rules.

146 

147 

148 

149**Prompt:**

150 

151```text

152Read the revised agenda from [document link, spreadsheet tab, or attached file]. Show the differences in session times, locations, and the submission deadline before applying them. Keep team registrations, project submissions, and their access rules unchanged. Flag conflicting dates or missing time zones. After I confirm the changes, update the agenda and show a preview before publishing.

153```

154 

155### Add a submission countdown

156 

157Make the deadline visible in the event's time zone and explain what happens when it passes.

158 

159 

160 

161**Prompt:**

162 

163```text

164Add a countdown to the confirmed submission deadline on [date and time] in [event time zone]. Keep the exact date, time, and time zone visible beside it. Show a clear message after the deadline instead of negative time. Follow the event's agreed late-submission rule; don't change whether submissions are accepted without asking. Test before, at, and after the deadline.

165```

Details

1---

2name: Manage creative requests from brief to delivery

3tagline: Collect complete briefs, review drafts, and keep final assets with each

4 request.

5summary: Turn your creative brief template into a request app where colleagues

6 submit work, designers manage the queue, and reviewers give feedback on

7 drafts.

8bestFor:

9 - Small creative teams receiving design requests through scattered messages.

10 - Marketing teams coordinating campaign assets and review deadlines.

11starterPrompt:

12 title: Manage creative requests from brief to delivery

13 body: >-

14 Use @Sites to build a creative request app for [team] using our brief

15 template.

16 

17 

18 Sources:

19 

20 - Brief template: [attached file or document link].

21 

22 - Brand guidelines and approved assets: [document or folder links].

23 

24 - Existing requests, if any: [spreadsheet or none].

25 

26 - Creative-team members and reviewers: [supplied list].

27 

28 

29 First confirm which sources you can read and summarize the required fields.

30 Ask about missing owners, unclear dates, or unavailable sources. Use the

31 supplied materials; don't invent brand rules or real requests.

32 

33 

34 Build an intake form for title, audience, key message, format or dimensions,

35 requested date, reviewer, and copy or reference links. Adapt the fields to

36 [request types, such as event graphics or presentations]. Show field

37 examples and explain how to fix missing or invalid entries. Keep the

38 requested date separate from the delivery date agreed by the creative team.

39 

40 

41 Give each request a detail page with its brief, requester, owner, reviewer,

42 agreed date, status, draft link, feedback, and final asset link. Use New, In

43 progress, In review, and Delivered. Make the latest draft and delivered

44 asset easy to distinguish. Add a queue with owner, status, and

45 agreed-delivery-date filters. Make unassigned requests and missing agreed

46 dates visible.

47 

48 

49 Require sign-in. Let requesters create and read their own requests and add

50 feedback. Let only the named creative team manage all requests, assignment,

51 and delivery status. Enforce access on the server, including direct record

52 access. Save records and feedback between visits.

53 

54 

55 Start with a reviewed copy of supplied request data and links to original

56 assets. Explain which content is copied and which remains linked. Don't

57 imply that source files stay synchronized, and don't modify connected

58 documents or task trackers.

59 

60 

61 Use labeled fictional requests for testing. Test required fields,

62 submission, status changes, saved feedback after reload, and two requesters

63 with different access. Check the layout on mobile. Keep the site private to

64 our workspace and show a saved preview, test results, and any unresolved

65 questions before publishing.

66 suggestedEffort: medium

67relatedLinks:

68 - label: Data and connections

69 url: /codex/sites/data-and-connections

70 - label: Publish and share

71 url: /codex/sites/publishing-and-sharing

72---

73 

74> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

75 

76## Before you start

77 

78Give colleagues one place to request creative work and follow it through review and delivery. A request app can collect the details a designer needs, show who owns the work, and keep feedback beside the latest draft. The requester can check progress without searching through messages; the creative team can see what needs attention across its queue.

79 

80Choose a recurring request, such as an event graphic, presentation, or campaign asset. Gather the material you already use:

81 

82| Source | Example | What it contributes |

83| -------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------- |

84| Request brief | A form or document with audience, message, dimensions, and requested date | Required fields and helpful examples |

85| Brand guidelines | An approved document in your team's shared drive | Design guidance and links to approved assets |

86| Current request list | A spreadsheet with request, owner, reviewer, status, and delivery date | Starting records, if you want to bring existing work into the portal |

87 

88Decide who submits requests, who assigns the work, and who approves the result. Include those roles in your prompt so the app reflects how your team works. Keep a requested date separate from the delivery date the creative team agrees to.

89 

90### Example: A webinar graphic

91 

92In this hypothetical example, a marketer needs a 1200 × 628 graphic for a webinar aimed at operations leaders. The brief includes the approved headline, registration link, reviewer, and requested delivery date. A designer accepts the request, agrees on a date, and adds a draft. The reviewer leaves feedback on that request, and the designer adds the final asset when it's ready.

93 

94You can use the same flow for another deliverable. Change the form fields to match the work: a presentation might need a slide count and speaker notes, while a print asset might need print dimensions and a production deadline.

95 

96## Connect your sources

97 

98 

99 

100 

1011. Attach the brief template and a small request export to the build conversation. To use a document from a connected app, ask ChatGPT which connections are available, then name the app and provide the exact document title or link. Complete any connection step ChatGPT requests.

102 For example, if Google Drive is available, provide the link to your “Brand guidelines” document and “Approved assets” folder, and ask which materials it can read.

1032. Ask ChatGPT to identify the required fields, summarize the brand guidance, and show how spreadsheet columns map to request records. Resolve missing owners or unclear dates before importing records.

1043. Save briefs, status updates, and feedback in the app. Link to original copy, drafts, and finished assets. Copying a spreadsheet into the site doesn't keep it synchronized with the original.

1054. If you want visitors to retrieve current brand resources, ask for a supported read connection to the approved folder or documents. Keep the site private to your workspace and test with a teammate's own account and permissions. See [data and connections](https://developers.openai.com/codex/sites/data-and-connections#build-a-site-with-connected-data).

106 

107 

108 

109 

110If a connection isn't available, attach the brief template and any request export, and link to the original assets. Check that the intended reviewers can open the linked files. The app's request status and a task in another app are separate records unless you add an integration.

111 

112## How to use it

113 

114 

115 

116 

1171. Submit a brief a designer can use

118 

119 Use the starter prompt with your sources, then submit a fictional request. Read the resulting brief as the person doing the work: can you tell who the asset is for, what it should communicate, which copy is approved, and who will review it? If a field is too vague, ask ChatGPT for a more specific question or an example beneath it.

120 

121 Try leaving a required field blank. Check that the form explains what is missing, retains the other entries, and creates only one request when submitted.

122 

1232. Review the draft and deliver the asset

124 

125 Assign an owner and agree on a delivery date. Add a draft link, move the request to In review, and leave feedback as the requester. For the sample graphic, feedback might be: “Keep the headline and image. Make the event date easier to read at the size used in an email.” Specific feedback gives the designer a clear next action.

126 

127 Add the final asset link and mark the request Delivered. Reopen it to check that the brief, feedback, and updates remain. Someone returning later should be able to distinguish the delivered asset from an earlier draft.

128 

1293. Share the queue with the right people

130 

131 Filter the queue by owner, status, and agreed delivery date. Use it to see which work is due next and which requests need a reviewer’s response. An unassigned request or a missing agreed date should be visible so the team can resolve it.

132 

133 Test with separate requester and creative-team accounts. A requester should see their own requests; the creative team should manage the queue. Confirm that another requester can't read or change a request by opening its URL directly. Review the site's audience, then publish it for your team. Adjust the form when the same missing detail keeps coming up.

134 

135 

136 

137 

138## Take it further

139 

140### Load brand resources from a connected app

141 

142Use a supported connection to show brand guidelines each visitor can access, with links to the original documents.

143 

144 

145 

146**Prompt:**

147 

148```text

149Check which of my connected apps can read [approved brand folder or document links]. If supported, add a Brand resources section that retrieves the relevant guidelines with the visitor's own connection and links to the originals. If access is missing, identify the resource that couldn't be loaded and explain how the visitor can connect their account or request access. Keep request briefs and feedback stored in the app, and don't change the source documents. If the connection isn't available, use the supplied resource links and explain what needs manual updating.

150```

151 

152### Find overdue requests and missing feedback

153 

154Add a view that separates overdue requests, requests waiting for feedback, and requests without an agreed delivery date.

155 

156 

157 

158**Prompt:**

159 

160```text

161Add a Needs attention view for requests awaiting feedback or past their agreed delivery date. Show the request, owner, reviewer, requested date, and agreed date. Keep requests without an agreed date in a separate group. Preserve the existing access rules. Test an overdue request, one waiting for feedback, and one without an agreed date; don't send reminders automatically.

162```

Details

1---

2name: See which campaigns bring in leads

3tagline: Compare spend, lead counts, and cost per lead by campaign, channel, and week.

4summary: Turn weekly campaign exports into a dashboard where your team can

5 compare lead volume and cost, filter a change by channel, and inspect the rows

6 behind the totals.

7bestFor:

8 - Marketers comparing lead volume and cost per lead across channels.

9 - Teams reviewing weekly exports from ad platforms and CRM reports.

10 - Teams replacing a static weekly report with a dashboard people can explore.

11starterPrompt:

12 title: See which campaigns bring in leads

13 body: >-

14 Use @Sites to build a campaign dashboard for [team] from [attached

15 spreadsheet or exact source link and tab].

16 

17 

18 Reporting context:

19 

20 - Review question: [what the team wants to understand from this report].

21 

22 - Period: [start and end dates].

23 

24 - Currency: [currency].

25 

26 - Week boundary and time zone: [definition].

27 

28 - Lead definition and attribution rule: [team definition].

29 

30 - Additional source and campaign-ID mapping, if needed: [file or none].

31 

32 

33 First inspect the source and show the columns, row count, date range, and

34 sample rows. Expect one row per campaign per week with campaign ID or name,

35 channel, spend, and leads. Flag duplicates, missing values, mixed

36 currencies, and unmatched campaign IDs. Ask how to resolve ambiguous

37 mappings; don't silently drop rows or invent data.

38 

39 

40 Show total spend, total leads, and cost per lead. Add campaign, channel, and

41 week filters, a weekly trend chart, and a campaign comparison table. Let a

42 reviewer inspect the source rows behind a selected campaign's totals. Make

43 every card, chart, and table use the same filtered rows. Label currency,

44 reporting period, source, and any excluded rows. Flag a partial reporting

45 week when the supplied dates establish that it is incomplete.

46 

47 

48 Calculate cost per lead as total spend divided by total leads for the

49 selected rows, never the average of row-level ratios. Show N/A for zero-lead

50 ratios, distinguish missing values from zero, and show an empty state when

51 no rows match. Keep different currencies separate unless I supply a

52 conversion rule.

53 

54 

55 Start with a saved copy of the supplied data. Explain how to replace it with

56 a new export without duplicating overlapping weeks. Don't describe it as

57 live unless a supported connection is configured and tested. Don't edit

58 source reports or campaign budgets, or infer revenue or lead quality from

59 lead counts.

60 

61 

62 Check totals against the source. Use a separate, labeled sample to test that

63 $1,000 with 25 leads plus $500 with five leads gives $1,500 spend, 30 leads,

64 and $50 cost per lead; don't add these sample rows to the real report. Also

65 test zero leads, missing values, duplicate rows, and combined filters. Check

66 the mobile layout. Keep the dashboard private to our workspace and show a

67 saved preview, validation results, and unresolved data questions before

68 publishing.

69 suggestedEffort: medium

70relatedLinks:

71 - label: Connect a data source

72 url: /codex/sites/data-and-connections#build-a-site-with-connected-data

73 - label: Review before sharing

74 url: /codex/sites/publishing-and-sharing#review-before-you-share

75---

76 

77> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

78 

79## Before you start

80 

81Turn weekly campaign exports into a dashboard your team can explore during a review. Show spend, lead volume, and cost per lead together so colleagues can compare campaigns, narrow to a channel or week, and open the rows behind a result.

82 

83Choose the question the dashboard should help answer. You might want to find campaigns where spend increased without a similar increase in leads, or see whether a change came from one campaign or an entire channel. That question helps you choose useful filters and comparisons.

84 

85Export a spreadsheet for the period you want to review. Use one row per campaign per week and include a stable campaign ID if names can change.

86 

87| Field | Sample value | Check before building |

88| -------------------- | ----------------------- | ------------------------------------------------------------- |

89| Week start | 2026-10-05 | All sources use the same week boundary and time zone |

90| Campaign ID and name | SEARCH-01, Product demo | The ID identifies the same campaign in each source |

91| Channel | Paid search | Channel names are consistent |

92| Spend and currency | 1000, USD | Values use one currency or remain in separate currency groups |

93| Leads | 25 | Every campaign uses the same lead definition |

94 

95Supply your team's lead definition and the reporting period. If spend comes from an advertising report and leads come from a customer relationship management (CRM) report, also provide the agreed campaign mapping and attribution rule. Without these, the site can't reliably connect the two reports.

96 

97## Connect your sources

98 

99 

100 

101 

1021. Attach a CSV or spreadsheet in the build conversation. For a sheet in a connected app, ask ChatGPT which connections can read it, then provide the file link, tab name, and relevant range or column names. Complete any connection step it requests.

103 For example, name the “Weekly campaign results” file in Google Drive, the “Campaigns” tab, and columns A–F, if an available connection can read that sheet. Otherwise, export the tab as a CSV and attach it.

1042. Ask ChatGPT to show the columns, reporting dates, row count, and a few sample rows before building. Identify blank values, duplicate campaign-week rows, and unmatched campaign IDs. Confirm how to handle them.

1053. Start with a saved copy of the export. Label it with its source and reporting period. To update it, provide a fresh file in the same conversation and say whether it replaces the dataset or adds new weeks. Check for overlap before combining exports.

1064. For data that loads when someone opens the site, ask ChatGPT to verify a supported read connection to the exact sheet or reporting source. Keep the site private to your workspace and test with a teammate's connection. See [data and connections](https://developers.openai.com/codex/sites/data-and-connections#build-a-site-with-connected-data).

107 

108 

109 

110 

111A source link or uploaded export doesn't create a live connection. For a connected version, request a manual Refresh action and a visible last-successful-refresh time. If access fails, show an error or previous results labeled with their refresh time rather than an empty report that looks like zero activity.

112 

113## How to use it

114 

115 

116 

117 

1181. Reconcile the totals with your export

119 

120 Use the starter prompt, then compare unfiltered totals with the source. Check a small selection yourself before relying on the summary cards. For example, these hypothetical campaigns illustrate how the dashboard should combine results:

121 

122 | Sample campaign | Spend | Leads | Cost per lead |

123 | -------------------- | ------ | ----- | ------------- |

124 | Product demo: Search | $1,000 | 25 | $40 |

125 | Product demo: Social | $500 | 5 | $100 |

126 | Combined | $1,500 | 30 | $50 |

127 

128 Combined cost per lead is $1,500 divided by 30 leads, or $50. Averaging $40 and $100 would incorrectly give $70 because the campaigns generated different numbers of leads. Ask ChatGPT to show how it calculates the total, then check it against your source rows.

129 

130 Test a zero-lead row, a missing value, and a filter with no matching rows. The cards, chart, and table should all use the same selection and distinguish missing data from zero.

131 

1322. Compare volume, cost, and weekly changes

133 

134 Choose the same reporting period for the campaigns you want to compare. Look at lead counts alongside cost per lead: a campaign can have a low unit cost while contributing only a small share of total leads. Then filter to a channel and inspect the weekly trend to find where a change began.

135 

136 Use the campaign table to identify what needs a closer look. If spend rose but leads stayed flat, check the source report and campaign changes for that period. If one week looks unusually low, first check whether the week is complete and all rows were included. Lead counts alone don't establish lead quality, revenue, or what caused a change.

137 

1383. Bring current data to the weekly review

139 

140 Review the audience and publish the dashboard for your team. Before each meeting, replace the export or refresh the connected source, then verify the reporting period, totals, and any excluded rows. In the meeting, filter to a result someone has a question about and trace it back to the source. Leave with a specific follow-up, such as checking a campaign's lead definition or reviewing a recent creative change. Keep budget changes outside the dashboard's reporting workflow.

141 

142 

143 

144 

145## Take it further

146 

147### Refresh from a connected spreadsheet

148 

149Replace repeated file uploads with a supported read connection that shows when the dashboard last refreshed successfully.

150 

151 

152 

153**Prompt:**

154 

155```text

156Check whether an available connection can read [spreadsheet link], tab [tab name], with columns [column names]. If supported, add a manual Refresh action using each visitor's own connection. Validate the new rows before replacing displayed results, preserve the current filters, and show the source, reporting period, and last successful refresh. Handle denied access and missing columns visibly. Keep this read-only; don't edit the sheet or change campaign budgets. If unsupported, retain the file-based update workflow.

157```

158 

159### Compare consecutive weeks

160 

161Show what changed from the previous calendar week, using consistent filters and totals for both periods.

162 

163 

164 

165**Prompt:**

166 

167```text

168Compare the selected week with the previous calendar week for spend, leads, and cost per lead, using the same campaign and channel filters. Show absolute changes and the two date ranges. Calculate each period's cost per lead from its total spend and leads. Label missing prior-week data and undefined ratios as unavailable rather than zero. Check the results against a small sample before saving the update.

169```

Details

1---

2name: Investigate an incident and test a fix

3tagline: Turn an incident packet into a reproduced bug, a focused patch, and a

4 reviewable handoff.

5summary: Give Codex Cloud a sanitized incident packet and a working test

6 environment. Investigate the evidence, reproduce a supported code defect, and

7 return a focused patch with regression-test evidence and a deployment and

8 rollback handoff.

9bestFor:

10 - Small engineering teams investigating a stabilized incident.

11 - Application defects with sanitized evidence and safe reproduction inputs.

12 - Teams reviewing a focused fix in a working, isolated test environment.

13starterPrompt:

14 title: Investigate an incident

15 body: >-

16 Investigate [incident ID] in [repository/service] using [path to sanitized

17 incident packet]. Work in the published cloud environment selected for this

18 task.

19 

20 

21 The packet identifies the incident time window, affected release SHA,

22 expected behavior, observed behavior, relevant log and trace excerpts, and

23 safe reproduction inputs. Read AGENTS.md and the repository's setup and

24 testing instructions first. Check that the checkout matches the affected

25 release, or explain the difference before drawing conclusions.

26 

27 

28 Build a short timeline with file and line references to the packet and

29 source code. Separate observations, hypotheses, and evidence gaps. Test the

30 strongest hypothesis in the isolated test environment. If you cannot

31 reproduce the failure or lack decisive evidence, return an investigation

32 with the next diagnostic step; do not claim a confirmed root cause or make a

33 speculative fix.

34 

35 

36 If the evidence supports a code defect, add a regression test and run it

37 against the original code to demonstrate the failure. Make the smallest fix,

38 rerun that test, then run the relevant broader tests, lint, and type checks

39 available in this repository. Record the commands, exit codes, and any

40 checks you could not run.

41 

42 

43 Prepare a branch diff, a draft pull-request description, test-evidence.md,

44 and a deployment and rollback handoff. Include the supported cause,

45 unresolved uncertainty, and behavior changed. Use one evidence row per

46 criterion: revision and environment versions, baseline command/exit/log,

47 final command/exit/log, actual result, and pass/fail/blocked/not-run status

48 with limitations. Keep the PR description as a local draft for review.

49 

50 

51 For this duplicate-webhook example, name the before-commit and after-commit

52 failure points, prove duplicate contention with independent database

53 connections, and state isolation, receipt-retention, and mixed-version

54 rollout assumptions.

55 

56 

57 For this duplicate-webhook example, before proposing that retained

58 deliveries resume, identify legacy orders without matching receipts after

59 old consumers have drained. Hold affected event keys using the existing

60 queue or quarantine procedure. Add a seeded orphan-with-pending-retry test

61 that proves no second order or success acknowledgment occurs while held.

62 Reconcile only from trusted immutable event identity, canonical payload, and

63 committed-order evidence; keep missing or conflicting evidence quarantined

64 for owner review. Do not invent fingerprints, delete receipts, or replay an

65 unresolved legacy event.

66 

67 

68 Use only the supplied packet and repository unless I identify another

69 approved source. Access only the explicitly approved read-only monitoring

70 source, if supplied. Write code and task artifacts only in this repository;

71 use only its isolated test database. Do not connect directly to production

72 application or database hosts, or modify production systems. Do not replay

73 production events, modify live data or monitoring, create or post external

74 updates, merge, or deploy.

75relatedLinks:

76 - label: Cloud environments

77 url: https://learn.chatgpt.com/docs/environments/cloud-environments

78---

79 

80> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

81 

82Give Codex one bounded incident to investigate in a prepared cloud environment. The goal is a result an engineer can review: a supported explanation, a regression test that fails before the fix, a focused patch, and evidence from the tests that ran.

83 

84This works well after the immediate incident has been stabilized and the remaining job is to understand and fix an application defect. If the available evidence cannot establish the cause, a useful result is a clear account of the investigation and the next diagnostic step.

85 

86## Prepare the environment and incident packet

87 

881. In the web or desktop app, choose **Work in** > **Cloud** and select a published environment.

89 

902. To create one, select **Create environment**, choose the GitHub repositories, and ask Codex to prepare the runtime, dependencies, and isolated test services.

91 

923. Review the setup report, resolve failed checks, and **Publish** before starting a new task.

93 

94- Follow the [current Cloud environment guide](https://learn.chatgpt.com/docs/environments/cloud-environments).

95- Keep repository guidance in [AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md).

96 

97If dependencies change or service setup changes, edit the reusable environment, test it, and **Republish**; then validate the updated environment in a new task.

98 

99Repository refresh does not rerun install/start commands, and existing tasks retain their own state.

100 

101Configure required network destinations and test credentials separately; allowing a destination does not grant service access.

102 

103Confirm that the affected checkout, dependencies, checks, and disposable test database work in the new task. Record the starting commit and test baseline.

104 

105Keep the initial investigation self-contained. Place a sanitized packet in the repository or make its files available to the task. An engineer should assemble and check it before starting:

106 

107| Input | What to include |

108| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |

109| Incident summary | Incident ID, UTC time window, expected and observed behavior, known impact, and current status. Distinguish confirmed counts from estimates. |

110| Release identity | The deployed commit SHA, deployment time, relevant configuration values without secrets, and the last known good release if available. |

111| Correlated evidence | Log and trace excerpts with timestamps, event IDs, request IDs, trace IDs, and file references. Preserve identifiers consistently when sanitizing. |

112| Safe reproduction | Test request bodies, relevant fixtures, and instructions for exercising the suspected path without calling live services. |

113| Repository guidance | Relevant paths, database engine, test commands, permitted changes, and the engineer who will review the result. |

114 

115Do not include access tokens, customer payloads, or personal data in the packet.

116 

117A monitoring integration is optional. If you use one, grant read-only access to the relevant project and time window; the file-based workflow does not require it. Repository instructions should keep test credentials separate from production configuration.

118 

119## Run the starter prompt

120 

121Use the starter prompt on this page and replace its placeholders with your incident ID, repository, and packet path. The first result should connect the evidence to the affected code, explain what can be reproduced, and identify missing information.

122 

123Ask Codex to continue through implementation only when the evidence supports a code defect. Otherwise, review the investigation and provide the missing input. A plausible explanation, a recent code change, or a successful test on a different release is not enough to establish the cause.

124 

125## Example: A retried webhook creates a duplicate order

126 

127Use the [runnable incident sample](https://developers.openai.com/codex/use-cases/incident-to-tested-fix/sample.zip) to practice the investigation. It includes a prepared incident packet, a handler with a reproducible defect, the corrected handler, and PostgreSQL tests.

128 

129The service receives an `order.created` webhook, writes an order, and records that the event has been processed. During an incident, a retry appears to create a second order. The team has already stabilized the service and wants a patch for review.

130 

131The sample uses JavaScript, Node.js's test runner, and PostgreSQL. Its disposable fixture has an `event_receipts` table with non-null identity columns and a non-deferrable unique key on `(provider, account_id, event_id)`.

132 

133The schema permits a receipt claim before the order exists and retains the canonical payload fingerprint needed to detect conflicting reuse of the same event key.

134 

135If your application instead requires an order foreign key at claim time or lacks this conflict evidence, stop for a separate schema/design decision.

136 

137Record the exact runtime, dependency lock, PostgreSQL version, transaction isolation level, and receipt schema in the evidence. The downloadable fixture pins Node.js 24.19.0, PostgreSQL 17.6, and `pg` 8.23.1.

138 

139For the supplied Compose setup, prepare an environment with a working Docker daemon and verify `docker info` before publishing it. Its database accepts only public test credentials and is bound to the local loopback interface. Use your application's database engine and relevant settings when adapting the test.

140 

141This example assumes PostgreSQL READ COMMITTED, stable provider event IDs, and receipt retention covering the provider's retry/replay window.

142 

143The fixed handler places receipt and order writes in one database transaction. The fixture provisions a disposable database and a `delivery_holds` table to model an existing queue's quarantine procedure; that table is test infrastructure, not a proposed application migration.

144 

145The application fix adds no queue, database service, schema migration, or monitoring integration. If those assumptions do not hold, stop and revise the scope.

146 

147### Run the sample

148 

149Extract the ZIP, open its directory, and use Docker with Compose:

150 

151```bash

152docker compose -p codex-incident-rehearsal up -d --wait postgres

153docker compose -p codex-incident-rehearsal run --rm runner node main.mjs baseline

154docker compose -p codex-incident-rehearsal run --rm runner node main.mjs regression

155docker compose -p codex-incident-rehearsal run --rm runner

156docker compose -p codex-incident-rehearsal down --volumes

157```

158 

159The baseline command must exit with code `1`: the retry assertion observes two orders and one receipt instead of one of each. The regression command runs the same assertion against the corrected handler and must exit with code `0`.

160 

161The final test command exercises the full matrix, including independent connections, database lock contention, and legacy-orphan quarantine.

162 

163If your installation provides `docker-compose`, use that spelling in place of `docker compose`. Run these commands sequentially; the suite resets only its marked test schema.

164 

165### Inspect the transaction handler

166 

167This excerpt comes from `main.mjs` in the download. Run the complete files after extracting the ZIP; the tests and fixture setup are included there.

168 

169<details>

170 <summary>Receipt claim and order transaction</summary>

171```javascript

172async function fixed(client, input, hook = async (_boundary) => {}) {

173 await assertNotHeld(client, input);

174 await hook("beforeClaim");

175 await client.query("BEGIN ISOLATION LEVEL READ COMMITTED");

176 let committed = false;

177 try {

178 const claimed = await client.query(

179 `INSERT INTO event_receipts(provider, account_id, event_id, payload_fingerprint)

180 VALUES ($1,$2,$3,$4)

181 ON CONFLICT (provider, account_id, event_id) DO NOTHING RETURNING event_id`,

182 [...key(input), fingerprint(input)]

183 );

184 trace("receipt-claim-result", client, input, {

185 inserted: claimed.rowCount === 1,

186 });

187 if (claimed.rowCount === 1) {

188 await hook("afterClaim");

189 await writeOrder(client, input);

190 await hook("afterOrder");

191 } else {

192 // A new READ COMMITTED statement sees the winner after its commit.

193 await checkReceipt(client, input);

194 }

195 await client.query("COMMIT");

196 committed = true;

197 trace("transaction-committed", client, input);

198 await hook("afterCommit");

199 trace("acknowledged", client, input, acknowledgment);

200 return acknowledgment;

201 } catch (error) {

202 if (!committed) {

203 await client.query("ROLLBACK");

204 trace("transaction-rolled-back-no-ack", client, input);

205 } else {

206 trace("committed-response-lost-no-ack", client, input);

207 }

208 throw error;

209 }

210}

211```

212 

213</details>

214 

215### 1. Provide a compact packet

216 

217Use these files as the packet outline:

218 

219```text

220incidents/INC-DEMO-014/incident.md

221incidents/INC-DEMO-014/logs.ndjson

222incidents/INC-DEMO-014/release.json

223incidents/INC-DEMO-014/order-created.sanitized.json

224```

225 

226In this scenario, `release.json` identifies the example release SHA `4a1c9e7d2b6f8a0c3d5e7f9012a4b6c8d0e2f413`, deployed at `2026-09-22T11:58:00Z`. The incident window is `12:03:00Z–12:05:00Z` on the same day. The packet reports two internal orders associated with one provider event. When preparing your own packet, include confirmed impact counts and identify any estimates.

227 

228The sanitized logs contain these correlated observations:

229 

230| UTC time | Correlation | Observation |

231| ------------ | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |

232| 12:03:04.110 | Event `evt_demo_042`, account `acct_demo_01`, request `req_demo_a`, trace `trace_demo_a` | First delivery enters the webhook handler. |

233| 12:03:04.145 | Same event and first trace | Order `ord_demo_101` is committed. |

234| 12:03:04.150 | Same worker | Worker exits before a receipt-write success or response is recorded. The excerpt alone does not prove the receipt is absent. |

235| 12:03:14.203 | Same provider/account/event, request `req_demo_b`, trace `trace_demo_b` | A retry enters the handler. The handler logs that no matching receipt was found. |

236| 12:03:14.241 | Same event and second trace | Order `ord_demo_102` is committed and a receipt is recorded. |

237 

238Keep a stable event ID across deliveries and distinct request and trace IDs per delivery. A per-request identifier would hide the relationship between the retry and the original event.

239 

240### 2. Trace the evidence and test the hypothesis

241 

242The incident report proposes that a retry storm caused the duplicate. Codex should treat that as a hypothesis. Two observed deliveries do not establish a storm, and retries are a normal condition a webhook handler must tolerate.

243 

244In the sample’s affected implementation, source inspection reveals this sequence: look up a receipt, commit the order, then write the receipt in a separate transaction. The gap between the two commits is a stronger explanation. A worker failure in that gap can leave an order without a receipt, so a retry can create another order. Concurrent deliveries can also both pass a separate check before either writes its receipt.

245 

246Have Codex trace the actual handler and transaction boundaries and cite the relevant lines. It should confirm the packet's release matches the code under test, or explain why testing a different commit limits the conclusion.

247 

248### 3. Reproduce before changing the handler

249 

250Add a regression test using the sample webhook and the isolated PostgreSQL database. Put a test-only failure hook immediately after the order write and before the receipt-write step in the original handler.

251 

252Confirm from the real transaction boundaries that the original order has committed at that point. Retry the same provider/account/event and assert exactly one order and one matching receipt; the seeded implementation should fail with two orders.

253 

254After the fix, exercise the corresponding hook after the order statement but before the shared transaction commits; the failed attempt must leave zero orders and zero receipts.

255 

256Retry and assert one of each. Keep the same business assertion across revisions and record the hook location on each revision.

257 

258Run the new test on the original implementation first. For the seeded defect in this sample, the expected failure is two orders instead of one. Save the command, starting commit, exit code, and assertion output. If that failure does not occur, investigate the mismatch before editing the handler.

259 

260An injected exception exercises the application's transaction behavior. Record that it does not reproduce every possible host, network, or database failure.

261 

262### 4. Make the smallest supported fix

263 

264For this specific defect, claim the event receipt and write the order within one transaction on one database connection. Use the existing unique key with `INSERT ... ON CONFLICT (provider, account_id, event_id) DO NOTHING RETURNING ...`.

265 

266Only the delivery that inserts the receipt may write the order; acknowledge success only after commit. A failed transaction rolls back both writes. Do not catch a uniqueness error and continue using an aborted transaction.

267 

268A competing delivery may wait for the first transaction. If the first commits, the second claim inserts nothing: issue a fresh SELECT under READ COMMITTED, verify the stored event identity/payload policy, and return the existing duplicate acknowledgment without writing an order.

269 

270If the first transaction rolls back, the competitor can claim and process the event. A missing receipt after a skipped insert, an identity conflict, or a transient database error must follow the documented retry/error path, not a fabricated success.

271 

272Under stricter isolation, serialization failures require the repository's bounded whole-transaction retry policy. See PostgreSQL's [INSERT conflict behavior](https://www.postgresql.org/docs/current/sql-insert.html) and [transaction isolation](https://www.postgresql.org/docs/current/transaction-iso.html).

273 

274Neither an in-memory set nor a separate “check then insert” protects against concurrent workers or restarts. Scope the key to the provider's event-identity contract. If a supposedly identical event carries conflicting identity or payload information, surface that inconsistency rather than silently treating it as a valid duplicate.

275 

276The demo's business mutation is entirely within the same database. If the real handler also sends email, charges a card, or calls another service, this transaction alone does not make those external effects idempotent. Identify that boundary and the system's existing delivery or idempotency mechanism before expanding the fix.

277 

278### 5. Verify the changed behavior

279 

280Ask for both the original failure and the post-fix result. Use test-only hooks at named transaction boundaries and inspect committed state from a separate connection. The sample passes eleven local PostgreSQL tests; `test-evidence.md` in the download records those results and the checks still needed for Cloud execution and deployment. Run the suite in your published environment and test actual connection loss during commit before relying on that recovery path.

281 

282| Check / failure point | Required behavior |

283| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

284| First delivery and committed duplicate | The first creates one order and one receipt; a repeated event returns the agreed acknowledgment and leaves counts unchanged. Record exact status/body. |

285| Before receipt claim | Failure creates neither row and returns no success acknowledgment. A retry creates one of each. |

286| After claim, before order write | Failure rolls back the receipt; no order exists. A retry can claim and process normally. |

287| After order write, before commit | Failure rolls back both rows. This is the fixed-code counterpart of the original committed-order/missing-receipt defect; a retry leaves one of each. |

288| After commit, before acknowledgment | Simulate a lost response. Both rows remain committed. A retry returns the duplicate acknowledgment without another order. |

289| Commit outcome unknown | Model a connection loss around commit separately from an ordinary exception. Return no unsupported success claim; a retry with the same stable event key must be safe whether the first transaction committed or rolled back. If this transport case was not exercised, mark it not run. |

290| Concurrent duplicate: first transaction commits | Two independent connections/workers contend on the same event key. Hold the winner after claim; start the competitor before releasing it. Verify the competitor actually contends, then release the winner. Both complete through the expected acknowledgment path; exactly one order and one receipt exist. |

291| Concurrent duplicate: first transaction rolls back | Repeat the controlled overlap, but fail the claimant before commit. The competitor claims after rollback and creates one order and one receipt. Check both responses and final state. |

292| Event identity and payload conflict | Different events and the same event ID under different accounts/providers remain independent. For an event with the same scoped ID but a conflicting canonical payload, follow the agreed conflict path; do not create a second order or silently acknowledge different work. |

293| Legacy order without receipt and pending retry | Seed a committed legacy order without a receipt. Identify and hold its event key before retries resume. A held retry returns no success acknowledgment and creates no row. Trusted reconciliation permits the duplicate acknowledgment with one existing order and one receipt; missing or conflicting evidence keeps the key held. |

294| Broader service checks | Run relevant integration tests, lint, and type checking. Separate new failures, pre-existing failures, and checks that could not run. |

295 

296Coordinate concurrency with barriers/latches and bounded timeouts, not sleeps or a bare pair of promises. Release blocked workers in test cleanup and use disposable fixtures. An exception test alone does not prove abrupt process death, transport loss, or production concurrency behavior.

297 

298The sample uses `node main.mjs baseline`, `node main.mjs regression`, and `node main.mjs` inside the pinned runner. Its structured output records transaction boundaries, committed row counts, acknowledgment status/body, and observed blocking backend IDs.

299 

300When adapting the workflow, use the commands actually defined by your repository and report their real results. A skipped check or unavailable dependency belongs in the handoff, with its impact on confidence.

301 

302## Review the handoff

303 

304### Shared evidence record

305 

306Use the incident workflow's evidence format for each acceptance criterion. Populate it from observed output; the fields below are a template, not passing results. For a new test that does not exist at baseline, distinguish “not yet present” from a failed existing check.

307 

308```text

309Criterion / test: [specific behavior and test location]

310Environment: [published setup reference; runtime, database and dependency versions]

311Before: [revision + test-only diff ID; command; exit code; observed assertion; log path]

312After: [final revision or base + diff ID; command; exit code; observed assertion; log path]

313Status: [pass / fail / blocked / not run]

314Limits / reviewer action: [untested behavior, blocker, or decision required]

315```

316 

317Keep red and green runs tied to their actual tested states, including test-only changes on the original revision. Keep the logs, response fixtures, and final diff with the handoff. A changed final revision needs updated evidence; checks that were not run or were blocked cannot count as passing.

318 

319Ask Codex to leave the code diff and these review artifacts in the repository:

320 

321- `incident-investigation.md`: timeline, evidence references, hypotheses tested, supported cause, and remaining uncertainty.

322- `test-evidence.md`: baseline commit, failing regression output, fix commit or diff, passing targeted checks, broader checks, and limitations.

323- `pr-draft.md`: a concise explanation of the defect, changed behavior, test evidence, and remaining risk.

324- `deployment-handoff.md`: verified schema/isolation/receipt-retention assumptions, named rollout owner, pre-deployment checks, duplicate-order and acknowledgment/error metrics, and the rollout/rollback gates below.

325 

326Before accepting the patch, verify that the original code fails the new test, the fix passes it, and the diff addresses the demonstrated defect. In the webhook example, inspect event-key scope, transaction boundaries, concurrency coverage, and any effects outside the transaction. Review existing duplicate orders separately; preventing new duplicates does not repair historical records.

327 

328### Plan deployment and rollback

329 

330A code patch protects only workers running the corrected handler. Old workers can still write an order before claiming a receipt, so a mixed-version canary is not evidence that the defect is eliminated.

331 

332The deployment owner must choose either a tested mixed-version strategy or an existing runbook that pauses ingestion, drains in-flight old workers, updates every consumer, and resumes retained deliveries without loss.

333 

334Confirm the provider/queue retry and retention behavior before choosing the latter; do not assume pausing is lossless.

335 

336Before resuming retained deliveries, identify committed legacy orders with no matching receipt. Pausing and draining old consumers prevents new legacy writes; it does not repair those existing orders. Hold every affected event key with the existing queue or quarantine procedure. If legacy records cannot establish a complete event identity, keep the affected deliveries or those from the affected time window paused for owner review rather than declaring the scan complete.

337 

338Reconcile a held key only when trusted immutable provider/account/event identity, canonical payload evidence, and the committed business record establish what already happened.

339 

340Under the approved reconciliation procedure, record the matching receipt without repeating the business mutation, then release that key's hold.

341 

342Keep affected keys quarantined for owner review when evidence is missing, payloads conflict, or multiple business records exist. A retry payload cannot substitute for trusted evidence of the original event when reconstructing its fingerprint.

343 

344The pre-resume gate passes only when each affected key is reconciled or its hold remains enforced across all consumers.

345 

346Seed a legacy order and its pending retry in staging. Verify the hold prevents another order and any success acknowledgment; after trusted reconciliation, the retry may receive the duplicate acknowledgment while the existing order count stays at one. Rehearse an unresolved case too: it must remain held without a fabricated receipt. Keep this gate in the deployment handoff alongside pause/drain, consumer-version, retention, and rollback checks.

347 

348Before rollout, verify the existing unique constraint is present and valid, the receipt key/payload policy is consistent across all consumers, receipt cleanup cannot reopen the replay window, and external effects remain outside this patch.

349 

350In staging, rehearse the selected sequence and failed-worker retry. Record the owner's observation window and stop thresholds for duplicate orders, webhook errors, acknowledgment latency, lock waits, and retry backlog; do not invent production thresholds.

351 

352Reverting to the old handler can reintroduce duplicate orders. The rollback handoff must name the containment step (for example, the approved pause/drain procedure), the safe revision, and the owner who decides when processing can resume. Preserve receipts; do not delete them to unblock retries. Historical duplicate orders require a separately reviewed remediation.

353 

354The engineer owns the deployment decision and any production remediation. A passing isolated test is evidence for review, not proof that production is healthy. Create the pull request, run your normal CI and review process, and deploy through your team's established controls after reviewing the draft.

355 

356## Continue in the same task

357 

358Use a focused follow-up to resolve uncertainty or improve the handoff:

359 

360```text

361Review the patch against two concurrent deliveries and a retry after a worker failure. Show which behaviors the tests establish and which remain untested. Keep the change limited to this handler and its regression tests; update the handoff with any remaining risk.

362```

363 

364If the first run could not reproduce the incident:

365 

366```text

367Stop implementation. List the hypotheses the evidence rules out, the strongest remaining hypothesis, and the smallest additional sanitized input or isolated experiment needed to distinguish it. Keep the root cause unconfirmed.

368```

use-cases/issue-to-tested-pr.md +1003 −0 created

Details

1---

2name: Turn an issue into a tested pull request

3tagline: Hand off a small feature with acceptance criteria and review the evidence.

4summary: Give Codex a scoped issue, a prepared cloud environment, and explicit

5 checks. Get an implementation, tests, documentation, and a reviewable change

6 with evidence for each acceptance criterion.

7bestFor:

8 - Startup engineers implementing a scoped API issue.

9 - Reviewers who need implementation and validation evidence together.

10 - Teams with acceptance criteria and a working test environment.

11starterPrompt:

12 title: Implement a scoped issue

13 body: >-

14 Implement [issue ID and title] in [repository] at [base branch and commit],

15 

16 using the prepared cloud environment I selected for this task.

17 

18 

19 Input packet: [paste or attach the issue, acceptance criteria, relevant

20 

21 files, public success/error schemas, authorization and pagination contract,

22 

23 test fixtures, and approved validation commands].

24 

25 Repository instructions: [AGENTS.md and any relevant nested instructions].

26 

27 Scope: [files or component boundaries].

28 

29 Out of scope: [explicit exclusions].

30 

31 Required checks: [focused tests, relevant regression tests, type check,

32 

33 lint, and any required CI checks].

34 

35 Output: [create a draft PR in this repository if the available integration

36 

37 supports it and I authorize it here / return a diff and draft PR

38 description].

39 

40 

41 First read the instructions and relevant code. Confirm the starting commit

42 

43 and run the existing baseline checks using the execution tools available

44 

45 in this environment. Record any new test files as still to be created.

46 

47 If the baseline fails, access is missing, or a

48 

49 required check cannot run, report the exact blocker before implementing.

50 

51 

52 State a short plan, then implement the smallest change that meets the

53 

54 acceptance criteria. Follow existing auth, error, pagination, and testing

55 

56 patterns. Add tests that distinguish the required behavior from the old

57 

58 behavior. For this pagination example, require tied timestamps across a

59 

60 page boundary, bounded traversal and replay on fixed fixtures, partial and

61 

62 exact-full final pages, authorization before cursor validation, invalid

63 

64 cursor cases, and success/error schema checks. Update the affected API

65 

66 documentation to match the implemented public response exactly.

67 

68 

69 After implementation, run the required checks again. For every acceptance

70 

71 criterion, point to the test or other evidence that verifies it. Record

72 

73 commands, results, the tested commit or working-tree state, and any checks

74 

75 that were not run. Separate pre-existing failures from new failures.

76 

77 Never describe an unrun check as passing.

78 

79 

80 Deliver the diff or authorized draft PR, a concise change summary, the

81 

82 acceptance-criteria evidence table, remaining risks, and reviewer actions.

83 

84 Do not merge, deploy, use production data, change permissions, add new

85 

86 dependencies, or expand the agreed scope. Stop for a decision if the

87 

88 contract conflicts with the repository or the work needs a schema change.

89relatedLinks:

90 - label: Download the runnable sample

91 url: /codex/use-cases/issue-to-tested-pr/sample.zip

92 - label: Cloud environments

93 url: https://learn.chatgpt.com/docs/environments/cloud-environments

94 - label: Keep documentation up-to-date

95 url: https://learn.chatgpt.com/use-cases/update-documentation

96---

97 

98> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

99 

100Hand Codex a small, well-defined issue and ask for a change a teammate can review: implementation, tests, documentation, and evidence that the requested behavior works. Keep the task in one prepared cloud environment so the code and validation use the same repository setup.

101 

1021. In the web or desktop app, choose **Work in** > **Cloud** and select a published environment.

103 

1042. To create one, select **Create environment**, choose the GitHub repositories, and ask Codex to prepare the runtime, dependencies, and isolated test services.

105 

1063. Review the setup report, resolve failed checks, and **Publish** before starting a new task.

107 

108- Follow the [current Cloud environment guide](https://learn.chatgpt.com/docs/environments/cloud-environments).

109- Keep repository guidance in [AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md).

110 

111If dependencies change or service setup changes, edit the reusable environment, test it, and **Republish**, then verify the changes in a new task.

112 

113Repository refresh does not rerun install/start commands, and existing tasks keep their own state.

114 

115Configure required network destinations and test credentials separately; allowing a destination does not grant service access.

116 

117If required checks cannot run, finish setup before using this workflow.

118 

119## When to use it

120 

121Choose a feature with a written contract and a clear stopping point: a new API endpoint, a validation rule, or a small addition to an existing service. The issue should fit in one pull request and have checks that can run without production access.

122 

123The example adds pagination to a project's activity feed. The runnable Node.js sample includes authentication, project access, signed cursors, and an existing project-detail route. It uses fixed test records in memory, so you can check the pagination contract without a database or external service.

124 

125## Try the runnable sample

126 

127[Download the sample](https://developers.openai.com/codex/use-cases/issue-to-tested-pr/sample.zip), extract it, and open its `issue-to-tested-pr` folder. Use **Node 22.6 or later**. The sample uses the standard library and needs no package installation or API key.

128 

129```bash

130node --version

131node api.mjs

132node test.mjs --seeded

133node test.mjs

134node reproduce.mjs ./evidence

135```

136 

137`node api.mjs` starts a local HTTP server, requests the first page, prints its public IDs, and closes the server.

138 

139The `--seeded` test run deliberately exits nonzero: its timestamp-only continuation skips two events tied at the first page boundary. The same tests pass with the corrected timestamp-and-ID tuple continuation.

140 

141The reproduction command succeeds only if the seeded state fails and the corrected state passes.

142 

143The corrected traversal with `limit=2` returns these pages in three requests:

144 

145```text

146[evt_105, evt_104]

147[evt_103, evt_102]

148[evt_101]

149```

150 

151The first two pages have a continuation cursor; the final page has `next_cursor: null`.

152 

153The seeded state returns `[evt_105, evt_104]`, then `[evt_101]`, losing `evt_103` and `evt_102`.

154 

155A successful run passes all 36 tests in the corrected state. The seeded state fails three assertions that expose the pagination defect.

156 

157To verify your environment, put the files in your selected repository, prepare and publish an environment with a supported Node runtime, then run the commands in a new Cloud task and record its results.

158 

159`reproduce.mjs` saves `red.log`, `green.log`, `observed-http.json`, and `run-manifest.json` in the requested evidence directory. The manifest identifies the runtime, commands, exit codes, seeded defect, correction, and SHA-256 source hashes.

160 

161It does not record session tokens or the signing key. Read `README.md` and `openapi.json` for the sample's setup and public contract.

162 

163### Inspect the runnable sample

164 

165These excerpts come from the files in the download. Run the complete files after extracting the ZIP.

166 

167<details>

168 <summary>Cursor and ordering implementation</summary>

169```javascript

170export function createApi({

171 sessions,

172 projects,

173 events,

174 signingKey,

175 seededBug = false,

176}) {

177 return createServer((request, response) => {

178 const send = (status, body) => {

179 response.writeHead(status, { "content-type": "application/json" });

180 response.end(JSON.stringify(body));

181 };

182 const fail = (code) => {

183 const [status, message] = errors[code];

184 send(status, { error: { code, message } });

185 };

186 // Authenticate, then authorize. Malformed parameters must not change these results.

187 const authorization = request.headers.authorization ?? "";

188 const bearer = /^Bearer ([A-Za-z0-9_-]+)$/.exec(authorization);

189 const tenantId = bearer ? sessions.get(bearer[1]) : undefined;

190 if (!tenantId) return fail("unauthenticated");

191 

192 const url = new URL(request.url ?? "/", "http://127.0.0.1");

193 const route = /^\/v1\/projects\/([^/]+)(\/events)?$/.exec(url.pathname);

194 let projectId;

195 try {

196 projectId = route ? decodeURIComponent(route[1]) : undefined;

197 } catch {

198 return fail("not_found");

199 }

200 const project = projectId ? projects.get(projectId) : undefined;

201 if (

202 request.method !== "GET" ||

203 !project ||

204 project.tenant_id !== tenantId

205 ) {

206 return fail("not_found");

207 }

208 // Existing project-detail behavior remains independent of pagination parameters.

209 if (!route[2]) return send(200, { id: projectId, name: project.name });

210 

211 const rawLimit = url.searchParams.get("limit");

212 const limit = rawLimit === null ? 25 : Number(rawLimit);

213 if (

214 url.searchParams.getAll("limit").length > 1 ||

215 (rawLimit !== null && !/^\d+$/.test(rawLimit)) ||

216 !Number.isInteger(limit) ||

217 limit < 1 ||

218 limit > 100

219 ) {

220 return fail("invalid_limit");

221 }

222 let cursor;

223 if (url.searchParams.has("cursor")) {

224 try {

225 if (url.searchParams.getAll("cursor").length !== 1)

226 throw new Error("Invalid cursor");

227 cursor = decodeCursor(

228 url.searchParams.get("cursor"),

229 tenantId,

230 projectId,

231 signingKey

232 );

233 } catch {

234 return fail("invalid_cursor");

235 }

236 }

237 

238 const matching = events

239 .filter(

240 (event) =>

241 event.tenant_id === tenantId && event.project_id === projectId

242 )

243 .sort(compareEvents)

244 .filter((event) => {

245 if (!cursor) return true;

246 // Deliberate baseline defect: timestamp-only continuation loses tied rows.

247 if (seededBug)

248 return (

249 timestampValue(event.created_at) < timestampValue(cursor.created_at)

250 );

251 return compareEvents(event, cursor) > 0;

252 });

253 const selected = matching.slice(0, limit);

254 const items = selected.map(({ id, created_at, type, summary }) => ({

255 id,

256 created_at,

257 type,

258 summary,

259 }));

260 const nextCursor =

261 matching.length > limit

262 ? encodeCursor(selected.at(-1), tenantId, projectId, signingKey)

263 : null;

264 return send(200, { items, next_cursor: nextCursor });

265 });

266}

267 

268export async function startApi(fixtures, options = {}) {

269 const server = createApi({ ...fixtures, ...options });

270 await new Promise((resolve, reject) => {

271 server.once("error", reject);

272 server.listen(0, "127.0.0.1", () => resolve(undefined));

273 });

274 const address = server.address();

275 if (!address || typeof address === "string")

276 throw new Error("No listening address");

277 return {

278 baseUrl: `http://127.0.0.1:${address.port}`,

279 close: () =>

280 new Promise((resolve, reject) =>

281 server.close((error) => (error ? reject(error) : resolve(undefined)))

282 ),

283 };

284}

285```

286 

287</details>

288 

289<details>

290 <summary>Seeded records and project access fixtures</summary>

291```javascript

292export function createFixtures() {

293 const tokenA = randomBytes(24).toString("base64url");

294 const tokenB = randomBytes(24).toString("base64url");

295 const sessions = new Map([

296 [tokenA, "tenant_a"],

297 [tokenB, "tenant_b"],

298 ]);

299 const projects = new Map([

300 ["feed", { tenant_id: "tenant_a", name: "Activity feed" }],

301 ["empty", { tenant_id: "tenant_a", name: "Empty feed" }],

302 ["exact", { tenant_id: "tenant_a", name: "Four events" }],

303 ["large", { tenant_id: "tenant_a", name: "Large feed" }],

304 ["precision", { tenant_id: "tenant_a", name: "Precise timestamps" }],

305 ["other", { tenant_id: "tenant_a", name: "Another project" }],

306 ["private", { tenant_id: "tenant_b", name: "Private project" }],

307 ]);

308 const event = (id, created_at, project_id, tenant_id = "tenant_a") => ({

309 id,

310 created_at,

311 project_id,

312 tenant_id,

313 type: "deployment",

314 summary: `Synthetic event ${id}`,

315 internal_note: "This field must never enter the HTTP response.",

316 raw_payload: { synthetic: true },

317 });

318 const feed = [

319 event("evt_105", "2026-01-15T12:00:03.123456Z", "feed"),

320 event("evt_104", "2026-01-15T12:00:02.123456Z", "feed"),

321 event("evt_103", "2026-01-15T12:00:02.123456Z", "feed"),

322 event("evt_102", "2026-01-15T12:00:02.123456Z", "feed"),

323 event("evt_101", "2026-01-15T12:00:01.123456Z", "feed"),

324 ];

325 const events = [

326 ...feed,

327 ...feed.slice(0, 4).map((item) => ({ ...item, project_id: "exact" })),

328 ...Array.from({ length: 105 }, (_, index) =>

329 event(

330 `large_${String(index).padStart(3, "0")}`,

331 new Date(Date.UTC(2026, 0, 15, 12, 0, index))

332 .toISOString()

333 .replace(".000Z", ".123456Z"),

334 "large"

335 )

336 ),

337 // Reverse ID order makes timestamp precision observable in the tests.

338 event("precise_a", "2026-01-15T12:00:00.123999Z", "precision"),

339 event("precise_z", "2026-01-15T12:00:00.123001Z", "precision"),

340 event("other_1", "2026-01-15T12:00:00.123456Z", "other"),

341 event("other_2", "2026-01-15T11:59:59.123456Z", "other"),

342 event("private_1", "2026-01-15T12:00:00.123456Z", "private", "tenant_b"),

343 event("private_2", "2026-01-15T11:59:59.123456Z", "private", "tenant_b"),

344 ];

345 return {

346 sessions,

347 projects,

348 events,

349 signingKey: randomBytes(32),

350 tokenA,

351 tokenB,

352 };

353}

354```

355 

356</details>

357 

358<details>

359 <summary>HTTP contract and regression tests</summary>

360```javascript

361test("five-event traversal crosses a timestamp tie and replays the same continuation", async () => {

362 const expected = ["evt_105", "evt_104", "evt_103", "evt_102", "evt_101"];

363 const pages = await traverse("feed", 2, expected);

364 assert.deepEqual(pages.map(ids), [

365 ["evt_105", "evt_104"],

366 ["evt_103", "evt_102"],

367 ["evt_101"],

368 ]);

369 assert.equal(pages.length, 3);

370 assert.ok(pages[0].next_cursor);

371 assert.ok(pages[1].next_cursor);

372 assert.equal(pages[2].next_cursor, null);

373 const replay = await request("feed", {

374 limit: "2",

375 cursor: pages[0].next_cursor,

376 });

377 assert.deepEqual(replay.body.items, pages[1].items);

378 const next = await request("feed", {

379 limit: "2",

380 cursor: replay.body.next_cursor,

381 });

382 assert.deepEqual(next.body.items, pages[2].items);

383 assert.equal(next.body.next_cursor, null);

384 console.log(

385 JSON.stringify({

386 fixture: "five-event-tie",

387 pages: pages.map(ids),

388 requests: pages.length,

389 replay: ids(replay.body),

390 })

391 );

392});

393 

394test("the same tie group traverses correctly with limit three", async () => {

395 const pages = await traverse("feed", 3, [

396 "evt_105",

397 "evt_104",

398 "evt_103",

399 "evt_102",

400 "evt_101",

401 ]);

402 assert.deepEqual(pages.map(ids), [

403 ["evt_105", "evt_104", "evt_103"],

404 ["evt_102", "evt_101"],

405 ]);

406});

407 

408test("timestamp ordering and continuation preserve submillisecond precision", async () => {

409 const pages = await traverse("precision", 1, ["precise_a", "precise_z"]);

410 assert.equal(pages[0].items[0].created_at, "2026-01-15T12:00:00.123999Z");

411 assert.equal(pages[1].items[0].created_at, "2026-01-15T12:00:00.123001Z");

412});

413 

414test("omitted, minimum, and maximum limits return 25, 1, and 100 of 105 events", async () => {

415 for (const [parameters, expected] of [

416 [{}, 25],

417 [{ limit: "1" }, 1],

418 [{ limit: "100" }, 100],

419 ]) {

420 const result = await request("large", parameters);

421 assert.equal(result.status, 200);

422 assert.equal(result.body.items.length, expected);

423 assert.ok(result.body.next_cursor);

424 }

425 const pages = await traverse(

426 "large",

427 100,

428 Array.from(

429 { length: 105 },

430 (_, index) => `large_${String(104 - index).padStart(3, "0")}`

431 )

432 );

433 assert.deepEqual(

434 pages.map((page) => page.items.length),

435 [100, 5]

436 );

437});

438 

439test("invalid and repeated limits return the documented generic error", async (context) => {

440 for (const limit of [

441 "0",

442 "101",

443 "-1",

444 "1.5",

445 "many",

446 "",

447 "1e2",

448 "Infinity",

449 ]) {

450 await context.test(`limit ${JSON.stringify(limit)}`, async () => {

451 const result = await request("feed", { limit });

452 assert.equal(result.status, 400);

453 assert.deepEqual(

454 result.body,

455 expectedError(

456 "invalid_limit",

457 "Limit must be an integer from 1 through 100."

458 )

459 );

460 });

461 }

462 const repeated = await request("feed", [

463 ["limit", "1"],

464 ["limit", "2"],

465 ]);

466 assert.equal(repeated.status, 400);

467});

468 

469test("empty, partial, and exactly full final pages have no continuation", async () => {

470 assert.deepEqual((await request("empty")).body, {

471 items: [],

472 next_cursor: null,

473 });

474 const partial = await traverse("feed", 2, [

475 "evt_105",

476 "evt_104",

477 "evt_103",

478 "evt_102",

479 "evt_101",

480 ]);

481 assert.equal(partial.at(-1).items.length, 1);

482 const exact = await traverse("exact", 2, [

483 "evt_105",

484 "evt_104",

485 "evt_103",

486 "evt_102",

487 ]);

488 assert.deepEqual(

489 exact.map((page) => page.items.length),

490 [2, 2]

491 );

492 assert.equal(exact[1].next_cursor, null);

493});

494 

495test("authentication precedes malformed and wrong-scope cursor or limit validation", async () => {

496 const privateCursor = (

497 await request("private", { limit: "1" }, fixtures.tokenB)

498 ).body.next_cursor;

499 for (const token of [null, "invalid_session"]) {

500 for (const parameters of [

501 { cursor: "broken" },

502 { cursor: privateCursor },

503 { limit: "0", cursor: "broken" },

504 ]) {

505 const result = await request("feed", parameters, token);

506 assert.equal(result.status, 401);

507 assert.deepEqual(result.body, unauthenticated);

508 }

509 }

510});

511 

512test("inaccessible and nonexistent projects return identical 404 before cursor validation", async () => {

513 const ownCursor = (await request("feed", { limit: "1" })).body.next_cursor;

514 for (const project of ["private", "missing"]) {

515 for (const parameters of [

516 {},

517 { cursor: "broken" },

518 { cursor: ownCursor },

519 { limit: "0", cursor: "broken" },

520 ]) {

521 const result = await request(project, parameters);

522 assert.equal(result.status, 404);

523 assert.deepEqual(result.body, notFound);

524 }

525 }

526});

527 

528test("a valid cursor from another project in the same tenant is invalid on an authorized feed", async () => {

529 const cursor = (await request("other", { limit: "1" })).body.next_cursor;

530 assert.ok(cursor);

531 const result = await request("feed", { cursor });

532 assert.equal(result.status, 400);

533 assert.deepEqual(result.body, invalidCursor);

534});

535 

536test("a valid cursor from another tenant is invalid on an authorized feed", async () => {

537 const cursor = (await request("private", { limit: "1" }, fixtures.tokenB))

538 .body.next_cursor;

539 assert.ok(cursor);

540 const result = await request("feed", { cursor });

541 assert.equal(result.status, 400);

542 assert.deepEqual(result.body, invalidCursor);

543});

544 

545test("undecodable, empty, tampered, and repeated cursor tokens return the same closed error", async () => {

546 const cursor = (await request("feed", { limit: "1" })).body.next_cursor;

547 const [payload, signature] = cursor.split(".");

548 const tampered = `${payload}.${signature[0] === "A" ? "B" : "A"}${signature.slice(1)}`;

549 for (const token of [

550 "broken",

551 "",

552 "%%%.___",

553 "a.b.c",

554 "x".repeat(2049),

555 tampered,

556 ]) {

557 const result = await request("feed", { cursor: token });

558 assert.equal(result.status, 400);

559 assert.deepEqual(result.body, invalidCursor);

560 }

561 const result = await request("feed", [

562 ["cursor", cursor],

563 ["cursor", cursor],

564 ]);

565 assert.equal(result.status, 400);

566 assert.deepEqual(result.body, invalidCursor);

567});

568 

569test("signed structurally invalid payloads and unsupported versions are rejected", async (context) => {

570 const valid = {

571 version: 1,

572 tenant_id: "tenant_a",

573 project_id: "feed",

574 created_at: "2026-01-15T12:00:02.123456Z",

575 id: "evt_104",

576 };

577 const missing = { ...valid };

578 delete missing.id;

579 const cases = [

580 ["missing ID", missing],

581 ["number ID", { ...valid, id: 104 }],

582 ["empty ID", { ...valid, id: "" }],

583 ["number timestamp", { ...valid, created_at: 0 }],

584 ["invalid date", { ...valid, created_at: "2026-02-30T12:00:00Z" }],

585 ["unsupported version", { ...valid, version: 2 }],

586 ["extra field", { ...valid, extra: true }],

587 ["wrong tenant type", { ...valid, tenant_id: 1 }],

588 ["wrong project type", { ...valid, project_id: null }],

589 ["null payload", null],

590 ["array payload", []],

591 ["invalid JSON", "not-json"],

592 ];

593 for (const [name, payload] of cases) {

594 await context.test(name, async () => {

595 const result = await request("feed", { cursor: signFixture(payload) });

596 assert.equal(result.status, 400);

597 assert.deepEqual(result.body, invalidCursor);

598 });

599 }

600});

601 

602test("every serialized event omits stored tenant, project, and raw payload fields", async () => {

603 const result = await request("feed", { limit: "100" });

604 assert.equal(result.status, 200);

605 for (const item of result.body.items) {

606 assert.deepEqual(Object.keys(item).sort(), [

607 "created_at",

608 "id",

609 "summary",

610 "type",

611 ]);

612 }

613 assert.ok(

614 fixtures.events.some((event) => event.internal_note && event.raw_payload)

615 );

616});

617 

618test("the schema oracle rejects extra, missing, incorrectly typed, and malformed public fields", () => {

619 const schema = specification.components.schemas.EventPage;

620 const page = {

621 items: [

622 {

623 id: "evt_1",

624 created_at: "2026-01-15T12:00:00.123456Z",

625 type: "deployment",

626 summary: "Synthetic",

627 },

628 ],

629 next_cursor: null,

630 };

631 assert.ok(conforms(page, schema));

632 assert.equal(conforms({ ...page, tenant_id: "tenant_a" }, schema), false);

633 assert.equal(

634 conforms(

635 { ...page, items: [{ ...page.items[0], raw_payload: {} }] },

636 schema

637 ),

638 false

639 );

640 assert.equal(conforms({ items: page.items }, schema), false);

641 assert.equal(conforms({ ...page, next_cursor: "" }, schema), false);

642 assert.equal(

643 conforms({ ...page, items: [{ ...page.items[0], id: 1 }] }, schema),

644 false

645 );

646 assert.equal(

647 conforms(

648 { ...page, items: [{ ...page.items[0], created_at: "yesterday" }] },

649 schema

650 ),

651 false

652 );

653 const errorSchema = specification.components.schemas.InvalidCursor;

654 assert.equal(

655 conforms(

656 { error: { ...invalidCursor.error, stack: "internal" } },

657 errorSchema

658 ),

659 false

660 );

661 assert.equal(

662 conforms(

663 { error: { ...invalidCursor.error, message: "tenant_a" } },

664 errorSchema

665 ),

666 false

667 );

668});

669 

670test("the existing project-detail route preserves access and ignores pagination parameters", async () => {

671 const own = await request(

672 "feed",

673 { cursor: "broken", limit: "0" },

674 fixtures.tokenA,

675 true

676 );

677 assert.equal(own.status, 200);

678 assert.deepEqual(own.body, { id: "feed", name: "Activity feed" });

679 const inaccessible = await request("private", {}, fixtures.tokenA, true);

680 assert.equal(inaccessible.status, 404);

681 assert.deepEqual(inaccessible.body, notFound);

682 const unauthenticatedResult = await request("feed", {}, null, true);

683 assert.equal(unauthenticatedResult.status, 401);

684 assert.deepEqual(unauthenticatedResult.body, unauthenticated);

685});

686 

687test("OpenAPI declares operation, limit boundaries, signed cursor, and response statuses", () => {

688 assert.equal(specification.openapi, "3.1.0");

689 const operation = specification.paths["/v1/projects/{project_id}/events"].get;

690 assert.equal(operation.operationId, "listProjectEvents");

691 const limit = operation.parameters.find(

692 (parameter) => parameter.name === "limit"

693 ).schema;

694 assert.deepEqual(limit, {

695 type: "integer",

696 minimum: 1,

697 maximum: 100,

698 default: 25,

699 });

700 assert.deepEqual(Object.keys(operation.responses).sort(), [

701 "200",

702 "400",

703 "401",

704 "404",

705 ]);

706 assert.equal(

707 specification.components.securitySchemes.session.scheme,

708 "bearer"

709 );

710});

711```

712 

713</details>

714 

715<details>

716 <summary>Evidence capture</summary>

717```javascript

718mkdirSync(output, { recursive: true });

719const states = [];

720for (const [name, arguments_] of [

721 ["red", ["--seeded"]],

722 ["green", []],

723]) {

724 const result = spawnSync(

725 process.execPath,

726 [resolve(directory, "test.mjs"), ...arguments_],

727 {

728 cwd: directory,

729 encoding: "utf8",

730 timeout: 30_000,

731 }

732 );

733 writeFileSync(

734 resolve(output, `${name}.log`),

735 `${result.stdout ?? ""}${result.stderr ?? ""}`

736 );

737 states.push({

738 name,

739 command: `node test.mjs${arguments_.length ? " --seeded" : ""}`,

740 exit_code: result.status,

741 signal: result.signal,

742 error: result.error?.message ?? null,

743 });

744}

745const sources = Object.fromEntries(

746 readdirSync(directory, { withFileTypes: true })

747 .filter((entry) => entry.isFile())

748 .map((entry) => [

749 entry.name,

750 createHash("sha256")

751 .update(readFileSync(resolve(directory, entry.name)))

752 .digest("hex"),

753 ])

754);

755const observed = { red: await observe(true), green: await observe(false) };

756writeJson("observed-http.json", observed);

757writeJson("run-manifest.json", {

758 execution: "Synthetic loopback HTTP fixture; no external service",

759 recorded_at: new Date().toISOString(),

760 node: process.version,

761 minimum_node: "22.6.0",

762 dependencies: "Node standard library only; no install step",

763 baseline:

764 "Deliberately seeded timestamp-only continuation; not a historical customer defect",

765 correction:

766 "Descending timestamp-and-ID tuple continuation using full fractional precision",

767 source_sha256: sources,

768 states,

769});

770if (

771 states[0].exit_code === null ||

772 states[0].exit_code === 0 ||

773 states[1].exit_code !== 0

774) {

775 throw new Error(

776 "Expected the unchanged tests to fail on the seeded baseline and pass on the corrected implementation. Inspect red.log and green.log."

777 );

778}

779console.log(

780 JSON.stringify(

781 {

782 execution: "synthetic-loopback-http",

783 red_exit: states[0].exit_code,

784 green_exit: states[1].exit_code,

785 green_pages: observed.green.pages.map((page) =>

786 page.body.items.map((item) => item.id)

787 ),

788 evidence_directory: output,

789 },

790 null,

791 2

792 )

793);

794```

795 

796</details>

797 

798## Prepare the input packet

799 

800An issue link is useful only if Codex can read it. Paste or attach the relevant content when the source is unavailable. Include the following information with the starter prompt.

801 

802| Input | Sample issue |

803| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

804| Issue and outcome | `API-142`: Add `GET /v1/projects/{project_id}/events` so customers can page through an activity feed. |

805| Repository and base | Put the sample folder in your chosen repository, then record the branch and exact commit selected for the task. |

806| Relevant code | The sample's `api.mjs`, `fixtures.mjs`, `test.mjs`, and `openapi.json`, plus your repository's `AGENTS.md`. |

807| Existing patterns | Keep authentication and project authorization before pagination validation. Use signed cursors and the documented public error envelope. When adapting the issue, follow your repository's existing helpers. |

808| Test setup | Fixed in-memory test projects for two tenants, six tenant-A projects, a timestamp tie split across a page boundary, timestamps with fractional precision, empty and exactly full final pages, and 105 events for limit checks. |

809| Validation | `node test.mjs --seeded` (expected failure), `node test.mjs` (corrected state), and `node reproduce.mjs ./evidence`. Run `node --check` for each `.mjs` file. For your own service, add its actual type, lint, regression, and CI commands. |

810| Scope | Endpoint, focused tests, and its OpenAPI documentation. No new dependencies, database migration, frontend, deployment, or auth redesign. |

811| Handoff | A draft PR when the available repository integration supports it and you have authorized creation; otherwise a diff and draft PR description. |

812 

813For the sample, prepare Node 22.6 or later; the tests create their own test sessions and cursor-signing key and bind a temporary HTTP server to the loopback interface. For your own service, also prepare its dependencies, test configuration, and isolated services. Verify that its cursor helper can bind a cursor to the authenticated tenant and requested project before assigning this issue.

814 

815### Define the API contract

816 

817Confirm the sample contract below matches your repository before implementation. A successful response is HTTP 200 with exactly `{ items, next_cursor }`.

818 

819Each item has exactly four required public fields: `id` (nonempty string), `created_at` (UTC RFC 3339 timestamp string), `type` (nonempty string), and `summary` (string). `items` is an array of these objects; `next_cursor` is an opaque nonempty string or `null`.

820 

821Internal tenant/project keys, database metadata, raw event payloads, and credentials are not response fields. Describe response field types, required fields, whether fields allow `null`, and closed object shapes in OpenAPI. Verify the serialized HTTP bodies against that schema.

822 

823Events sort by `created_at DESC, id DESC`, preserving the timestamp's fractional precision and using descending lexical ID order. The cursor resumes strictly after the last returned tuple in that order.

824 

825The default page size is 25; valid limits are decimal integers from 1 through 100. Repeated limit or cursor parameters are invalid. Return a non-null cursor only when another matching event exists.

826 

827Both a partial final page and an exactly full final page return `next_cursor: null`; an empty authorized project returns `items: []` and `next_cursor: null`.

828 

829The sample signs each cursor and binds it to the authenticated tenant and requested project. Authenticate and authorize the requested project before validating its limit or cursor, so parameter errors cannot reveal whether another tenant's project exists.

830 

831A missing or invalid session returns 401; an inaccessible or nonexistent project returns the same generic 404. After confirming access, check the limit before the cursor.

832 

833A malformed, tampered, structurally invalid, or tenant/project-mismatched cursor returns HTTP 400 with code `invalid_cursor`. A valid cursor for another project in the same tenant, or for another tenant, is invalid when supplied to an authorized project.

834 

835A supplied cursor must not change the authorization result for an inaccessible project. The sample's version-1 cursor does not expire; unsupported versions are invalid.

836 

837In the sample, every error body has exactly `{ error: { code, message } }`, with both fields required strings and no extra fields.

838 

839Use these generic error responses:

840 

841- 400 `invalid_cursor` / `Invalid cursor.`

842- 400 `invalid_limit` / `Limit must be an integer from 1 through 100.`

843- 401 `unauthenticated` / `Authentication required.`

844- 404 `not_found` / `Project not found.`

845 

846Keep decoded cursor contents, tenant/project identifiers, raw errors, and stack traces out of every error body. Use the real repository's approved envelope and codes if they differ; resolve that contract difference before changing shared helpers.

847 

848Traversal and replay assertions use fixed fixtures with no concurrent writes: following cursors returns every event once in the agreed order and terminates within `ceil(event_count / limit)` requests for a nonempty feed, or one request for an empty feed.

849 

850Replaying a valid cursor with the same request parameters returns the same items and continuation position while those fixtures stay unchanged; it need not produce byte-identical signed tokens.

851 

852This contract does not promise single-use cursors, snapshot pagination, or identical results after feed changes. Document the repository's existing feed consistency policy; stop for a product decision if no policy exists and the issue requires snapshot behavior.

853 

854## How to use

855 

8561. **Choose the issue and prepare the environment.** Confirm that the contract above matches the actual repository. Select the prepared cloud environment and the intended base commit. Supply the issue packet directly in the task; starting this recipe does not depend on an automatic issue-tracker trigger.

8572. **Run the starter prompt.** Replace its bracketed inputs with your repository, issue packet, boundaries, and checks. Choose whether Codex should return a diff or create an authorized draft PR. The starter prompt on this page contains the full handoff instructions.

8583. **Establish the baseline.** Codex should read repository instructions and run the approved baseline checks before editing.

859 

860 For the sample, run `node test.mjs` and the syntax checks in `README.md`; `--seeded` is a separate deliberate failure demonstration.

861 

862 For your own service, run its existing regression, type, and lint checks. A new endpoint's focused test file may not exist yet; record that as a test to create. Resolve failing existing checks or unavailable test services before attributing later results to the feature.

863 

8644. **Implement and verify the contract.** Ask Codex to use the existing access and cursor helpers, implement the endpoint, add the acceptance tests below, and update its API documentation. Where practical, the new tests should fail against the original code and pass after the change. Keep those results with the final verification record. In the downloadable sample, `--seeded` selects a deliberate defect for this comparison; this comparison is separate from a failing baseline in your repository.

8655. **Review the change and its evidence.** Read the diff, acceptance checks, check outputs, and remaining risks together. If Codex creates an authorized draft PR, verify that its base and head are correct. If PR tooling is unavailable, use the returned diff and description to prepare the PR through your normal process. List any unresolved checks in the draft PR and keep it unready for merge.

8666. **Resolve review feedback and merge through your normal process.** Give Codex the specific comments to address in the same task, then rerun the affected checks. A reviewer confirms required CI and approves the final commit before merging.

867 

868## Acceptance tests to require

869 

870These tests make the issue small enough to delegate and specific enough to review. Adapt the names to your test runner, while preserving the behavior each test proves.

871 

872### Stable traversal

873 

874Seed five fixed events: `evt_105` has the newest timestamp, `evt_104`, `evt_103`, and `evt_102` share the middle timestamp, and `evt_101` has the oldest.

875 

876With `limit=2`, require exactly these pages: `[evt_105, evt_104]`, `[evt_103, evt_102]`, `[evt_101]`. The tie group must cross the first boundary.

877 

878Assert exact order, no duplicate or missing IDs, non-null cursors on the first two pages, and a null cursor on the third; fail if traversal exceeds three requests.

879 

880Replay the first continuation cursor with the same limit against unchanged fixtures and assert the same second-page items and continuation position.

881 

882### Default and boundary sizes

883 

884Seed more than 100 events. Assert 25 items without a `limit` parameter, one with `limit=1`, and 100 with `limit=100`.

885 

886### Invalid limits

887 

888Assert HTTP 400 for `0`, `101`, `-1`, a fractional value, and a string such as `many`, using the existing validation error shape.

889 

890### Empty and final pages

891 

892Assert an empty authorized project returns `items: []` and `next_cursor: null`. With `limit=2`, assert that five events produce a one-item third page with a null cursor and that four events produce a two-item second page with a null cursor. The full final page must not require an extra empty request.

893 

894### Authentication

895 

896Assert missing and invalid sessions return the agreed 401 schema, including requests with a malformed cursor or a cursor from another scope. Cursor validation must not override authentication.

897 

898### Tenant isolation

899 

900Authenticate as tenant A and request tenant B's project or a nonexistent project. Assert the same agreed generic 404 body with no event data, including when the request carries a malformed or mismatched cursor. Authorization must precede cursor validation.

901 

902### Cursor scope

903 

904Request an authorized tenant-A project with (1) a valid signed cursor from another tenant-A project and (2) a valid signed cursor issued under tenant B.

905 

906Assert both return the same HTTP 400 `invalid_cursor` body with no event data or scope details. Test these separately from requests to an inaccessible project, which must return 404.

907 

908### Malformed and tampered cursors

909 

910On an authorized project, assert HTTP 400 `invalid_cursor` for tokens the helper cannot decode, tokens with modified signatures, and signed tokens that omit required cursor fields or give those fields the wrong type. Use isolated test signing fixtures.

911 

912Cover unsupported versions or expired tokens if the shared helper supports those features. Assert that responses match the agreed generic error schema and contain no decoded contents, identifiers, raw errors, or stack traces.

913 

914### Existing behavior

915 

916The sample checks the existing project-detail route with authorized, unauthorized, and inaccessible-project requests. Run syntax checks for its `.mjs` files. In your service, run its established regression suite, type check, and lint.

917 

918### Public response schema

919 

920Validate actual HTTP 200, 400, 401, and 404 response bodies against the agreed OpenAPI schemas. Assert required keys, item field types, timestamp format, whether the cursor allows `null`, and no extra properties at any object level.

921 

922Seed internal fields in stored events and verify they are absent from serialized items; verify every error response contains only its approved generic code and message.

923 

924### Documentation

925 

926Verify the OpenAPI operation and examples match the implemented parameters, exact success/error schemas, authorization precedence, ordering, final-page rules, and fixed-fixture replay/consistency limits. Run the repository's API specification validation if one exists.

927 

928## What good output looks like

929 

930### Shared evidence record

931 

932Use this evidence format for each acceptance criterion. Populate it from observed output. For a new test that does not exist at baseline, distinguish “not yet present” from a failed existing check.

933 

934```text

935Criterion / test: [specific behavior and test location]

936Environment: [published setup reference; runtime, database and dependency versions]

937Before: [revision + test-only diff ID; command; exit code; observed assertion; log path]

938After: [final revision or base + diff ID; command; exit code; observed assertion; log path]

939Status: [pass / fail / blocked / not run]

940Limits / reviewer action: [untested behavior, blocker, or decision required]

941```

942 

943Keep red and green runs tied to their actual tested states, including test-only changes on the original revision. Keep the logs, response fixtures, and final diff with the handoff; file and test names alone do not prove execution. Update the evidence when the tested revision changes. Checks that are blocked or have not run cannot count as passing.

944 

945A useful PR description links each claim to its evidence. Use this handoff template and replace every bracketed field with observed results.

946 

947```text

948Title: Add cursor pagination for project events

949Issue: API-142

950Base: [branch and commit]

951Tested state: [head commit, or base commit plus attached diff identifier]

952 

953Changes

954- Add the project-events endpoint using the existing project access helper.

955- Reuse signed cursors scoped to tenant and project.

956- Add contract tests and update the OpenAPI operation.

957 

958Acceptance evidence

959- [One row per criterion above: test, observed result, and log reference.]

960 

961Validation

962- Before edits: [command, result, and log reference for each baseline check].

963- New tests against original code: [result, or not run with reason].

964- After edits: [command, result, and log reference for each required check].

965- CI: [check links and actual states, or not available].

966- Remaining checks: [specific checks and blockers, or none with supporting evidence].

967 

968Reviewer focus

969- [Highest-risk behavior and unresolved criteria.]

970 

971Remaining risk and rollback

972- [Observed limitations; distinguish them from untested assumptions.]

973- [How this change can be reverted and whether any callers depend on it.]

974```

975 

976## Iterate from review comments

977 

978Use a focused follow-up for a specific review finding. For example:

979 

980```text

981Address this review comment in the current change:

982"Repeat the five-event traversal with limit=3; assert pages

983[evt_105, evt_104, evt_103] and [evt_102, evt_101], then a null cursor."

984 

985Add the regression test and change the implementation only if it exposes a

986failure. Rerun the focused endpoint tests and any other required checks affected

987by the edit. Update the evidence table with the tested state and actual results.

988Keep the same scope. Do not merge or deploy.

989```

990 

991Provide the text of the comment if Codex cannot access the review directly. A reviewer should check that the response resolves the behavior in question, rather than only satisfying the wording of the comment.

992 

993## When to stop

994 

995- **The baseline is red or cannot run.** Return the failing check and setup details. Fix the environment or agree on a documented baseline before continuing.

996- **The contract conflicts with existing behavior.** Identify the conflict, affected callers, and the smallest decision needed. Do not silently change the contract.

997- **The change needs a migration, new dependency, or auth redesign.** Explain why and split or re-scope the issue before expanding it.

998- **Tests fail after implementation.** Do not treat the change as ready to merge. Report the failing cases. Do not remove assertions or weaken authorization to make checks pass.

999- **Repository write integration is unavailable.** Return the diff, draft PR description, and validation evidence. A teammate can complete the repository handoff.

1000 

1001## Reuse the workflow

1002 

1003After a reviewer accepts the first result, save the issue packet and acceptance checklist in your team's issue template. Record stable repository guidance, such as test commands and where API contracts live, in `AGENTS.md` or the existing contributor guide.

Details

1---

2name: Migrate a validation module

3tagline: Upgrade one module and prove that its public behavior still holds.

4summary: Give Codex a bounded migration brief, contract fixtures, and a prepared

5 cloud environment. Upgrade one validation module and return the focused

6 change, parity evidence, remaining differences, and rollback instructions.

7bestFor:

8 - Engineering teams moving a legacy validation component.

9 - Migrations with explicit contracts and repeatable tests.

10 - Teams needing a small reviewed change while other modules keep working.

11starterPrompt:

12 title: Migrate a validation module

13 body: >-

14 Migrate [module] in [repository] from [current pinned version] to [approved

15 target version] in this cloud environment.

16 

17 

18 Read AGENTS.md, [migration brief], [official migration guide], the

19 dependency lockfile, the module's callers, and [contract test locations].

20 Record the starting commit, environment/setup reference, installed Python

21 version, framework/Pydantic/bundled-v1 versions, and lockfile hash. Confirm

22 baseline/target compatibility; treat shared dependency/import preparation as

23 a separate reviewed change.

24 

25 

26 First run [baseline commands], inventory the migration surface, and write a

27 short plan in [plan file]. Preserve [public contracts]. Keep [out-of-scope

28 modules] unchanged. Remove obsolete code only inside the approved slice;

29 name any compatibility adapter that must remain.

30 

31 

32 If the slice can be isolated and the baseline is usable, run the same frozen

33 HTTP contract tests before and after the change through the real router,

34 error handler, and response serialization. Compare status codes, contractual

35 headers, accepted/rejected inputs, public JSON keys/types, defaults/nulls,

36 and the complete error envelope. Run affected integration, lint and type

37 checks too. If this requires broader upgrades or public behavior changes,

38 return that decision before expanding scope. Do not approve new snapshots,

39 normalize away public differences or weaken assertions. Record each check's

40 revisions/environment/versions, before/after commands, exit codes, evidence

41 paths, and pass/fail/blocked/not run with limitations.

42 

43 

44 Return the diff, before-and-after evidence, unresolved differences,

45 dependency and lockfile changes, rollback instructions, and a draft PR

46 description. Create a draft PR only if the connected repository tools and my

47 permissions support it; otherwise provide the patch and description. Do not

48 merge, deploy, change production data, or begin another slice.

49relatedLinks:

50 - label: Cloud environments

51 url: https://learn.chatgpt.com/docs/environments/cloud-environments

52 - label: Pydantic migration guide

53 url: https://docs.pydantic.dev/latest/migration/

54---

55 

56> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

57 

58## When to use it

59 

60Use this workflow when a migration has a clear boundary and tests that compare old and new behavior. Give Codex a migration brief, the module and its callers, frozen contract fixtures, and a prepared cloud environment. Ask for one focused change with evidence and a rollback path.

61 

62The sample migrates one validation model in an orders service from `Pydantic` v1 to v2. It includes the original implementation, a compatibility preparation stage, and the final module. Its tests exercise the real FastAPI router, error handler, response serialization, adjacent legacy route, and public OpenAPI document. No database migration or deployment is included.

63 

64## Prepare the cloud environment

65 

661. Choose **Work in** > **Cloud** and select a published environment.

67 

682. To create one, select **Create environment**, choose the GitHub repositories, and ask Codex to prepare the runtime, dependencies, and isolated test services.

69 

703. Review the setup report, resolve failed checks, and **Publish** before starting a new task.

71 

72- Follow the [Cloud environment guide](https://learn.chatgpt.com/docs/environments/cloud-environments).

73- Keep repository guidance in [AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md).

74 

75If dependencies change or service setup changes, edit the reusable environment, test it, and **Republish**.

76 

77Validate the updated environment in a new task. Repository refresh preserves dependency caches without rerunning installation or startup commands, and existing tasks retain their own state.

78 

79Configure required network destinations and credentials separately.

80 

81Record the environment/setup reference, source commit, Python patch version, framework and dependency versions, package-manager version, and lockfile hash at each stage. Verify those versions inside the new task before using its results.

82 

83## Run the sample

84 

85Download the [complete migration sample](https://developers.openai.com/codex/use-cases/migrate-a-validation-module/sample.zip). It includes three standalone `uv` projects, their resolved `uv.lock` files, application implementations, a shared error and schema adapter, frozen HTTP fixtures, and the contract runner.

86 

87Use Python 3.11, 3.12, or 3.13 and install [`uv`](https://docs.astral.sh/uv/getting-started/installation/), then run:

88 

89```bash

90unzip sample.zip

91cd migrate-a-validation-module

92uv run --project support/target --python python3 --frozen python run.py --compare-stages --output evidence

93```

94 

95The comparison creates a separate `.venv` for each stage and installs dependencies with `uv sync --frozen`. All stages use the invoking Python interpreter and the same HTTP and schema fixtures; dependency versions remain locked.

96 

97Python 3.14 and later are excluded because the legacy `Pydantic` v1 bridge does not support them. A successful run ends with:

98 

99```text

100PASS: legacy, prepared, target HTTP/schema/serialization contracts match

101Native model schemas differ; the existing public schema adapter is preserved.

102```

103 

104Read `evidence/legacy.json`, `prepared.json`, `target.json`, and `summary.json`. They contain observed responses, runtime versions, source, fixture, and lockfile hashes, commands, exit codes, and raw-log paths.

105 

106The runner exercises the sample application's HTTP endpoints through `FastAPI`'s in-process `TestClient`. Run it inside your chosen Cloud task and retain that task's evidence.

107 

108Run `python3 run.py` without `--compare-stages` only to check the target model in an environment with `Pydantic` v2 installed. That self-contained mode checks seven validation and serialization cases. It does not run HTTP or cross-stage parity checks. Use the full comparison command above to verify a migration.

109 

110The full comparison checks 23 HTTP cases in each stage, public OpenAPI, and response serialization. It passed locally on Python 3.11, 3.12, and 3.13. The download's `README.md` records the tested versions and execution history. Run the current comparison in your Cloud environment to record its results there.

111 

112### Keep the dependency stages separate

113 

114| Stage | Python / FastAPI / `Pydantic` | Migration boundary |

115| ----------------- | ------------------------------- | ------------------------------------------------------------------------------ |

116| Legacy | `3.11–3.13 / 0.119.0 / 1.10.22` | Existing models import from `pydantic`. |

117| Prepared baseline | `3.11–3.13 / 0.119.0 / 2.12.3` | Both models use `pydantic.v1`. Record the actual bundled-v1 version. |

118| Target | `3.11–3.13 / 0.119.0 / 2.12.3` | Only `OrderInput` moves to v2. The adjacent route keeps its separate v1 model. |

119 

120The prepared and target dependency locks are identical. The module slice changes the model and its direct serialization method, not the dependency environment.

121 

122The preparation step is a separate reviewed change. It also makes the remaining v1 route adapters emit alias-keyed dictionaries with `dict(by_alias=True)` before response validation; returning the v1 model directly under the v2 host can lose public aliases.

123 

124The target order adapter uses `model_dump(by_alias=True)`, and the adjacent v1 route keeps its preparation adapter.

125 

126The [FastAPI migration guide](https://fastapi.tiangolo.com/how-to/migrate-from-pydantic-v1-to-pydantic-v2/) limits temporary mixed v1/v2 route support to `0.119.0` through `0.127.x`; `0.128.0` removed it.

127 

128This example deliberately pins `0.119.0` to demonstrate that transition. Use a fully v2 boundary for a newer FastAPI application, and keep an owner and removal condition for the temporary bridge. Do not nest v1 and v2 models. This bridge is not a Python 3.14 migration path.

129 

130### Review the contract tests

131 

132The frozen HTTP cases cover successful requests, an omitted note or a note explicitly set to null, missing and invalid IDs, multiple unknown fields, mixed field errors, invalid notes, malformed JSON, wrong content type, missing body, aliases, array and string bodies, and the adjacent legacy route. They compare status codes, the `Content-Type` header, and complete parsed JSON responses without stripping error fields or changing expected types.

133 

134The service has an application-owned error envelope and public OpenAPI document from the legacy stage. The error and schema adapters remain in place. The target preserves the legacy error order: declared-field errors first, then unknown keys alphabetically.

135 

136The tests compare the complete ordered `error.fields` array; they do not sort observed responses. The runner records each model's native schema separately and verifies that native schemas differ; public schema parity does not mean the dependency's generated schemas are identical.

137 

138The service preserves a legacy input behavior: it accepts an array of pairs such as `[["order_id", "ord_123"]]` as an object, and both `[]` and an empty JSON string become empty objects with a missing-ID error. A nonempty string remains an invalid body. The target model keeps successful `dict(value)` conversions. Changing it to object-only input requires an owner's contract decision, not a rewritten test expectation.

139 

140For your repository, freeze its actual existing responses before editing. Preserve optional fields with explicit defaults, keep strictness and unknown-field behavior, and test the real error handler and response serialization.

141 

142Raw validation errors differ across versions. The [`Pydantic` migration guide](https://docs.pydantic.dev/latest/migration/) also describes changes to aliases, coercion, optional fields, and custom validation functions; v2 does not convert `TypeError` raised inside those functions into `ValidationError`.

143 

144### Inspect the contract runner

145 

146This excerpt comes from `run.py` in the download. Run the complete files after extracting the ZIP; the locked stages and HTTP fixtures are included there.

147 

148<details>

149 <summary>Compare the three stages</summary>

150```python

151class OrderInput(BaseModel):

152 internal_order_id: StrictStr = Field(alias="order_id")

153 customer_note: StrictStr | None = None

154 model_config = ConfigDict(extra="forbid", populate_by_name=False)

155 

156 @model_validator(mode="before")

157 @classmethod

158 def preserve_legacy_mapping(cls, value):

159 # v1 tries dict(value), so pairs, [] and "" can become objects.

160 # Preserve successful conversions without changing failed-input errors.

161 if not isinstance(value, dict):

162 try:

163 return dict(value)

164 except (TypeError, ValueError):

165 return value

166 return value

167 

168 

169def serialize_order(payload):

170 return OrderInput(**payload).model_dump(by_alias=True)

171 

172 

173def preserve_legacy_error_order(fields):

174 # v1 reports declared-field errors first and unknown keys alphabetically.

175 # v2 keeps unknown keys in input order; preserve the existing HTTP contract.

176 known = [field for field in fields if field["code"] != "extra_forbidden"]

177 extra = sorted(

178 (field for field in fields if field["code"] == "extra_forbidden"),

179 key=lambda field: field["path"],

180 )

181 return known + extra

182 

183 

184def check_target_model():

185 for payload, expected in (

186 ({"order_id": "ord_123"}, {"order_id": "ord_123", "customer_note": None}),

187 (

188 {"order_id": "ord_123", "customer_note": "Leave at desk"},

189 {"order_id": "ord_123", "customer_note": "Leave at desk"},

190 ),

191 (

192 {"order_id": "ord_123", "customer_note": None},

193 {"order_id": "ord_123", "customer_note": None},

194 ),

195 ):

196 assert serialize_order(payload) == expected

197 for payload, expected in (

198 ({}, [("order_id", "missing")]),

199 ({"order_id": 123}, [("order_id", "string_type")]),

200 (

201 {"order_id": "ord_123", "customer_note": 17},

202 [("customer_note", "string_type")],

203 ),

204 (

205 {"order_id": "ord_123", "unexpected": True},

206 [("unexpected", "extra_forbidden")],

207 ),

208 ):

209 try:

210 OrderInput(**payload)

211 except ValidationError as error:

212 assert [

213 (item["loc"][0], item["type"]) for item in error.errors()

214 ] == expected

215 else:

216 raise AssertionError(f"Accepted invalid order: {payload}")

217 print("PASS: target model validation and serialization (7 cases)")

218 print("Model-only checks; use --compare-stages for HTTP migration parity.")

219 

220 

221def main():

222 parser = argparse.ArgumentParser()

223 parser.add_argument("--output", type=Path, default=ROOT / "evidence")

224 parser.add_argument("--compare-stages", action="store_true")

225 args = parser.parse_args()

226 if not args.compare_stages:

227 check_target_model()

228 return

229 args.output.mkdir(parents=True, exist_ok=True)

230 reports = []

231 commands = []

232 for stage in ("legacy", "prepared", "target"):

233 project = Path("support") / stage

234 stage_commands = [

235 [

236 "uv",

237 "sync",

238 "--project",

239 str(project),

240 "--python",

241 sys.executable,

242 "--frozen",

243 ],

244 [

245 "uv",

246 "run",

247 "--project",

248 str(project),

249 "--python",

250 sys.executable,

251 "--frozen",

252 "python",

253 "test/run_stage.py",

254 stage,

255 str(args.output / f"{stage}.json"),

256 ],

257 ]

258 for command in stage_commands:

259 completed = subprocess.run(

260 command, cwd=ROOT, capture_output=True, text=True

261 )

262 label = f"{stage}-{len(commands)}"

263 (args.output / f"{label}.log").write_text(

264 completed.stdout + completed.stderr

265 )

266 commands.append(

267 {

268 "command": command,

269 "exit_code": completed.returncode,

270 "log": f"{label}.log",

271 }

272 )

273 (args.output / "commands.json").write_text(

274 json.dumps(commands, indent=2, sort_keys=True) + "\n"

275 )

276 if completed.returncode:

277 print(completed.stdout + completed.stderr, file=sys.stderr)

278 raise SystemExit(completed.returncode)

279 print(completed.stdout, end="")

280 reports.append(json.loads((args.output / f"{stage}.json").read_text()))

281 assert all(report["python"] == platform.python_version() for report in reports)

282 for report in reports[1:]:

283 for key in (

284 "observations",

285 "public_openapi",

286 "direct_call",

287 "fixture_sha256",

288 "schema_fixture_sha256",

289 ):

290 assert report[key] == reports[0][key], f"Parity failed: {key}"

291 assert reports[1]["lock_sha256"] == reports[2]["lock_sha256"]

292 native_schema_differs = (

293 reports[0]["native_model_schema"] != reports[2]["native_model_schema"]

294 )

295 assert native_schema_differs, "The example should expose native schema drift"

296 manifest = {

297 str(path.relative_to(ROOT)): digest(path)

298 for path in sorted(ROOT.rglob("*"))

299 if path.is_file()

300 and not path.resolve().is_relative_to(args.output.resolve())

301 and not any(

302 part in {".venv", "__pycache__", "evidence", ".ruff_cache"}

303 for part in path.relative_to(ROOT).parts

304 )

305 }

306 summary = {

307 "status": "pass",

308 "scope": "synthetic in-process FastAPI TestClient execution",

309 "stages": [report["stage"] for report in reports],

310 "http_cases_per_stage": reports[0]["http_cases"],

311 "native_model_schema_differs": native_schema_differs,

312 "public_http_and_schema_parity": True,

313 "commands": commands,

314 "source_sha256": manifest,

315 }

316 (args.output / "summary.json").write_text(

317 json.dumps(summary, indent=2, sort_keys=True) + "\n"

318 )

319 print("PASS: legacy, prepared, target HTTP/schema/serialization contracts match")

320 print(

321 "Native model schemas differ; the existing public schema adapter is preserved."

322 )

323```

324 

325</details>

326 

327## Run the workflow in your repository

328 

3291. **Write the brief.** Name the module, direct callers, owner, approved versions, contract tests, out-of-scope code, and rollback instructions. Stop if the slice requires a wider dependency upgrade or changes public behavior.

3302. **Establish the baseline.** Have Codex read `AGENTS.md`, the brief, official migration guidance, locks, callers, and tests. Record the starting commit and installed versions. Explain or repair failing baseline checks before claiming parity.

3313. **Prepare compatibility separately.** If shared dependencies couple the module to other code, review that preparation first. Prove that the same fixtures pass against both the original and prepared baselines.

3324. **Migrate one slice.** Use the starter prompt on this page. Change the module and its direct adapter; preserve unrelated modules and name any compatibility code that remains.

3335. **Run the same tests.** Compare baseline and final HTTP/schema fixtures without changing expectations. Run affected integration, lint, and type checks, and save output tied to the final source and dependency state.

3346. **Review the handoff.** Inspect the diff, observed evidence, unresolved differences, remaining bridge, and rollback. Create a draft PR only when repository tools and permissions support it. Leave merging, deployment, and another migration slice to the reviewer.

335 

336## Record what was verified

337 

338Use an evidence record for each criterion:

339 

340```text

341Criterion: [behavior and test]

342Environment: [setup reference; actual runtime and dependency versions]

343Before: [commit/diff ID; command; exit code; observed result; evidence path]

344After: [final commit/diff ID; command; exit code; observed result; evidence path]

345Status: [pass / fail / blocked / not run]

346Limits: [untested behavior, missing prerequisite, or required owner decision]

347```

348 

349A successful import, lint check, or regenerated snapshot does not prove API parity. Record intentional differences separately, and rerun checks when the final source changes. When adapting the sample, include your application's callers and services in the contract tests.