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

# Image uploads

> Shrink an image in the browser and upload it to your app's image endpoint.

`uploadImage(endpoint, file)` reads an image file, scales it down to at most 1600 px on its longest edge, and posts it to an image endpoint as a data URI. It resolves with where the image now lives.

```tsx theme={null}
import { uploadImage, ImageUploadError } from '@cskn/sdk';
import { FilePicker, useToast } from '@cskn/ui';

function CoverUpload({ onUploaded }: { onUploaded: (url: string) => void }) {
  const toast = useToast();

  return (
    <FilePicker
      accept="image/*"
      onSelect={async ([file]) => {
        try {
          const image = await uploadImage('/apps/blog/images', file);
          onUploaded(image.url);
        } catch (error) {
          if (error instanceof ImageUploadError) {
            toast.error(
              error.reason === 'tooLarge' ? 'That image is too large.'
              : error.reason === 'unreadable' ? 'That file is not an image we can read.'
              : 'Upload failed. Try again.',
            );
          }
        }
      }}
    />
  );
}
```

## Contract

The endpoint receives `{ image: "<data URI>" }` and must answer `{ image: { url, width, height, bytes } }`. The server does the real compression, producing a much smaller WebP than a browser canvas can and applying the photo's EXIF orientation, so portrait photos do not arrive sideways.

## Errors

`uploadImage` throws `ImageUploadError` rather than resolving empty, so a failed upload is never silently dropped. `error.reason` is one of:

| Reason | Cause |
| - | - |
| `unreadable` | The file is not an image, or the browser cannot decode it. |
| `tooLarge` | The server answered `413`. |
| `failed` | Any other failure. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.