Redactor 4 Configuration Guide

Last Updated: January 2026
Related Files:

  • client/js/crm/editors/redactor4.js - Base configurations and plugins
  • app/javascript/controllers/redactor4_init_controller.js - Stimulus initialization
  • app/inputs/redactor_input.rb - SimpleForm custom input

Overview

Redactor 4 is the WYSIWYG editor used throughout the CRM for rich text editing. It supports multiple modes depending on the use case, from full-featured blog editing to minimal note-taking.


Editor Modes Comparison

Feature Blog Email Default Air (Notes) Readonly
Base Options defaultRedactor4Options + aiRedactor4Options emailRedactor4Options defaultRedactor4Options simpleNotesRedactor4Options Any + disabled
Min Height 2000px 500px 300px 100px Inherited
Pathbar Hidden Hidden Hidden Hidden Hidden
Source Code Via data attr Via data attr Via data attr No No
Toolbar External (#redactor-blog-toolbar) Sticky (72px offset) Sticky (72px offset) Air (floating) Hidden
CSS Injection WWW styles CodeMirror custom CSS None None None
AI Tools
Context Menu

Plugin Availability by Mode

Plugin Blog Email Default Air Description
alignment Text alignment
counter Word/character count
fullscreen Fullscreen editing
imageresize Visual image resizing
fontcolor Text color
fontfamily Font family
fontsize Font size
blockcolor Block text color
blockbackground Block background
blockborder Block borders
blockfontsize Block font size
wyimage WY Image Library picker
wyembed oEmbed (YouTube, etc.)
wyvideo WY Video Library picker
wyfaq WY FAQ Library picker
ai OpenAI text/image tools
mergetag Liquid merge tags
clips Email snippets
variable Variable insertion
email Email formatting
wyemailblocks WY Header/Footer blocks

Mode Detection Logic

The Stimulus controller (redactor4_init_controller.js) detects which mode to use:

// In initializeRedactor4()
const isEmailEditor = editor.id === 'redactor-email-template-body-v4' || 
                      (this.hasEmailValue && this.emailValue);
const isBlogEditor = (this.hasBlogValue && this.blogValue) || 
                     editor.id === 'redactor-blog';
const isAirMode = this.hasAirValue && this.airValue;

Priority Order:

  1. Air Mode - data-redactor4-init-air-value="true"
  2. Email Mode - editor.id === 'redactor-email-template-body-v4' OR data-redactor4-init-email-value="true"
  3. Blog Mode - editor.id === 'redactor-blog' OR data-redactor4-init-blog-value="true"
  4. Default Mode - Fallback

Usage Examples

1. Blog Editor (Full Featured)

Used in: app/views/crm/posts/_content.html.erb

<div data-controller="redactor4-init" 
     data-redactor4-init-blog-value="true"
     data-redactor4-init-source-value="<%= current_user&.admin? %>">
  <%= f.input_field :solution, 
      id: 'redactor-blog', 
      class: 'form-control',
      data: { 
        redactor4_init_target: 'editor',
        www_css: webpack_css_url('www').to_json
      } %>
</div>

Blog-specific features:

  • External toolbar in sticky header (#redactor-blog-toolbar)
  • WWW CSS injected for WYSIWYG preview
  • 2000px minimum height
  • AI tools, FAQ picker, Video picker enabled
  • blog-post-body class applied for styling

2. Email Template Editor

Used in: app/views/email_templates/_form.html.erb

<div data-controller="redactor4-init" 
     data-redactor4-init-email-value="true">
  <%= f.text_area :body, 
      id: 'redactor-email-template-body-v4',
      class: 'form-control',
      data: { redactor4_init_target: 'editor' } %>
</div>

Email-specific features:

  • Liquid merge tags (recipient, sender, etc.)
  • Email clips (header, footer snippets)
  • WY Email Blocks plugin
  • Custom CSS injection from CodeMirror
  • Paste converts to plain text

3. Default Editor (Standard)

Used in: Most forms via SimpleForm input

<%= f.input :description, as: :redactor %>

Or manually:

<div data-controller="redactor4-init">
  <%= f.text_area :body, 
      class: 'form-control',
      data: { redactor4_init_target: 'editor' } %>
</div>

4. Air Mode (Minimal Notes)

Used in: Activity notes, comments

<div data-controller="redactor4-init" 
     data-redactor4-init-air-value="true">
  <%= f.text_area :notes, 
      class: 'form-control',
      data: { redactor4_init_target: 'editor' } %>
</div>

Air mode features:

  • Floating toolbar (appears on selection)
  • Minimal buttons: bold, italic, deleted, list
  • No plugins loaded
  • Plain text paste

5. Readonly Preview

Any mode can be made readonly:

<div data-controller="redactor4-init" 
     data-redactor4-init-readonly-value="true">
  <%= f.text_area :preview, 
      class: 'form-control',
      data: { redactor4_init_target: 'editor' } %>
</div>

Stimulus Controller Values

Value Type Description
blog Boolean Enable blog mode with AI, FAQ, Video plugins
email Boolean Enable email mode with merge tags
air Boolean Enable minimal air mode (floating toolbar)
minHeight String Override minimum height (e.g., "500px")
plugins Array Override plugin list
source Boolean Enable HTML source editing (admin only)
readonly Boolean Disable editing, hide toolbar
legacyBody String Legacy v3 content for migration

Custom WY Plugins

WY Image (wyimage)

Image picker that integrates with the Image Library. Uses oEmbed API for rich HTML insertion.

Features:

  • Image search and selection
  • Visual cropping with Cropper.js
  • All ImageKit transformations (resize, rotate, blur, etc.)
  • Responsive srcset generation
  • Caption support
  • Re-editing existing images

Inserted HTML structure:

<figure class="wy-image-embed" data-wy-image-id="123" data-wy-image-options="{...}">
  <img src="..." srcset="..." alt="..." loading="lazy" class="img-fluid">
  <figcaption class="figure-caption">Caption here</figcaption>
</figure>

WY Video (wyvideo)

Video picker that integrates with the Video Library. Uses oEmbed API.

Blog only - Not available in email or default modes.

WY FAQ (wyfaq)

FAQ picker that embeds FAQ accordion blocks. Uses oEmbed API.

Blog only - Not available in email or default modes.

WY Embed (wyembed)

Enhanced oEmbed for external content (YouTube, Vimeo, etc.).

Not available in email mode - Email clients don't support iframes.

WY Email Blocks (wyemailblocks)

Quick insertion of branded email header/footer.

Email only - Not available in other modes.


Configuration Files

Base Configurations (redactor4.js)

// Exported configurations:
window.defaultRedactor4Options   // Standard editor
window.emailRedactor4Options     // Email templates
window.aiRedactor4Options        // AI + wyfaq + wyvideo (merged for blog)
window.simpleNotesRedactor4Options  // Air mode

Paste Settings

Default/Blog Mode:

  • clean: true - Sanitize for XSS protection
  • plaintext: false - Keep HTML formatting
  • stripAttr.allowedClasses - Whitelist of Bootstrap 5.3 + blog classes
  • stripAttr.allowedAttributes - Safe attributes only (no style)

Email Mode:

  • plaintext: true - Convert to plain text

Air Mode:

  • plaintext: true - Convert to plain text

CSS Class Handling

The paste settings include a whitelist of allowed CSS classes for security:

  • Blog classes: blogLeft, blogRight, blog-cta, blog-post-body, etc.
  • Bootstrap 5.3: Spacing, display, flexbox, text, colors, tables, etc.
  • Redactor alignment: wrap-center, wrap-left, wrap-right, etc.

See defaultRedactor4Options.paste.stripAttr.allowedClasses for full list.


Troubleshooting

Editor not initializing

  1. Check browser console for errors
  2. Verify Stimulus controller is connected: 📝 Redactor4 Stimulus controller connected
  3. Check if element has correct data attributes
  4. Verify lazy loading: 📝 Redactor 4 module loaded

Toolbar missing

  1. For blog: Check #redactor-blog-toolbar exists in DOM
  2. For email/default: Check toolbar.stickyTopOffset is correct (72px for CRM navbar)
  3. For readonly: Toolbar is intentionally hidden

Plugins not working

  1. Check plugin is in the correct options set
  2. Check browser console for plugin errors
  3. Verify plugin JS is imported in redactor4.js

Images not cropping correctly

  1. Verify Oembed::ImageProvider is handling crop params
  2. Check that crop dimensions aren't being upscaled beyond their size
  3. Verify ImageKit transformation string is correct

Related Documentation