Skip to main content

ChatGPT App

ChatGPT App developer guide

Build or extend Jobby.dev ChatGPT App widgets — the manifest, the OpenAI Apps SDK contract, CSP requirements.

Last updated

How to build, extend, or fork the Jobby.dev ChatGPT App. Aimed at contributors who want to add a widget or change widget behavior; non-developers should head back to the overview.

Repo layout

The App lives under apps/chatgpt-app/ in the Jobby.dev monorepo:

apps/chatgpt-app/
├── manifest.json         # App manifest (widgets, scopes, MCP endpoint, CSP)
├── README.md             # Setup + submission notes
└── widgets/
    ├── InterviewRoomWidget.tsx
    ├── UpgradeCheckoutWidget.tsx
    └── QueueStatusWidget.tsx

The manifest

Declares everything OpenAI's directory and the Apps SDK need to know:

  • Display name, description, logo.
  • MCP endpoint URL (the hosted https://jobby.dev/api/mcp in production).
  • OAuth scopes the App requests at install.
  • Widget declarations: each entry binds a content-type to a widget component file and declares the iframe / network / script-src CSP allowlist that widget needs.

Widget contract

A widget is a React component that consumes two hooks from @openai/apps-sdk:

import { useToolResult, useToolInvoke, declareWidget } from "@openai/apps-sdk";

declareWidget({
  id: "queue_status",
  contentType: "application/json+jobbydev_queue_status",
});

export function QueueStatusWidget() {
  const initialResult = useToolResult<QueueStatusToolResult>();
  const invoke = useToolInvoke();
  // ...
}
  • useToolResult — returns the tool result that caused this widget to mount, fully typed if you pass a generic.
  • useToolInvoke— lets the widget invoke other tools on the user's behalf (e.g. polling jobbydev_queue_status for refreshes).
  • declareWidget — registers the binding from content-type to component. Called at module-load time so the Apps SDK can dispatch.

CSP requirements

Each widget needs its iframe / network / script CSP allowlist declared in the manifest. Examples:

  • InterviewRoom: frame-src includes *.daily.co; connect-srcincludes Daily's WebRTC signaling endpoints.
  • UpgradeCheckout: frame-src includes js.stripe.com; connect-src includes api.stripe.com.
  • QueueStatus: only needs connect-src jobby.dev for poll calls.

The OpenAI directory review checks these allowlists before approval. Tighter is better — request only what the widget actually needs.

Adding a new widget

  1. Define the tool result shape: which MCP tool causes this widget to mount, what fields the result carries.
  2. Add a content-type label: pick a uniqueapplication/json+jobbydev_* string.
  3. Update the corresponding tool handler on the Jobby.dev backend to set the content-type header on the response.
  4. Build the widget component in apps/chatgpt-app/widgets/.
  5. Register in the manifest.
  6. Update the widgets reference page: /docs/chatgpt-app/widgets.

Local development

Today: scaffold-only — @openai/apps-sdkisn't installed in the monorepo yet (the README has the install dance). Once installed, the Apps SDK has a local-dev playground that mounts widgets against synthetic tool results without round-tripping through ChatGPT.

Submission

The OpenAI directory submission process is documented at docs/agent-directory-submissions.md in this repo. Pre-submission requirements:

  • Manifest validated against OpenAI's schema.
  • CSP review passes (frame-src / connect-src tight).
  • OAuth flow tested end-to-end against Jobby.dev staging.
  • Each widget mounts without errors against a synthetic tool result.

Related reading