Working with .erb templates in a plugin

AI-generated summary

angus provides a guide on working with .erb templates in Discourse plugins, assuming prior knowledge of Rails and .erb basics. The guide covers three main techniques: (1) Server plugin outlets, which let you inject custom .erb templates into Discourse’s existing templates using <%= server_plugin_outlet [name] %>, as shown in the chat plugin’s unsubscribe_options outlet. (2) HTML builders, for dynamically generating HTML directly in .erb templates via <%= build_plugin_html [name] %>, registered with register_html_builder in plugin.rb — example: the solved plugin’s server:before-head-close-crawler. While client-side HTML can technically be added this way, it’s discouraged; client outlets should be used instead. The register_custom_html method exists but is not recommended unless necessary. (3) Overriding templates, achieved by prepending your plugin’s view path to a controller’s view_paths — standard Rails behavior. For example, to override app/views/email/unsubscribed.html.erb, place your template at your-plugin-folder/app/views/email/unsubscribed.html.erb and add EmailController.prepend_view_path File.expand_path("../views", __FILE__) to plugin.rb. Global view paths are unnecessary in most cases. Links to Rails guides and plugin examples are provided for further reading.

I’ve been giving some advice on how to work with .erb templates in Discourse recently and noticed there isn’t much detail on this here on meta, so I thought I’d start this wiki on the subject. This wiki assumes you know what .erb templates are and what their function is in Discourse, and Rails more broadly. If you want more details on that background please read this first.

Server plugin outlets

Server plugin outlets work in a similar fashion to client plugin outlets, albeit there are only a few of them. They let you insert your own .erb template into a discourse/discourse .erb template. They are defined like this:

<%= server_plugin_outlet [name] %>

For an example see the use of the unsubscribe_options outlet in the chat plugin:

HTML builders

HTML builders are designed for standard HTML that is dynamically built, instead of using templates and plugin outlets. To use HTML builders in .erb templates look for these insertion points:

<%= build_plugin_html [name] %>

You can insert HTML at these points by returning it to a register_html_builder block in your plugin.rb, i.e.

register_html_builder([name]) do |controller|
   ## build and return your custom HTML
end

For an example see the use of server:before-head-close-crawler in the solved plugin:

You can actually register html builders to add client-side html as well, however this is not recommended. Add client-side html using the client-side plugin outlets and templates.

In a similar vein there is also a server-side plugin api method called register_custom_html, which can be used for similar purposes, however unless you have specific reason to do so, and you know what you’re doing, I wouldn’t use it.

Overriding a template

You can override .erb templates from a plugin by adding your plugin’s views to a controller’s view_paths. This is just Rails, and is not Discourse specific. How this works is covered in the rails guides here:

For example, if you want to override app/views/email/unsubscribed.html.erb, you should to do two things:

Structure your plugin views using the same namespace

Your template should be here:

your-plugin-folder/app/views/email/unsubscribed.html.erb

Prepend your plugin views path to the relevant controller

In this case the relevant controller is EmailController. Add the following to your plugin.rb file:

EmailController.prepend_view_path File.expand_path("../views", __FILE__)

You could add your plugin’s views to the global view paths, however there’s no need to in most cases.

8 Likes