Image

image.create#

Type: false | function

Default: false

Replaces the built-in image insert UI. When set, image.open calls your function instead of opening the default dropdown.

const app = Redactor('#entry', {
  image: {
    create(app) {
      // open your own picker / modal
    }
  }
});

image.edit#

Type: false | function

Default: false

Replaces the built-in image edit UI. When set, image.edit calls your function with the current image block.

const app = Redactor('#entry', {
  image: {
    edit(app, block) {
    }
  }
});

image.upload#

Type: false | string | 'base64' | function

Default: false

Enables file upload for images.

Modes

  • URL string — POST to your endpoint with FormData.
  • 'base64' — no server upload; images are converted client-side to data URLs.
  • Custom function — full control over the upload.

Required for drag-and-drop and clipboard paste of image files. When upload is off, pasted HTML with images is still inserted (existing src values are kept).

Endpoint upload

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
  }
});

The endpoint should return JSON.

Success — single image

{
  "url": "/uploads/photo.jpg"
}

url or src is required (public image URL). Optional per-image fields: id, alt, caption, width, height, srcset, link (href when the image is wrapped in <a>).

Success — multiple images

Object with one entry per file (keys are arbitrary):

{
  "file-0": { "url": "/uploads/a.jpg", "id": "abc" },
  "file-1": { "url": "/uploads/b.jpg", "id": "def" }
}

Or an array:

[
  { "url": "/uploads/a.jpg" },
  { "url": "/uploads/b.jpg" }
]

Each item must be an object with url or src.

Error

Any object with a truthy error property is treated as failure (upload:error, image.error callback):

{
  "error": true,
  "message": "File type not allowed"
}

Custom upload function

const app = Redactor('#entry', {
  image: {
    upload(uploader, { data, files, e }) {
      // data — FormData (already contains files under image.name)
      // files — FileList from input / drop / paste

    fetch('/api/upload', { method: 'POST', body: data })
      .then((res) => res.json())
      .then((json) => uploader.complete(json, e))
      .catch((err) => uploader.complete({ error: true, message: String(err) }, e));
    }
  }
});

Base64 mode

const app = Redactor('#entry', {
  image: {
    upload: 'base64',
    convertFormat: 'image/webp',
    convertQuality: 0.8
  }
});

No HTTP request; complete receives an array like [{ name: 'photo.webp', url: 'data:image/webp;base64,…', base64: true }].

image.url#

Type: boolean

Default: true

Shows the By URL tab in the image dropdown and allows inserting an image by pasting a direct image URL.

When image.url is the only insert source enabled (upload and select are off), the dropdown opens as a simple URL form without tabs.

const app = Redactor('#entry', {
  image: {
    url: false,
    upload: '/upload/'
  }
});

image.select#

Type: false | string | object

Default: false

Enables the Select tab — a grid of thumbnails loaded from a server or from inline data.

  • URL string — JSON endpoint; request method from image.selectMethod
  • Object / array — preloaded list of images
const app = Redactor('#entry', {
  image: {
    select: '/api/images.json'
  }
});

JSON format

The endpoint (or inline value) must be a JSON array or an object whose values are image entries. Each entry is an object:

Field Required Description
url yes* Full-size image URL inserted into the document (<img src>)
thumb yes* Thumbnail URL shown in the select grid. If omitted, url is used
id no Stored on the image block
alt no alt attribute
caption no Figure caption; adding one converts a p/div wrapper to <figure>
link no Wraps the image in <a>
width no width attribute
height no height attribute

Array (typical for a json file):

[
  {
    "thumb": "/uploads/photo-1-thumb.jpg",
    "url": "/uploads/photo-1.jpg",
    "id": "photo-1",
    "alt": "Alternative text",
    "caption": "My caption",
    "link": "https://example.com"
  },
  {
    "thumb": "/uploads/photo-2-thumb.jpg",
    "url": "/uploads/photo-2.jpg",
    "id": "photo-2",
    "width": "300",
    "height": "100"
  }
]

Object (arbitrary keys):

{
  "photo-1": { "thumb": "/uploads/a-thumb.jpg", "url": "/uploads/a.jpg" },
  "photo-2": { "thumb": "/uploads/b-thumb.jpg", "url": "/uploads/b.jpg" }
}

Inline preload:

const app = Redactor('#entry', {
  image: {
    select: [
      { thumb: '/thumb.jpg', url: '/full.jpg', alt: 'Photo' }
    ]
  }
});

image.selectMethod#

Type: string

Default: 'get'

HTTP method for the image.select endpoint ('get' or 'post').

const app = Redactor('#entry', {
  image: {
    select: '/api/images.json',
    selectMethod: 'post'
  }
});

image.name#

Type: string

Default: 'file'

Form field name for the uploaded file in multipart requests.

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
    name: 'my-file'
  }
});

image.data#

Type: false | object

Default: false

