How to integrate tinymce 8 with rails 7+ and stimulus (step-by-step tutorial)

Learn how to integrate tinymce 8 in rails 7+ using stimulus. step-by-step guide with self-hosted setup, clean configuration, and best seo practices for developers.

Introduction

Are you looking to integrate TinyMCE 8 into your Rails 7+ application with Stimulus? TinyMCE 8 brings modern, robust, and highly customizable rich-text editing, but using it in Rails can be tricky if you want a self-hosted setup.

In this tutorial, we will show you step-by-step how to:

  • Self-host TinyMCE 8 in Rails

  • Load TinyMCE via Stimulus controllers

  • Avoid deprecated warnings like forced_root_block

  • Properly configure plugins, themes, and skins

By the end of this guide, you’ll have a fully functional, production-ready TinyMCE editor integrated seamlessly into your Rails application.


Step 1: Download TinyMCE 8

  1. Visit the TinyMCE Self-Hosted Download

  2. Download the latest TinyMCE 8 ZIP package.

  3. Extract it into your Rails project under public/vendor/tinymce:

    mkdir -p public/vendor/tinymce

    Recommended Folder Structure

    public/vendor/tinymce/
    ├── tinymce.min.js
    ├── plugins/
    │   ├── advlist/plugin.js
    │   ├── autolink/plugin.js
    │   └── ... other plugins
    ├── themes/
    │   └── silver/theme.js
    ├── skins/
    │   ├── ui/oxide/skin.min.css
    │   └── content/default/content.min.css
    ├── models/
    │   └── dom/model.js
    ├── icons/
    │   └── default/icons.js

    ⚡ Tip: Use content.min.css instead of content.css to avoid 404 errors in Rails.

Step 2: Generate a Stimulus Controller

Generate a Stimulus controller for TinyMCE:

rails generate stimulus tinymce

This will create app/javascript/controllers/tinymce_controller.js.


Step 3: Configure TinyMCE in Stimulus

Update your tinymce_controller.js:

import { Controller } from "@hotwired/stimulus";
import "tinymce";

export default class extends Controller {
  static targets = ["textarea"];

  connect() {
    this.initializeEditor();
  }

  disconnect() {
    this.destroyEditor();
  }

  initializeEditor() {
    if (typeof tinymce === "undefined") {
      console.warn("TinyMCE is not loaded.");
      return;
    }

    const textarea = this.textareaTarget;
    if (!textarea || tinymce.get(textarea.id)) return;

    tinymce.baseURL = "/vendor/tinymce";

    tinymce.init({
      target: textarea,
      skin_url: "/vendor/tinymce/skins/ui/oxide",
      content_css: "/vendor/tinymce/skins/content/default/content.min.css",
      license_key: "gpl", // open-source license
      body_class: "tinymce-content",
      plugins: [
        "advlist", "autolink", "lists", "link", "charmap", "preview", "anchor",
        "insertdatetime", "fullscreen", "image", "code", "searchreplace",
        "wordcount", "visualblocks", "visualchars", "directionality", 
        "nonbreaking", "media", "help"
      ],
      toolbar: "undo redo | blocks | link | table | searchreplace | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image | removeformat | help",
      height: textarea.dataset.height || "600px",
      valid_elements: "*[*]", // allow all HTML
      setup: (editor) => {
        editor.on("change", () => {
          clearTimeout(this.saveTimeout);
          this.saveTimeout = setTimeout(() => {
            textarea.value = editor.getContent();
          }, 300);
        });

        editor.on("init", () => {
          editor.setContent(textarea.value);
        });
      },
    });
  }

  destroyEditor() {
    const editor = tinymce.get(this.textareaTarget?.id);
    if (editor) editor.remove();
  }
}

✅ TinyMCE 8 no longer requires forced_root_block, so removing it avoids console warnings.


Step 4: Add TinyMCE to Your Views

Use a textarea with the Stimulus controller attached:

<textarea data-controller="tinymce" data-tinymce-target="textarea" data-height="400px"></textarea>

Step 5: Test Your Editor

  1. Start the Rails server:

  1. Navigate to the page with your textarea. You should see a fully functional TinyMCE editor with all plugins and toolbar options working.


Step 6: Optional Customizations

  • Toolbar customization:toolbar: "undo redo | bold italic | alignleft aligncenter alignright | bullist numlist"

  • Dynamic height:<textarea data-controller="tinymce" data-tinymce-target="textarea" data-height="500px"></textarea>
  • Add or remove plugins depending on your app’s needs.


Common Issues & Fixes

Issue Fix
CSS 404 errors Use content.min.css instead of content.css
forced_root_block warning Remove the option entirely in TinyMCE 8
License warning Add license_key: "gpl" in the init config
Plugin 404 errors Ensure all plugins are copied into public/vendor/tinymce/plugins

FAQ

Q: Can I still use forced_root_block: false?
A: TinyMCE 8 no longer requires it. Omitting it entirely is cleaner and prevents warnings.

Q: Should I host TinyMCE via importmap or public/vendor?
A: For Rails 7, self-hosting under public/vendor is recommended for full control, offline support, and correct asset loading.

Q: Can I customize skins and themes?
A: Yes. You can change skin_url and content_css to use your own styles.


Conclusion

Following this tutorial, you now have a modern TinyMCE 8 editor in Rails 7 with Stimulus, fully self-hosted, with:

  • All plugins and skins working

  • Easy customization via Stimulus

This setup ensures fast loading, SEO-friendly content editing, and maintainability in production Rails apps.