Overview

How Redactor configuration works: where options come from, how they merge, and how to read or change them from core code, modules, and plugins.

How to set options#

Pass a plain object to the editor:

const app = Redactor('#entry', {
  lang: 'en',
  debug: true,
  toolbar: {
    buttons: ['bold', 'italic', 'link']
  },
  plugins: {
    counter: { plugin: 'counter' }
  }
});

You can also set defaults for every instance of the editor with Redactor.settings (see below).

After startup, change options at runtime with app.setOption(path, value).

Merge order#

On init, Redactor builds one options object:

  1. Built-in defaults (defaultOptions)
  2. Options from the host element data-* attributes (if present)
  3. Options passed to Redactor(selector, options)
  4. Redactor.settings (global overrides — highest priority)

Nested objects are merged deeply.

Extending objects and arrays#

Objects

To change one nested key, pass only that branch. Other keys from defaults stay intact:

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

Arrays

Arrays are replaced, not appended. If defaults define toolbar.buttons: ['bold', 'italic'] and you pass buttons: ['link'], the result is ['link'] only.

To extend an array, build the full list yourself:

const buttons = ['bold', 'italic', 'underline', 'link'];

const app = Redactor('#entry', {
  toolbar: { buttons }
});

Replace a whole object

By default, nested objects are merged. To replace an entire object instead of patching it, add __replace: true on that object:

const app = Redactor('#entry', {
  popups: {
    __replace: true,
    control: { set: true }
  }
});

__replace applies only to that object level. Child objects inside it still merge normally unless they also use __replace.

Global settings#

Set shared defaults once before creating editors:

Redactor.settings = {
  lang: 'en',
  https: true
};

const app = Redactor('#entry', {
  lang: 'de' // overridden by Redactor.settings if also set there
});

Redactor.settings is merged last, so it overrides constructor options and element attributes.

Reading options in modules and plugins#

Inside any module or plugin you have access to the editor instance as this.app.

Dot-path read (returns fallback if missing):

const url = this.app.getOption('ai.url', false);
const transport = this.app.getOption('ai.transport');

Check if an option is enabled (not undefined, null, or false):

if (this.app.isOption('autosave')) {
  // autosave is on
}

Full options object:

const options = this.app.getOptions();

Changing options in modules and plugins#

At runtime (updates the live options tree):

this.app.setOption('ai.url', '/api/ai');
this.app.setOption('ai.model', 'gpt-4o-mini');

setOption accepts dot paths and creates missing intermediate objects.

Plugins may also update their own this.options when the value is plugin-local and does not need to live in the global tree. For anything other code might read (or that should survive in getOptions()), prefer setOption under the plugin key:

this.app.setOption('review.role', 'author');

Plugin options#

Each plugin can define defaults:

export class MyPlugin {
  static defaultOptions = {
    limit: 100,
    showInToolbar: true
  };

  constructor(app, options = {}) {
    this.app = app;
    this.options = options;
  }
}

Enable a plugin and pass options in two equivalent ways (they are merged together). After your plugin script calls Redactor.addPlugin('myPlugin', MyPlugin) (or you call it yourself for a custom plugin):

const app = Redactor('#entry', {
  plugins: ['myPlugin'],
  myPlugin: {
    limit: 50,
    showInToolbar: false
  }
});

Or with an object map:

const app = Redactor('#entry', {
  plugins: {
    myPlugin: {
      plugin: 'myPlugin',
      options: { limit: 50 }
    }
  },
  myPlugin: {
    showInToolbar: false
  }
});

Merge order for a plugin key (myPlugin):

  1. PluginClass.defaultOptions
  2. plugins.myPlugin.options
  3. Root-level myPlugin: { ... }

The result is stored on app.options.myPlugin and passed to the plugin constructor as this.options.

The plugin instance is also available as app.myPlugin (same key as in plugins).

Shared (core) options in plugins

Plugins read core settings through app.getOption() — toolbar, image upload, lang, etc.

const classname = this.app.getOption('classname');
const debounce = this.app.getOption('sync.debounce');

Plugin-specific values use this.options or app.getOption('pluginKey.optionName') after merge.

Options from element data attributes#

Redactor can read configuration from the host element (the same node you pass to the constructor).

Single options via data-redactor-*

Use one attribute per option. The part after data-redactor- maps to an option name in camelCase:

<div
  id="entry"
  data-redactor-lang="en"
  data-redactor-debug="true"
  data-redactor-min-height="200px"
></div>

Maps to lang, debug, minHeight.

Boolean strings ("true" / "false") and numeric strings are converted automatically. Values that look like JSON objects or arrays are parsed.

Full object via data-redactor-options

Put a JSON object on the element for many settings at once:

<div
  id="entry"
  data-redactor-options='{"lang":"en","toolbar":{"buttons":["bold","italic"]}}'
></div>

Constructor options and Redactor.settings override element attributes when the same keys are set in multiple places (see merge order above).

Option reference by area#