ChatGPT App - Enable punchout button with dynamic url

Hello

I’m building an MCP server with a widget and need help with the punch-out button.

The Problem
When my tool generates content, it creates a unique URL with a parameter (e.g., https:/example.com/item?id=ABC123). I want the punch-out button to open this specific URL, but it only opens the static base domain (https:/example.com) that I set in the resource’s _meta.

What I’ve Tried
I’ve tried setting openai/widgetDomain in:

  1. The resource definition (in resources/read response)
  2. The tool response _meta
  3. The widget resource’s _meta inside the tool response

None of these approaches make the punch-out button use the dynamic URL.

The Question
How do I make the punch-out button open a different URL for each tool invocation?

I know Canva does this - when you generate a design, the punch-out button opens that specific design’s editor, How is this achieved?

Any guidance would be appreciated! Thanks!

3 Likes

Hi!

If your punch-out button is inside your widget, use window.openai.openExternal({ href: url}) method to open any URL.

Of course, this URL could be dynamic.

Thank you for responding. I believe the punch-out button is enabled when openai/widgetDomain is set as mentioned in the OpenAi apps-sdk docs. It’s the one that says “Open in Canva” from the screenshot below.

image

Hey did you find a solution for this? Running into the same issue

I have the same issue. The widget domain is required and I need the punch out button to use a dynamic url. How is this achieved?
I tried the window.openai.openExternal({ href: url}) but I’m not sure how to override the url.
Figma and Canva have this capability though

Same issue here, there is no mention of it in the docs

We ran into this exact problem at Speechify and finally figured it out by reverse-engineering how Canva and Figma handles it.

How it works (based on observation, pending official confirmation from OpenAI team): It seems like ChatGPT uses the iframe’s window.location.href directly for the punchout button link. The domain/host is determined by your openai/widgetDomain resource. ChatGPT proxies your widget through their sandbox domain, but the punchout button resolves to your actual widget domain.

The solution: Use history.replaceState to update the iframe’s URL path dynamically:

  // In your widget component (React example)
  useEffect(() => {
    if (resourceId) {
      // Must use relative URL - iframe policy blocks absolute URLs with different domains
      history.replaceState({}, '', `/open/${resourceId}`);
    }
  }, [resourceId]);

So if your openai/widgetDomain is https://example.com and you set the path to /open/ABC123, the punchout button opens https://example.com/open/ABC123

Important notes:

  1. Use relative URLs only — The iframe’s security policy won’t allow history.replaceState with absolute URLs pointing to a different domain. Stick to paths like /open/${id}.
  2. Next.js caveat — If you’re using Next.js, the framework wraps history.replaceState with its own routing logic which can block the URL change. Bypass it with the native method:
const nativeReplaceState = History.prototype.replaceState.bind(history);
nativeReplaceState({}, '', `/open/${resourceId}`);

Hope this helps! Would be great to see this documented officially.

1 Like

Thank you so much! I’ve tried your solution and it worked for me. Thank you for providing this solution and let’s hope this would be documented officially.

Hi all. Thanks for flagging this :folded_hands: @clowreed @albertus-speechify We shipped a new api to set this natively. it looks like window.openai.setOpenInAppUrl({ href }) you can read more about this at: Build your ChatGPT UI

2 Likes

Thank you @coreyching and the OpenAI team for shipping this update. :rocket:

1 Like