If your code contains client.responses.create({ prompt: { id: 'pmpt_...' } }), you have a deadline.
"v1/prompts is scheduled to shut down on November 30, 2026."
— OpenAI's own migration guide
It was announced on 2026-06-03. Two other things were deprecated the same day, and both got a named successor: Agent Builder points at the Agents SDK, the Evals platform points at Promptfoo. Reusable Prompts got a sentence — "move reusable prompt content into your application code" — and no tooling.
The awkward part: there is no read path
You cannot script the export, because prompt objects were never readable over the API:
"they cannot be created, retrieved or modified with an API key"
The request for a list endpoint sat open for months and was closed on 2026-06-25 by OpenAI staff without being built:
"Thank you for taking the time to share this feature request. We appreciate and value your feedback. While we can't promise implementation or provide a timeline, we're grateful you shared it with us."
Someone in that thread summed the situation up:
"The only work around now is manually copying and pasting."
So: your prompt text lives in exactly one place you do not control, it has a deletion date, and the only thing on earth that can read it is the browser tab that renders the dashboard.
Fine. Let's use the browser tab.
Getting the content out
# start Chrome once with the DevTools port open, then sign in as normal
chrome --remote-debugging-port=9222
npx pmpt-eject capture
116 days until v1/prompts shuts down (November 30, 2026).
attached to: Prompts - OpenAI API
https://platform.openai.com/prompts
captured 7 prompts / 19 versions — keep clicking through your prompts list, Ctrl-C when done
It attaches over the Chrome DevTools Protocol to a tab you already opened and already signed into. It does not drive your login, does not ask for a password, does not store a credential, and there is no puppeteer or playwright involved — just Network.enable and Network.getResponseBody over Node 22's built-in WebSocket.
You click through your prompts; the counter moves as content lands. Ctrl-C writes prompts/ and stops.
Nothing about an OpenAI endpoint path is hardcoded. Those internal routes are undocumented and change without notice, so the filter is deliberately dumb: any response body that parses as JSON and whose raw text contains pmpt_ gets inspected. When OpenAI reshuffles its internal API next month, this keeps working.
The result is diffable JSON you commit:
{
"id": "pmpt_abc",
"name": "support-triage",
"versions": {
"2": {
"instructions": "You are a support agent for {{customer_name}}.",
"messages": [{ "role": "user", "content": "Summarise the ticket." }],
"model": "gpt-5.6-terra",
"variables": ["customer_name"],
"capturedAt": "2026-08-06T09:12:44.000Z"
}
}
}
Re-capturing merges by id + version and never clobbers a version already on disk. If a body differs from what you already have, it lands in <name>.<id>.conflict.json with a warning instead of silently overwriting your rescue.
Finding the ones you missed
Capture only sees what the dashboard actually fetches — a prompt you never clicked is a prompt that never crossed the wire. So:
npx pmpt-eject scan . --strict
CAPTURED pmpt_abc (support-triage) 3 version(s)
src/support.ts:6:20
STRANDED pmpt_def
src/notes.ts:3:38
STRANDED pmpt_ghi789
workers/digest.py:3:21
3 unique id(s) in 4 file(s): 1 captured, 2 stranded.
--strict: failing because 2 id(s) are stranded.
--strict exits 1 while anything is unrescued, so it drops straight into CI as a gate. Every one of those STRANDED lines is a call site that starts failing in production on November 30.
The half nobody talks about
Most of the coverage treats this as an archival problem. Read the deprecation thread and it isn't:
"I have a lot of web apps based on prompt objects, is super convenient cause I can make small fixes to the prompt without redeploying, and also rollback to previous versions."
"the main advantage of stored prompts is application independent, rapid prompt development, model optimizations and hotfixing"
"Move it into your application code" takes that away. Your prompt becomes a string literal behind a build, a review and a deploy. Fixing a typo in a system prompt now means shipping.
You can get it back without a vendor. Before:
await client.responses.create({ prompt: { id: 'pmpt_abc', version: '2', variables: { customer_name: 'Acme' } } })
After:
const prompts = createPromptResolver({ source: 'https://raw.githubusercontent.com/me/app/main/prompts' })
await client.responses.create(await prompts.expand({ id: 'pmpt_abc', version: '2', variables: { customer_name: 'Acme' } }))
expand() returns exactly what responses.create() wants:
{
instructions: 'You are a support agent for Acme.',
input: [ { role: 'user', content: 'Summarise the ticket.' } ],
model: 'gpt-5.6-terra'
}
Because source is an https URL pointed at a directory in your repo, editing that JSON file on main changes what your running process sends. No restart, no redeploy. The cache is stale-while-revalidate: a fresh copy is served with no network call, a stale one is served immediately while a refresh runs in the background, so expand() never blocks on the network after the first call. If the refresh fails, the stale copy keeps being served and you get a warning — it never throws, and it backs off instead of hammering a source that is down.
A couple of details that mattered more than expected:
- Unknown
{{placeholders}}are left exactly as they are, not blanked. A half-rendered prompt is far easier to debug than a silently empty one. They are reported onargs.unresolved. -
unresolved,promptIdandpromptVersionare non-enumerable, so{ ...args }andJSON.stringify(args)still contain onlyinstructions,inputandmodel. Nothing extra ever reaches the API.
Install
npm install pmpt-eject
npx pmpt-eject doctor # live countdown + what is still stranded
Zero runtime dependencies, Node 22+, MIT. 105 tests, fully offline, run against two real recorded Chrome DevTools transcripts rather than a mocked protocol.
Source: github.com/Booyaka101/pmpt-eject
One last thing worth being clear about: capture becomes useless on November 30, because there will be nothing left to capture. The resolver does not — it reads a store you own, on disk or over plain HTTPS, and keeps working indefinitely. Capture is a one-time rescue with a hard expiry. The resolver is the actual replacement.
If you are going to do this, do it while the dashboard still renders.
Top comments (0)