Open Wagtail Rich Text Block Links in a New Tab

You'd think being able to set external link targets in rich text blocks to open in a new tab would be something offered out of the box. I put this in as a feature request with Wagtail which was promptly shut down with the explanation that "it's against UX best practice" and that it should be up to the visitor to decide if they want to open in a new tab.

Personally, I find it sloppy when a link to an external site isn't opened in a new tab by default. I've lost my place in the site I was on, and now have to navigate back or reload from history. If the opened page is something I want to keep open and save for later while I continue on the original site, then the new tab is the only route for this. I'm more likely to just not return to the referring page.

Much more importantly, leaving your site to visit another is extremely bad practice from a business/marketing/retention viewpoint where the very aim is to retain the visitor, not to send them elsewhere.


This applies only to links created using the External Link option in Wagtail's built-in rich text block editor (DraftTail). Links that you create in custom blocks or any other way are entirely up to the developer. Since linking from text is such a common feature, this requires extra consideration.


There are two routes that I've come across to circumvent this: adding a site-wide policy via wagtail hooks or dropping in a block of JQuery into the relevant page types.

A note about the rel attribute

When opening a link to an external site, I add to attributes to the <a> element: rel="nofollow noopener"

These are optional and included for the following reasons:

noopener - this closes down an old security flaw and instructs the browser to navigate to the target resource without granting the new browsing context access to the document that opened it by not setting the Window.opener property on the opened window (it returns null). This is included for older browsers where target="_blank" used an opener which was vulnerable to exploit. noopener is now the default behaviour in all major browsers. See here for a discussion on this.

nofollow - instructs web crawlers to not follow the link. A couple of reasons for doing this: it avoids circular references (a link to a page that links back to the current page) which will score negative points on your SEO; it avoids giving SEO to your competitors (not sure why you'd want to link to your competitors, but it's here for completeness).

Opening Links in New Tabs via JQuery

This is a very simple override that amends the attributes of your href a tags if they point to an address using http or https protocols. There are a couple of benefits to using this over a site-wide policy that affects every 'external link':

  1. In a site where there are Django pages alongside Wagtail pages (such as this one), linking to your internal Django pages has to be done using the External Link option since they don't feature in the Page Chooser. These should be opened in the same tab, only opening truly external links in new tabs. The JQuery method allows this, provided you use relative paths for internal URL's. You may also be using the External Link option to point to aliased domains or routable pages, all of which you'd want in the same tab.
  2. You can apply this method only to the page types you want, rather than the entire site.
  3. It doesn't involve messing with Wagtail internals which can potentially break with future updates.
    $('a[href^="http://"]').attr('target', '_blank');
    $('a[href^="http://"]').attr('rel', 'nofollow noopener');
    $('a[href^="https://"]').attr('target', '_blank');
    $('a[href^="https://"]').attr('rel', 'nofollow noopener');

That's all there is to it. I've dropped this into my site-wide js as I don't have any special requirements. It doesn't mess with the OAuth links and leaves my "internal external" Django links alone as well.


Opening Links in New Tabs via Wagtail Hooks

Doing this will apply a site-wide policy on all 'External Links', even those that point to internal links.

Starting with Wagtail 2.5 the following is possible in your

from django.utils.html import escape
from wagtail.core import hooks
from wagtail.core.rich_text import LinkHandler

class NewWindowExternalLinkHandler(LinkHandler):
    # This specifies to do this override for external links only.
    # Other identifiers are available for other types of links.
    identifier = 'external'

    def expand_db_attributes(cls, attrs):
        href = attrs["href"]
        # Let's add the target attr, and also rel="noopener" + noreferrer fallback.
        # See
        return '<a href="%s" target="_blank" rel="noopener noreferrer">' % escape(href)

def register_external_link(features):

This is not compatible with 2.14 though. The unofficial method for pre-2.5 can be:

from draftjs_exporter.dom import DOM
from wagtail.core import hooks
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import ExternalLinkElementHandler, PageLinkElementHandler

def register_extended_link_feature(features):
    feature_name = 'extended_link'
    type_ = 'LINK'

    control = {
        'type': type_,
        'icon': 'link',
        'description': 'Extended link',
        # We want to enforce constraints on which links can be pasted into rich text.
        # Keep only the attributes Wagtail needs.
        'attributes': ['url', 'id', 'parentId', 'rel', 'target'],
        'whitelist': {
        # Keep pasted links with http/https protocol, and not-pasted links (href = undefined).
        'href': "^(http:|https:|undefined$)",

        'draftail', feature_name, draftail_features.EntityFeature(control)

    features.register_converter_rule('contentstate', feature_name, {
        'from_database_format': {
            'a[href]': ExternalLinkElementHandler('LINK'),
            'a[linktype="page"]': PageLinkElementHandler('LINK'),
        'to_database_format': {
            'entity_decorators': {'LINK': extended_link_entity}

def extended_link_entity(props):
    id_ = props.get('id')
    link_props = {}

    if id_ is not None:
        link_props['linktype'] = 'page'
        link_props['id'] = id_
        link_props['href'] = props.get('url')
        # if external link -- add rel="nofollow noopener" and target="_blank"
        link_props['rel'] = 'nofollow noopener'
        link_props['target'] = '_blank'
    return DOM.create_element('a', link_props, props['children'])

Now in your text block declaration, remove the 'link' option and add 'extended_link'.


body = blocks.RichTextBlock(features=['h1', 'h2', 'h3', 'h4', 'bold', 'italic', 'ol', 'ul', 'hr', 'extended_link', 'image'])),


Well, my vote goes with the JQuery option for the reasons listed above. It's simple, non-intrusive and should it break, your links simply open in the same tab. Nothing disastrous. It's the method applied to this website.

If you have any thoughts or questions on this, please feel free to leave them below.