Image upload
Send images to your server: a function, a URL, and server examples.
With no setup, images are inlined as base64, which makes posts large. On production sites, upload them. Picking, dragging, pasting and images copied from other sites all go through the same path.
1. Pass an upload function (recommended)
createEditor('#editor', {
imageMaxSize: 10 * 1024 * 1024,
uploadImage: async (file, { onProgress }) => {
const form = new FormData();
form.append('file', file);
const res = await fetch('/upload', { method: 'POST', body: form });
if (!res.ok) throw new Error('Upload failed');
const { url } = await res.json();
return url; // the editor inserts <img src=url>
},
});2. Just a URL
createEditor('#editor', {
uploadUrl: '/upload',
uploadField: 'file',
uploadHeaders: { 'X-CSRF-Token': token },
});
// reply with { "url": "https://…/a.png" }Node (Express + multer)
import express from 'express';
import multer from 'multer';
const upload = multer({ dest: 'public/files', limits: { fileSize: 10 * 1024 * 1024 } });
const app = express();
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file?.mimetype.startsWith('image/')) return res.status(400).end();
res.json({ url: '/files/' + req.file.filename });
});Cloudflare Workers + R2
export default {
async fetch(req, env) {
const form = await req.formData();
const file = form.get('file');
if (!file || !file.type.startsWith('image/')) return new Response('bad', { status: 400 });
const key = crypto.randomUUID() + '.' + file.type.split('/')[1];
await env.BUCKET.put(key, file.stream(), { httpMetadata: { contentType: file.type } });
return Response.json({ url: 'https://files.example.com/' + key });
},
};