In our previous article, we went over the basics of how Drupal handles revisions and content moderation. But one of Drupal's strengths is what we call "structured content" and its ability to implement complex content models, as opposed to a big blob of HTML in a WYSIWYG field. Entities can have lots of different fields. Those fields can refer to other entities that also have lots of other fields. It is easy to establish content relationships.
Even with Drupal websites that don't have complex content requirements, there are almost always a few entity reference fields that carry a lot on their shoulders. An article might reference other related articles. An album might reference an "artist" content type. A biography might reference an "address" content type.
And to make things easier for editors, Drupal also has tools to allow inline editing of this referenced content within the context of a parent entity. But this lays potential, unexpected traps. When using content moderation and revisions with structured content, there are some dangers involved that you should be aware of.
Implementation approaches for structured content
The more structured we have our content, the more responsibility we take on to make sure we implement that structured content responsibly.
We won't go over details about why structured content is preferable in modern content-rich sites and will assume you have already decided to move away from the everything-in-a-single-field as much as possible. For our purposes, “structured content” will mean a set of relationships between “components” that constitute the pieces of your content.
In Drupal, when we want to create entity relationships, there are several implementation options, each with its pros and cons. We will focus here on two of the most popular implementation approaches: Entity reference fields and Paragraphs.
Entity reference fields are probably the most common way of relating two entities together. Drupal core does that extensively. Examples of this are: when assigning Users as authors to Nodes, in file or image media fields, when using Taxonomy Terms, etc. This means that often the “components” of your content will likely be an entity of some sort, and you will probably be using entity_reference fields to put your components together.
Another prevalent approach to creating structured content is the Paragraphs contributed module. The Paragraphs module lets you pre-define your components (called “paragraphs” under the hood), and by doing so, you ensure their appearance is consistent when rendered. Content editors can choose on-the-fly which paragraph types they want to use when creating the page, and you know the components will always look the same. We will get into more details about this option later.
Challenges when moderating structured content and inline editing
Consider one of the simplest and most common content modeling scenarios: a page (node) with an entity_reference field to another node. Let’s assume the main page is a “Bio” profile page, and the component we are interested in is called “Location.”
Note on implementation choices for your components: Using nodes as the data storage mechanism for components that don’t have a standalone version (page) is common but requires additional contributed modules, such as micronode, rabbit hole, etc. Other approaches and modules that don’t use nodes are equally valid, such as using core Content Blocks, the micro-content module, or even custom entities that you create in code. However, for the purposes of this example, all of these approaches are equivalent since they all use an entity-reference field to relate the host entity with the target entity (component).
By default, Drupal core doesn’t provide a great UX for inline editing. For example, the entity reference field only comes with an autocomplete widget by default, which means that when creating a Bio node, we aren’t able to finish the page unless the Location we want to use is already created.
We can add inline editing of referenced entities through different contributed modules, and Inline Entity Form and Entity Browser are the most popular solutions. If we configure Inline Entity Form, for example, we will get a node form similar to the one below:
The whole UX could still arguably be improved, but for the sake of this example, let’s assume this is what our editors usually work with. After creating a first published version of our page, we would have something like:
Sometime after this page is published, the editor needs to perform a few modifications, which will require review and approval before going live. Content moderation to the rescue! They can just create a new draft, right?
If we don’t pay close attention, everything seems to have worked as expected, since when we save the form, we see the /node/123/latest
version of that page, which is a forward (unpublished) revision, and this indeed contains all changes we expect to get approved before they go live:
However, if we log out and visit the currently-published version of this page, we see that the new office location for the bio is already live. That's not what we wanted.
We edited a draft. So how did the changes leak to the live version?
Well, it turns out that is indeed the expected behavior. Here is what happened.
When the editor clicks on the “Edit” button, they are opening the edit form of that referenced node entity. Once entity_reference
fields only store information about the entity type and entity ID, the action being performed really is “let’s modify the default revision of this referenced entity, and save that as a new default revision.” This is the same as if they went to /node/45/edit
and edited the location node there. Editing the referenced entity like this is almost never what you want to do in the context of using an inline form because it will:
- Affect this (re-usable) component everywhere it may be being used
- Even if it’s not being used elsewhere, in this scenario, it will change the location entity default revision, so the published content referencing it will reflect the changes immediately.
How to mitigate or reduce the risk of this happening on your site
There is no one-size-fits-all solution for these dangers, but you can minimize the risk.
Train your editors
If your editors understand how revisions and moderation workflows work, they can more easily work around CMS limitations when necessary. For example, in this case, it might be enough just to remove the reference to that particular Location component and create a new node instead. When the main page draft is published, it will display the new node instead of the old one. Admittedly, this is not always possible or desirable in all scenarios and teams.
Avoid inline editing when moderation workflows are in place
If editors have to go to the standalone form to modify the referenced content, this might make it more visible that the changes could affect more instances than desired.
Use helper contributed modules to reduce confusion
There are modules created to help editors know better the repercussions of the editorial changes. For example, the Entity Usage module can be configured to display a warning message in the edit form when we are altering content referenced from other places. Additionally, the Entity Reference Preview module helps editors preview unpublished content that references items that also have forward revisions.
Architect your implementation to account for the scenarios your editors will find
Maybe none of the mitigation ideas mentioned above are enough for you, or you need a more robust way to guarantee your inline edits in moderated content will be safe regardless of the editor’s skills. In this case, you might want to consider stopping the use of entity_reference fields to point to entities as components and start using the Paragraphs module instead.
What is different with Paragraphs?
The Paragraphs module still creates entities to store your components' data, but the difference is that it enforces that a given revision of the component (paragraph) is always tied to a specific revision of the host entity (parent). In fact, this is often referred to by developers as a “composite entity,” meaning the paragraph itself is only ever expected to exist “in the context of its parent.”
This solves our problem of inline editing moderated content nicely since when we create a new draft of the parent content, we will also be generating new draft revisions of all components on the page, which will always travel together through the moderation workflow.
This also has some downsides you should consider when choosing your implementation model. For example, in a paragraphs scenario, your components can’t be re-used directly. You will need to create one-off versions of a given component every time you need to place it on a page. Also, depending on your content model, if you have deep nesting levels of components inside components, the UX for inline editing might be tricky. On sites with a high volume of data, this could lead to large database tables since all revisions of all components will be created independently.
Conclusion
If you have to take one thing from this read, it should be “be careful with inline editing of referenced content in entity_reference fields when using content moderation.” This is a high-risk scenario that you should discuss early in the project with all stakeholders and plan accordingly. Unfortunately, there is no one-size-fits-all solution, and you should create your Drupal architecture to best serve the use cases that matter for your site users.
Page-building strategy is a complex subject that we haven’t explored in depth here either. Layout Builder options, embedding content in WYSIWYG areas, re-usability of components, media handling, translation workflows, theming repercussions, decoupled scenarios, etc., are all topics you should have in mind when deciding on a page-building approach. Understanding how revisions, entity_reference fields, and content moderation all play together is a good start.
Lullabot has helped plan and build editorial workflows for organizations of all shapes and sizes so that Drupal helps them work toward their content goals. If you want proven help navigating these issues, contact us.