> ## Documentation Index
> Fetch the complete documentation index at: https://sunpeak.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# ChatGPT Hooks

> Host-specific React hooks for ChatGPT features: file uploads, modals, and checkout.

<Badge color="yellow">sunpeak API</Badge>
<Badge color="blue">ChatGPT Only</Badge>

## Overview

These hooks wrap the ChatGPT `window.openai` runtime APIs, providing typed React hooks for host-specific features that go beyond the MCP Apps standard. They are available **only when running inside ChatGPT** (or the inspector with the ChatGPT host selected).

<Warning>
  These hooks throw if called outside ChatGPT. Always feature-detect with [`isChatGPT()`](/app-framework/functions/host-detection) before using them.
</Warning>

## Import

```tsx theme={null}
import { useUploadFile, useRequestModal, useRequestCheckout } from 'sunpeak/host/chatgpt';
import { isChatGPT } from 'sunpeak/host';
```

## Feature Detection Pattern

```tsx theme={null}
import { isChatGPT } from 'sunpeak/host';
import { useUploadFile } from 'sunpeak/host/chatgpt';

function MyResource() {
  // Only use ChatGPT hooks when running on ChatGPT
  if (!isChatGPT()) {
    return <p>File upload requires ChatGPT.</p>;
  }

  return <FileUploader />;
}

function FileUploader() {
  const uploadFile = useUploadFile();
  // Safe to call — we're inside ChatGPT
  // ...
}
```

***

## useUploadFile

Upload a file from the app UI into the ChatGPT conversation.

### Signature

```tsx theme={null}
function useUploadFile(): (file: File) => Promise<CreateFileResult>
```

### Returns

<ResponseField name="uploadFile" type="(file: File) => Promise<CreateFileResult>">
  Async function that uploads a file and returns its ID.
</ResponseField>

### CreateFileResult

<ResponseField name="fileId" type="string" required>
  The ID of the uploaded file.
</ResponseField>

### Supported Formats

`image/png`, `image/jpeg`, `image/webp`

### Usage

```tsx theme={null}
import { useUploadFile } from 'sunpeak/host/chatgpt';

function ImageUploader() {
  const uploadFile = useUploadFile();

  const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.currentTarget.files?.[0];
    if (!file) return;
    const { fileId } = await uploadFile(file);
    console.log('Uploaded:', fileId);
  };

  return <input type="file" accept="image/*" onChange={handleChange} />;
}
```

***

## useRequestModal

Open a host-controlled modal in ChatGPT.

### Signature

```tsx theme={null}
function useRequestModal(): (params: OpenModalParams) => Promise<void>
```

### OpenModalParams

<ResponseField name="template" type="string">
  URL of an alternate UI template to load in the modal. Omit to reuse the current UI.
</ResponseField>

<ResponseField name="params" type="Record<string, unknown>">
  Arbitrary params forwarded to the modal content.
</ResponseField>

### Usage

```tsx theme={null}
import { useRequestModal } from 'sunpeak/host/chatgpt';

function SettingsButton() {
  const requestModal = useRequestModal();

  return (
    <button onClick={() => requestModal({ template: 'ui://widget/settings.html' })}>
      Open Settings
    </button>
  );
}
```

***

## useRequestCheckout

Trigger the ChatGPT instant checkout flow.

### Signature

```tsx theme={null}
function useRequestCheckout(): (session: CheckoutSession) => Promise<CheckoutOrder>
```

### CheckoutSession

<ResponseField name="id" type="string" required>
  Unique session ID.
</ResponseField>

<ResponseField name="payment_provider" type="CheckoutPaymentProvider" required>
  Payment provider configuration.

  <Expandable title="CheckoutPaymentProvider">
    <ResponseField name="provider" type="string" required>
      Payment provider name (e.g. `'stripe'`).
    </ResponseField>

    <ResponseField name="merchant_id" type="string" required>
      Merchant account ID.
    </ResponseField>

    <ResponseField name="supported_payment_methods" type="string[]" required>
      Accepted payment methods (e.g. `['card', 'apple_pay']`).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="status" type="'ready_for_payment'" required>
  Must be `'ready_for_payment'` to initiate checkout.
</ResponseField>

<ResponseField name="currency" type="string" required>
  ISO 4217 currency code (e.g. `'USD'`).
</ResponseField>

<ResponseField name="totals" type="CheckoutTotal[]" required>
  Line items and totals.

  <Expandable title="CheckoutTotal">
    <ResponseField name="type" type="string" required>
      Total type (e.g. `'total'`, `'subtotal'`).
    </ResponseField>

    <ResponseField name="display_text" type="string" required>
      Display label.
    </ResponseField>

    <ResponseField name="amount" type="number" required>
      Amount in smallest currency unit (e.g. cents).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="links" type="CheckoutLink[]" required>
  Legal links shown during checkout.

  <Expandable title="CheckoutLink">
    <ResponseField name="type" type="'terms_of_use' | 'privacy_policy' | string" required>
      Link type.
    </ResponseField>

    <ResponseField name="url" type="string" required>
      Link URL.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="payment_mode" type="'live' | 'test'" required>
  Whether to use live or test payment processing.
</ResponseField>

### CheckoutOrder

<ResponseField name="id" type="string" required>
  Order ID.
</ResponseField>

<ResponseField name="checkout_session_id" type="string" required>
  ID of the checkout session that produced this order.
</ResponseField>

<ResponseField name="status" type="'completed' | string" required>
  Order status.
</ResponseField>

<ResponseField name="permalink_url" type="string">
  Optional permalink to the order receipt.
</ResponseField>

### Usage

```tsx theme={null}
import { useRequestCheckout } from 'sunpeak/host/chatgpt';

function BuyButton() {
  const requestCheckout = useRequestCheckout();

  const handleBuy = async () => {
    try {
      const order = await requestCheckout({
        id: 'session-1',
        payment_provider: {
          provider: 'stripe',
          merchant_id: 'acct_xxx',
          supported_payment_methods: ['card', 'apple_pay'],
        },
        status: 'ready_for_payment',
        currency: 'USD',
        totals: [{ type: 'total', display_text: 'Total', amount: 999 }],
        links: [],
        payment_mode: 'live',
      });
      console.log('Order completed:', order.id);
    } catch {
      console.log('Checkout cancelled or failed');
    }
  };

  return <button onClick={handleBuy}>Buy Now</button>;
}
```

***

## Low-Level Access

For advanced use cases, you can access the `window.openai` runtime directly:

```tsx theme={null}
import { getOpenAIRuntime } from 'sunpeak/host/chatgpt';
import type { OpenAIRuntime } from 'sunpeak/host/chatgpt';

const runtime: OpenAIRuntime | undefined = getOpenAIRuntime();
if (runtime?.openExternal) {
  runtime.openExternal({ href: 'https://example.com' });
}
```

## Inspector Support

When the ChatGPT host is selected in the [Inspector](/testing/inspector), a mock `window.openai` runtime is automatically injected into the iframe. This makes `isChatGPT()` return `true` and all ChatGPT hooks functional with stub implementations that log to the browser console.

When a different host (e.g. Claude) is selected, `window.openai` is not injected and `isChatGPT()` returns `false`.

## See Also

<Card horizontal title="Host Detection" icon="radar" href="/app-framework/functions/host-detection">
  Detect which host is running your app.
</Card>

<Card horizontal title="Inspector" icon="flask" href="/testing/inspector">
  Test ChatGPT hooks locally with the mock runtime.
</Card>
