---
title: External images
description: Load images from another site by listing its address; Charming allows only the addresses you list through the browser security policy that blocks everything else.
sidebar:
  label: External images
  order: 14
---

## What your app can show

**Photos, covers, and icons from other sites.** By default, a Charming app's UI can't show an image hosted somewhere else: the browser's security policy blocks it. Your agent lists the exact web addresses the images come from when it builds or updates the app, and images from those addresses render like any other image.

## Technical

The app declares its allowed image origins in the manifest, and `window.charming.images` handles loading them in the UI.

### Copy this prompt for your agent

```text
Add external images to my Charming app. Read
https://charm.ing/docs/capabilities/external-images.md first. List
the exact HTTPS origins the image URLs come from in the app's
manifest, then load each image with window.charming.images.load()
so it renders in every embed.
If you haven't built a Charming app before, read
https://charm.ing/docs/build-mcp.md (or build-http.md for the HTTP API)
for the app skeleton first.
```

### How an agent performs this job

Your agent lists the exact HTTPS origins the app's images come from in `manifest.permissions.browser["img-src"]` (full origins, not bare hostnames) when it creates or updates the app. Charming appends each origin to the app's `img-src` content security policy; any origin not on the list stays blocked. Wildcards, paths, credentials, loopback, private, and link-local targets aren't allowed. A bad entry rejects `create_app` or `update_app` with `invalid_manifest_schema`.

```ts
permissions: {
  browser: {
    "img-src": ["https://images.unsplash.com", "https://covers.openlibrary.org"],
  },
}
```

In the UI, the agent renders each image through `window.charming.images.load(url)` or `window.charming.images.proxy(url)` rather than a raw `<img src="https://…">`, which fails silently under some embeds' content security policy even when the origin is on the allowlist.

### The contract

```ts
window.charming.images.load(url); // Promise<string> - resolves to a data: URL
window.charming.images.proxy(url); // string - same-origin proxy URL
```

- `window.charming.images.load(url)` fetches the image through Charming's server-side image proxy and resolves to a `data:` URL. It works in every embed, including Claude and ChatGPT, both of which inject their own outer content security policy that forbids a cross-origin `<img src>` but permits `data:`. Prefer this whenever the app may be embedded.
- `window.charming.images.proxy(url)` returns the same-origin proxy URL. It works standalone, but not inside either Claude's or ChatGPT's inline embed.

### Limits, access, and deletion

- **Allowlist enforcement.** Both `load` and `proxy` check the `permissions.browser["img-src"]` allowlist on Charming's server; the proxy refuses an undeclared host even when called directly. Neither bypasses the allowlist.
- **Undeclared origin.** An image URL whose origin isn't on the allowlist doesn't render, and a raw `<img>` tag to it fails with no visible error: the image just stays broken.
- **Finding broken images.** Blocked `<img>` requests and other content-security-policy violations surface as `diag_report` events on `/app/<id>/activity`, so you can find them without waiting for a user report.

## Related

- [Data storage](data-storage)
- [Limits](limits)
- [How Charming works](../concepts/how-charming-works)
- [Docs home](..)
- [llms-full.txt](https://charm.ing/docs/llms-full.txt)

Found a bug or need a feature? [Tell us](/docs/capabilities/feedback) with `submit_feedback` or `POST /app/{id}/feedback`. Your feedback shapes what we build next.
