Richer Text


Custom Embeds is a JSON only feature

Richer Text requires JSON storage to be used to support Custom Embeds

has_richer_text :body, store_as: :json

Custom Embeds are almost a requirement these days in modern apps. If you’ve used ActionText this will feel familiar but also a bit different at the same time.

Due to Tiptap using a strict schema, we can’t embed just any HTML into the document or parts Tiptap doesn’t understand will be stripped out. To get around this, inside the text editor, we use an iframe that loads a embeds endpoint to render the content inside. This keeps everyone happy at the same time.


Setting up a model

class Post < ApplicationRecord
  include RicherText::Embeddable

  has_richer_text :body, store_as: :json

  # Optionally you can override this method per model
  # Defaults to `to_partial_path` if not defined.
  def to_embeddable_partial_path

In this example, we’ve included our concern which defines two methods, to_embeddable_partial_path which will be used when we render inside the iframe, and outside the iframe when we render the JSON… and embeddable_sgid which returns a signed global id with our purpose so we can look up the object later at render time.

For this example, Richer Text would now look for a partial at app/views/posts/_embeddable_post.html.erb.

The backend setup

Richer Text is different because we require you to setup a route, view and controller for controlling the code that renders in the iframe inside of the Richer Text editor.

By default the editor looks for a path of “/embeds/:sgid”. If you wish to change this, pass "embeds-path": "/your-new-path-here" to the <richer-text-editor> tag/form helper.


  resources :embeds, only: [:show]


class EmbedsController < ApplicationController
  layout "application"

  def show
    @embed = RicherText::Embed.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    render html: "Embed not found", status: :not_found

Feel free to customize which layout file renders around your Embeds controller view. You could choose to make a dedicated Embeds layout if you so choose to.


<%= @embed.html %>

Lastly you’ll need an endpoint for your model that returns JSON for the front-end to load.

Your endpoint should return two keys: “label” and “content”. Label should be anything you want the user to search and filter by, and content should be the erm well content you wish to have inserted into the text editor.

In our case we want to embed a custom <richer-text-embed> html tag we use for identifying custom embeds.

Take for example this Jbuilder template

json.label post.title
json.content "<richer-text-embed sgid=\"#{post.embeddable_sgid}\"></richer-text-embed>"

All you need to do is ensure the content piece renders a richer-text-embed tag with an attribute of sgid, all other attributes will be ignored.

Front-end setup

Paired with Custom Suggestions and Embeds, Richer Text packs a powerful punch.

Once you’ve got your backend endpoints up, and know where you need to point the Custom Suggestion script to return your records… you need to plug it in to the editor.

<%= form.richer_text_area :body, "custom-suggestions": [
    { "name": "posts", "trigger": "!", "path": "/posts.json" }
  ].to_json %>

In this example, we’re using ! as the character to trigger the searching and filtering, it will return a list of posts to filter by then the user can select one.

Upon selection, the JSON’s content attribute will be injected into the Text Editor and your iframe should load properly in the Text Editor. Try saving the Post and seeing your to_embeddable_partial_path content should be rendered in place of the richer-text-embed tag.

That’s all, really. With Richer Text you get full control of the HTML that renders inside and outside of the editor.

Need some inspriation?