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

# Media

> Upload, fetch, and delete images, video, audio, and documents

export const TypeTooltip = ({name, type, children}) => {
  const [visible, setVisible] = React.useState(false);
  const [pos, setPos] = React.useState({
    top: 0,
    left: 0
  });
  const triggerRef = React.useRef(null);
  const show = () => {
    if (triggerRef.current) {
      const rect = triggerRef.current.getBoundingClientRect();
      setPos({
        top: rect.bottom + 6,
        left: rect.left
      });
    }
    setVisible(true);
  };
  const hide = () => setVisible(false);
  return <>
      <span ref={triggerRef} onMouseEnter={show} onMouseLeave={hide} style={{
    cursor: "pointer",
    position: "relative",
    display: "inline"
  }}>
        {children || <code>{name}</code>}
      </span>
      {visible && <span style={{
    position: "fixed",
    top: pos.top,
    left: pos.left,
    zIndex: 9999,
    padding: "8px 12px",
    borderRadius: "8px",
    fontSize: "13px",
    lineHeight: "1.5",
    fontFamily: "'Azeret Mono', monospace",
    whiteSpace: "pre",
    backgroundColor: "var(--tw-prose-pre-bg, #1e1e1e)",
    color: "var(--tw-prose-pre-code, #e5e5e5)",
    border: "1px solid var(--border, rgba(128,128,128,0.2))",
    boxShadow: "0 4px 16px rgba(0,0,0,0.3)",
    pointerEvents: "none"
  }}>
          {type}
        </span>}
    </>;
};

The `media` resource handles the lifecycle of every file you send or receive. Uploaded media gets a `mediaId` you reference from `messages.send`; inbound media arrives as a `mediaId` you resolve to a signed URL.

## Upload

```ts theme={null}
import { readFile } from "node:fs/promises";

const { mediaId } = await client.media.upload({
  file: await readFile("/path/to/photo.jpg"),
  mimeType: "image/jpeg",
  filename: "photo.jpg",
});

await client.messages.send({
  to: "+15551234567",
  image: { id: mediaId, caption: "Here's the photo" },
});
```

`file` accepts a `Buffer` or `Uint8Array` — stream the file yourself if it doesn't fit in memory.

<Accordion title="UploadOptions" description="">
  | Option     | Type                   | Description |
  | ---------- | ---------------------- | ----------- |
  | `file`     | `Buffer \| Uint8Array` |             |
  | `filename` | `string`               |             |
  | `mimeType` | `string`               |             |
</Accordion>

Returns an <TypeTooltip name="UploadResult" type={`interface UploadResult {
readonly mediaId: string;
}`} />. Save the `mediaId` only long enough to use it — WhatsApp expires uploaded media after \~30 days.

## Get URL

Resolve an inbound `mediaId` to a signed, time-limited URL plus metadata:

```ts theme={null}
for await (const event of client.events.subscribe()) {
  if (event.type !== "message") continue;
  if (event.message.content.type !== "image") continue;

  const { url, mimeType, fileSize, sha256 } = await client.media.getUrl(
    event.message.content.media.id,
  );

  // Fetch the bytes yourself
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.WA_ACCESS_TOKEN}` },
  });
  const bytes = await response.arrayBuffer();
}
```

<Accordion title="MediaUrlResult" description="">
  | Field      | Type     | Description |
  | ---------- | -------- | ----------- |
  | `fileSize` | `number` |             |
  | `mimeType` | `string` |             |
  | `sha256`   | `string` |             |
  | `url`      | `string` |             |
</Accordion>

The URL is signed by Meta and requires your access token in the `Authorization` header when you fetch it.

## Delete

```ts theme={null}
await client.media.delete(mediaId);
```

Removes the uploaded file from Meta's storage. Safe to skip if you're happy to let the 30-day expiration handle cleanup — delete explicitly when you need the space freed immediately (e.g. compliance requirements).

## Supported MIME types

WhatsApp restricts which types are accepted per content category. The SDK passes whatever you give it — validation happens server-side.

| Category | Common types                                                                        |
| -------- | ----------------------------------------------------------------------------------- |
| Image    | `image/jpeg`, `image/png`                                                           |
| Video    | `video/mp4`, `video/3gpp`                                                           |
| Audio    | `audio/aac`, `audio/mp4`, `audio/mpeg`, `audio/amr`, `audio/ogg` (voice notes only) |
| Document | Any MIME — PDFs, spreadsheets, archives                                             |
| Sticker  | `image/webp`                                                                        |

Check the [official limits table](https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media) for current size caps — they change per account tier.
