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

# Handle Image Uploads in Lumen Editor

> Wire the uploadImage hook, understand file validation rules, and apply server-side safeguards for secure image handling in Lumen Editor.

Lumen Editor delegates image storage entirely to your application — you supply an `uploadImage` hook that receives the selected `File`, uploads it however you like, and returns the final URL that gets embedded in the content. This keeps the editor lightweight and lets you integrate with any storage backend (S3, Cloudinary, your own API, etc.).

## The uploadImage hook

The `uploadImage` option accepts an **async function** with the following signature:

```ts theme={null}
uploadImage: (file: File) => Promise<string>
```

The editor calls this function whenever a user inserts an image — either by clicking the toolbar button or by pasting/dropping a file. Your function receives the raw `File` object and must return a `string` URL that the editor will embed in the content. The editor never stores or exposes the internal blob URL.

```js theme={null}
const editor = new Editor('#editor', {
  uploadImage: async (file) => {
    const form = new FormData();
    form.append('file', file);
    const res = await fetch('/api/upload', { method: 'POST', body: form });
    return (await res.json()).url;
  }
});
```

## Validation rules

Before your `uploadImage` hook is ever called, Lumen Editor runs a three-layer validation pass on the selected file. All three checks must pass, or the upload is rejected.

<CardGroup cols={3}>
  <Card title="MIME type check" icon="file-circle-check">
    The file's `type` property is checked against the `allowedImageTypes` option. Defaults to common web image types.
  </Card>

  <Card title="Magic byte verification" icon="binary">
    The first bytes of the file are read and matched against known PNG, JPEG, GIF, and WebP signatures. MIME alone is not trusted.
  </Card>

  <Card title="Size limit" icon="weight-scale">
    The file size is compared against `maxImageSize`. The default ceiling is **5 MB**. You can raise or lower it as needed.
  </Card>
</CardGroup>

You can configure both size and type constraints via editor options:

```js theme={null}
const editor = new Editor('#editor', {
  maxImageSize: 2 * 1024 * 1024,          // 2 MB
  allowedImageTypes: ['image/png', 'image/jpeg'],
  uploadImage: async (file) => { /* ... */ }
});
```

<Warning>
  Client-side validation is a usability aid, not a security boundary. Always re-validate MIME type, magic bytes, and file size on your server before persisting anything. A malicious actor can bypass browser-side checks entirely.
</Warning>

## Handling upload errors

If your `uploadImage` function throws — or if the editor's own validation rejects the file — an `error` event fires on the editor instance. Listen to it to surface failures in your UI:

```js theme={null}
editor.on('error', ({ code, message }) => {
  console.error(`[${code}] ${message}`);
  showToast(`Image upload failed: ${message}`, 'error');
});
```

Common error codes related to uploads include `UPLOAD_TOO_LARGE`, `UPLOAD_INVALID_TYPE`, `UPLOAD_INVALID_MAGIC` (when the magic byte check fails), and `UPLOAD_FAILED` (when your hook throws or the network request fails).

<Note>
  If your `uploadImage` function returns `null` or `undefined`, the editor silently skips the insertion without firing an error. Make sure your function always returns a non-empty URL string on success, or explicitly throws on failure so the `error` event fires.
</Note>

## Server-side considerations

Even with client-side validation in place, you should treat every incoming upload request as untrusted on the server:

<Steps>
  <Step title="Re-check the MIME type and magic bytes">
    Parse the first bytes of the uploaded stream server-side to confirm the file is what it claims to be. Libraries like `file-type` (Node.js) make this straightforward.
  </Step>

  <Step title="Enforce size limits at the HTTP layer">
    Set a request body size limit in your web framework (e.g. `express.json({ limit: '5mb' })` or equivalent) so oversized files are rejected before they're fully buffered.
  </Step>

  <Step title="Sanitize filenames and paths">
    Strip or replace special characters in the original filename before writing to disk or passing to a storage SDK to prevent path traversal attacks.
  </Step>

  <Step title="Restrict access to the upload endpoint">
    Require authentication on your upload endpoint so only legitimate users can submit files. Return a storage URL that is scoped to your own storage domain rather than a user-supplied path.
  </Step>
</Steps>

<Tip>
  Store uploaded images under a content-addressed path (e.g. a hash of the file content) rather than the original filename. This naturally deduplicates files and eliminates filename collision issues.
</Tip>
