Skip to main content
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:
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.
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.

MIME type check

The file’s type property is checked against the allowedImageTypes option. Defaults to common web image types.

Magic byte verification

The first bytes of the file are read and matched against known PNG, JPEG, GIF, and WebP signatures. MIME alone is not trusted.

Size limit

The file size is compared against maxImageSize. The default ceiling is 5 MB. You can raise or lower it as needed.
You can configure both size and type constraints via editor options:
const editor = new Editor('#editor', {
  maxImageSize: 2 * 1024 * 1024,          // 2 MB
  allowedImageTypes: ['image/png', 'image/jpeg'],
  uploadImage: async (file) => { /* ... */ }
});
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.

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:
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).
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.

Server-side considerations

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

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.
2

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.
3

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.
4

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.
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.