Extra fields merged into upload FormData (CSRF token, folder id, etc.).

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
    data: { folder: 'content' }
  }
});

image.drop#

Type: boolean

Default: true

Allows dragging image files into the editor. Requires image.upload to be enabled.

Set to false to ignore file drops.

const app = Redactor('#entry', {
  image: {
    drop: false
  }
});

image.multiple#

Type: boolean

Default: true

Allows selecting and uploading more than one file at a time in the upload tab and drag-and-drop handler.

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
    multiple: false
  }
});

image.clipboard#

Type: boolean

Default: true

Handles pasted image files from the clipboard. Requires image.upload for file blobs.

When image.upload is a server URL, pasted data:image/... inside HTML is uploaded and replaced with returned URLs before insert. In base64 mode, data URLs are kept.

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
    clipboard: false
  }
});

image.types#

Type: string[]

Default: ['image/*']

Allowed MIME types for the file input accept attribute.

const app = Redactor('#entry', {
  image: {
    types: ['image/jpeg', 'image/png', 'image/webp']
  }
});

image.tag#

Type: string

Default: 'figure'

Wrapper tag for new image blocks (upload, insert, bare <img> without a wrapper). Existing wrappers from content or paste (<p>, <div>, <figure>) are preserved.

When a caption is added in the image edit form, a p or div wrapper is converted to <figure> and all wrapper attributes are kept.

const app = Redactor('#entry', {
  image: {
    tag: 'div'
  }
});

Type: boolean

Default: true

Shows the Link and Open link in new tab fields in the image properties form.

When false, link UI is hidden.

const app = Redactor('#entry', {
  image: {
    link: false
  }
});

image.width#

Type: boolean

Default: false

Controls the Width field in the image edit form.

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
    width: true
  }
});

image.height#

Type: boolean

Default: false

Controls the Height field in the image edit form.

const app = Redactor('#entry', {
  image: {
    upload: '/upload/',
    height: true
  }
});

image.convertFormat#

Type: string

Default: 'image/webp'

Target format for client-side image conversion (base64 upload mode and clipboard re-encoding).

const app = Redactor('#entry', {
  image: {
    upload: 'base64',
    convertFormat: 'image/avif'
  }
});

Use MIME types:

image/webp
image/jpeg
image/jpg
image/png
image/avif

image.convertQuality#

Type: number

Default: 0.8

Quality for lossy conversion (0–1), used with image.convertFormat.

const app = Redactor('#entry', {
  image: {
    upload: 'base64',
    convertFormat: 'image/avif',
    convertQuality: 1
  }
});

outset#

Type: object | false

Default:

{
  none: 'none',
  left: 'outset-left',
  both: 'outset-both',
  right: 'outset-right'
}

Preset map for the Outset dropdown on image blocks. Keys are UI ids; values are CSS classes applied to the image wrapper.

Outset shifts the image outside the text column — left, right, or both sides — using layout classes from the editor stylesheet (outset-left, outset-right, outset-both). Width and offset use the --rx-outset CSS variable (default 26px).

Set to false to hide the Outset control in the image dropdown and context bar.

const app = Redactor('#entry', {
  outset: false
});

Custom class names:

const app = Redactor('#entry', {
  outset: {
    none: 'none',
    left: 'my-outset-left',
    both: 'my-outset-both',
    right: 'my-outset-right'
  }
});

wrap#

Type: object | false

Default:

{
  none: 'none',
  left: 'float-left',
  center: 'wrap-center',
  right: 'float-right'
}

Preset map for the Wrap dropdown on image blocks. Keys are UI ids; values are CSS classes on the image wrapper.

Default classes from the editor stylesheet:

  • float-left / float-right — float the image beside text (max-width: var(--rx-float-width), margins via --rx-float-margin)
  • wrap-center — centers the image and caption inside the block
  • none — removes wrap alignment

Set to false to hide the Wrap control in the image dropdown and context bar.

const app = Redactor('#entry', {
  wrap: false
});

Custom class names (add matching rules in your content CSS):

const app = Redactor('#entry', {
  wrap: {
    none: 'none',
    left: 'img-float-left',
    center: 'img-center',
    right: 'img-float-right'
  }
});

wrapWithStyle#

Type: false | boolean | object

Default: false

When enabled, Wrap applies inline styles instead of CSS classes from the wrap map.

  • true — use defaults: margin: 1em, marginBottom: 1em, maxWidth: 200px
  • object — same fields, any value can be overridden

For float left / float right, styles are set on the image block wrapper: float, maxWidth, marginBottom, and marginLeft or marginRight (side margin depends on float direction). Previous float styles and wrap classes are cleared first.

Center and none still use inline text-align / margin on the wrapper and image (no float).

const app = Redactor('#entry', {
  wrapWithStyle: true
});
const app = Redactor('#entry', {
  wrapWithStyle: {
    margin: '1.5em',
    marginBottom: '0.75em',
    maxWidth: '240px'
  }
});