Summary
The ChatGPT Apps developer panel warns that DevSpace's UI template has no "widget domain" set:
Widget domain is not set for this template. A unique domain is required for app submission.
This is a real submission gate for the ChatGPT Apps Directory: OpenAI requires each app's UI template to declare a stable, unique sandbox origin via _meta.ui.domain. DevSpace currently omits this field, so the template is registered without it.
What "widget domain" is
When DevSpace registers its widget app as an MCP resource (ui://devspace/workspace-app.html), the MCP Apps schema allows _meta.ui with several fields. DevSpace sets only the CSP allowlists:
// src/server.ts — appCsp(config)
{
csp: {
resourceDomains: [publicBaseUrl],
connectDomains: [publicBaseUrl],
}
}
There is a sibling field, domain, that DevSpace never emits. Per the OpenAI Apps SDK reference:
Dedicated origin for hosted components (required for app submission; must be unique per app). Defaults to https://web-sandbox.oaiusercontent.com.
From the "Build your MCP server" guide:
ChatGPT renders the widget under <domain>.web-sandbox.oaiusercontent.com.
So domain is not the server's own public URL (e.g. the ngrok/tunnel origin). It is a stable, dedicated origin inside ChatGPT's host sandbox that ChatGPT assigns to the widget iframe. Without it, ChatGPT falls back to an ephemeral per-conversation sandbox origin.
Why OpenAI asks for it
A stable sandbox origin matters for (per SDK docs):
- OAuth callback redirect URIs (need a fixed origin on the allowlist)
- CORS policies on external APIs that check the
Origin header
- API key restrictions by origin
For directory distribution, OpenAI wants every app pinned to a unique, stable origin so these things are predictable and reviewable. That is why the panel calls it "required for app submission" and "must be unique per app." In developer mode the widget still renders under the default ephemeral sandbox origin, so development use is unaffected — only submission is gated.
Relevant code
appCsp() in src/server.ts only returns CSP allowlists and never includes domain. The resource is registered at src/server.ts around the registerAppResource(server, "DevSpace Diff Card", WORKSPACE_APP_URI, ...) call, where _meta.ui.csp is set both at listing level and on the content item.
A fix would extend the _meta.ui block to include domain, e.g.:
_meta: {
ui: {
csp: appCsp(config),
domain: "<unique-per-app-label>",
},
}
The exact domain format is host-specific (OpenAI docs say the widget renders under <domain>.web-sandbox.oaiusercontent.com, with a default of web-sandbox.oaiusercontent.com). The value appears to be a short stable label that ChatGPT turns into a subdomain, but the precise accepted format needs verification against a working submitted app or the SDK's hosted-submission docs.
Scope and open questions (needs further investigation)
I do not fully understand this feature yet, so I am filing this for visibility rather than as a ready-to-merge fix. Things to clarify before changing anything:
- What is this actually for in DevSpace's case? DevSpace's widget currently only loads its own bundled JS/CSS from the DevSpace server's public origin. Does it make any cross-origin calls or rely on OAuth/CORS that would benefit from a stable sandbox origin? If not, the field is purely a submission-compliance checkbox, not a runtime improvement.
- Does DevSpace intend to support being an official registered ChatGPT App at all? I am running it in developer mode against chatgpt.com. I do not know whether the upstream maintainer registers/publishes DevSpace or keeps it developer-only. If it stays developer-only, this warning can be ignored; if it is meant to be submittable, the field is mandatory.
- Resource/connect domains may also need to include the sandbox origin once
domain is pinned, or the iframe app cannot load its own bundled assets. The OpenAI docs explicitly warn: declare all origins the app loads from, including the sandbox itself once pinned. This needs to be handled together with the domain addition.
- Exact accepted format for
_meta.ui.domain on ChatGPT specifically. The hosted-submission flow and any SDK examples should be checked; the docs mention web-sandbox.oaiusercontent.com as the default and <domain>.web-sandbox.oaiusercontent.com as the pattern, but whether a bare label or a full URL is expected is not fully spelled out.
Environment
- DevSpace 1.0.3 (post-merge with upstream/main, includes
feat/config: add codex tool mode)
- Client: chatgpt.com (Developer Mode connector)
- Public origin via ngrok tunnel (
*.ngrok-free.app)
DEVSPACE_TOOL_MODE=codex, DEVSPACE_WIDGETS=full
References
Summary
The ChatGPT Apps developer panel warns that DevSpace's UI template has no "widget domain" set:
This is a real submission gate for the ChatGPT Apps Directory: OpenAI requires each app's UI template to declare a stable, unique sandbox origin via
_meta.ui.domain. DevSpace currently omits this field, so the template is registered without it.What "widget domain" is
When DevSpace registers its widget app as an MCP resource (
ui://devspace/workspace-app.html), the MCP Apps schema allows_meta.uiwith several fields. DevSpace sets only the CSP allowlists:There is a sibling field,
domain, that DevSpace never emits. Per the OpenAI Apps SDK reference:From the "Build your MCP server" guide:
So
domainis not the server's own public URL (e.g. the ngrok/tunnel origin). It is a stable, dedicated origin inside ChatGPT's host sandbox that ChatGPT assigns to the widget iframe. Without it, ChatGPT falls back to an ephemeral per-conversation sandbox origin.Why OpenAI asks for it
A stable sandbox origin matters for (per SDK docs):
OriginheaderFor directory distribution, OpenAI wants every app pinned to a unique, stable origin so these things are predictable and reviewable. That is why the panel calls it "required for app submission" and "must be unique per app." In developer mode the widget still renders under the default ephemeral sandbox origin, so development use is unaffected — only submission is gated.
Relevant code
appCsp()insrc/server.tsonly returns CSP allowlists and never includesdomain. The resource is registered atsrc/server.tsaround theregisterAppResource(server, "DevSpace Diff Card", WORKSPACE_APP_URI, ...)call, where_meta.ui.cspis set both at listing level and on the content item.A fix would extend the
_meta.uiblock to includedomain, e.g.:The exact domain format is host-specific (OpenAI docs say the widget renders under
<domain>.web-sandbox.oaiusercontent.com, with a default ofweb-sandbox.oaiusercontent.com). The value appears to be a short stable label that ChatGPT turns into a subdomain, but the precise accepted format needs verification against a working submitted app or the SDK's hosted-submission docs.Scope and open questions (needs further investigation)
I do not fully understand this feature yet, so I am filing this for visibility rather than as a ready-to-merge fix. Things to clarify before changing anything:
domainis pinned, or the iframe app cannot load its own bundled assets. The OpenAI docs explicitly warn: declare all origins the app loads from, including the sandbox itself once pinned. This needs to be handled together with thedomainaddition._meta.ui.domainon ChatGPT specifically. The hosted-submission flow and any SDK examples should be checked; the docs mentionweb-sandbox.oaiusercontent.comas the default and<domain>.web-sandbox.oaiusercontent.comas the pattern, but whether a bare label or a full URL is expected is not fully spelled out.Environment
feat/config: add codex tool mode)*.ngrok-free.app)DEVSPACE_TOOL_MODE=codex,DEVSPACE_WIDGETS=fullReferences
_meta.ui.domain): https://developers.openai.com/apps-sdk/referencesrc/server.tsappCsp()and theregisterAppResource(... "DevSpace Diff Card" ...)block