Displaying Multiple Authors for Entries in ExpressionEngine

ExpressionEngine doesn’t let you display multiple authors for a single entry. Here’s a workaround that doesn’t require any add-ons.
ExpressionEngine Logo

When adding a new entry in ExpressionEngine, one of the things that you must specify is its author, i.e., the registered member who created the entry and to whom it “belongs.” By default, the “Author” field is located under the “Options” tab, though it might be in a different location based on your channels’ publish layouts. This field contains a list of all members who have the necessary access and editing privileges for the channel in question. (These are controlled by their member roles.)

However, EE only lets you specify a single author for any given channel entry. This probably isn’t an issue most of the time, since an entry’s author is more of a behind-the-scenes system requirement. But there are scenarios where you want to display an author’s name and information on the site’s front-end. (For instance, if the site in question is a blog or news site with bylines.) Furthermore, it’s not too hard to imagine posts and news articles that have multiple authors, i.e., collaborative pieces where multiple individuals contributed and should have their bylines displayed.

Given EE’s single-author limitation, then, what can you do for multi-author entries? EEHarbor’s “User” add-on may be one potential solution, thanks to its “related authors for entries” feature. There is, however, another way to display multiple authors for a single entry, and that’s using EE’s built-in “relationships” field — assuming you’re OK with a few caveats, which I’ll detail later.

(On a sidenote, my other favorite CMS — Craft — currently doesn’t support multi-author entries either, but it’s on the roadmap for v5.0.)

As its name suggests, EE’s “relationships” field allows you to relate an entry in one channel to one or more entries in other channels. It’s one of EE’s power features, and it unlocks a lot of possibilities for content design, structuring, and management. Although it’s for an older version of EE, this video provides a good overview of how relationships work.


Setting Up the Channels

Let’s say we’re building a site with a blog and we want to be able to assign multiple authors to individual blog posts. This requires, at minimum, two channels:

  • “Blog,” which contains our “blog post” entries. This is the “parent” channel and its short name is blog.
  • “Blog Authors,” which contains entries for each “blog post” author. This is the “child” channel and its short name is blog_authors.

Alongside all of the other fields in the “Blog” channel’s publish layout, we’ll add a “relationships” field titled “Authors” (with a short name of authors) and set it to relate to the “Blog Authors” channel. A “relationships” field has a bunch of settings, but the most important one for our purposes is “Allow multiple relationships?,” which should be enabled. When adding/editing an entry in the “Blog” channel, we’ll now be able to select — i.e., relate to — as many “Blog Authors” entries as we’d like.

As for the “Blog Authors” channel, we can add whatever fields we want for author profiles, including bio, avatar, contact info, and related links. This isn’t too dissimilar from how EE’s default member profiles work. Default member profiles can also have custom fields, but they’re limited to just a handful of fieldtypes. However, we can use all of the EE’s fieldtypes for the “Blog Authors” channel — which grants you a lot of freedom when determining what author profiles should look like.


Setting Up the Templates

Now that our channels are set up, we can begin adding entries. We’d probably start by adding all of our authors to the “Blog Authors” channel. Once we have our authors list, we can then begin creating blog posts and assigning (i.e., relating) authors to them. But how do we render all of this on the front-end?

Displaying a Single “Blog” Post

We’ll start by creating a new template group called blog. It has an index template by default, which we’d probably use for the blog’s main page. Therefore, we’ll add a new template called post that’ll be used to display a single “Blog” entry via this simplified code:

{exp:channel:entries channel="blog" url_title="{segment_3}" dynamic="no"}

	<article class="post">
	
		<header class="post-header">
		
			<h1 class="post-title">{title}</h1>
			
			<div class="post-date">
				{entry_date format="%F %j, %Y"}
			</div>
			
			<div class="post-authors">
			
				{authors}
					<div class="post-author">
						<a href="/blog/author/{authors:url_title}">{authors:title}</a>
					</div>
				{/authors}
			
			</div>
		
		</header>
		
		<div class="post-content">
		
			[Output content fields here]
		
		</div>
	
	</article>

{/exp:channel:entries}

We start with an {exp:channel:entries} tag that lets us pull in info from the blog channel for the entry in question, which is identified via the url_title parameter. The url_title="{segment_3}" assumes that the entry’s unique URL title (i.e., its “slug”) is always the third URL segment (i.e., https://domain.com/blog/post/slug). This can, of course, be adjusted to match your site’s URL structure.

We then begin displaying the entry’s info and content (e.g., title, post date). But the key to displaying the authors is this bit:

<div class="post-authors">
			
	{authors}
		<div class="post-author">
			<a href="/blog/author/{authors:url_title}">{authors:title}</a>
		</div>
	{/authors}
			
</div>

The {authors} tag pair tells EE to begin pulling in the content for the “Authors” field. Since it’s a multi-relationship field, these tags will loop through and display any related “Blog Authors” entries that were selected for the “Blog” entry being displayed. Each author’s title (i.e., name) will be displayed inside a “post-author” <div> and link to their profile page, which will list all of their posts.

You’ll notice the {authors:url_title} and {authors:title} tags, which return the URL title and title for each related entry. To prevent EE from getting confused, they have an authors prefix that identifies them as “author” fields, i.e., fields belonging to the “Blog Authors” channel. Without them, {url_title} and {title} would just return the URL title and title for the “Blog” entry itself rather than its related authors, which would obviously be incorrect.

Displaying the Author’s Profile Page

Moving on to the post author’s profile page — which will be displayed via a template called author — we basically do the inverse of the above. Instead of displaying all of the authors for a single entry, we’re going to display all of the entries for a single author.

{exp:channel:entries channel="blog_authors" url_title="{segment_3}" dynamic="no"}

	<h1>All Blog Posts by {title}</h1>
	
	{embed="blog/_author_posts" posts="{parents:entry_ids field="authors"}"}

{/exp:channel:entries}

Initially, this looks very similar to the code for displaying a single “Blog” entry. The {exp:channel:entries} tag pulls in the info for the author in question, with the url_title="{segment_3}" parameter assuming that the profile page’s URL looks like https://domain.com/blog/author/slug (which can be modified). But after we display the author’s name (i.e., the {title} field), things get a little weird.

You’re probably expecting a loop that displays all of the author’s posts. Instead, there’s an {embed} tag with a posts parameter, the value of which has been set to {parents:entry_ids field="authors"}. The {parents:entry_ids} tag returns a delimited list of IDs — e.g., 300|301|305|472 — for all of the “Blog” entries that this author is related to via the “Authors” field. (The parents prefix is used because, as you’ll recall, the “Blog” channel is the “parent” channel in our post/author relationships.)

Looking at the _author_posts template that’s embedded by the {embed} tag, it contains the following code:

{exp:channel:entries channel="blog" entry_id="{embed:posts}" limit="10" dynamic="no" paginate="bottom"}

	{if count == 1}
		<ul>
	{/if}
	
	<li>
		<a href="{url_title_path=blog/post}">{title}</a>
	</li>
		
	{if count == total_results}
		</ul>
	{/if}
	
	{paginate}
		<div class="pagination">
			{pagination_links}
		</div>
	{/paginate}

{/exp:channel:entries}

Note: The preceding underscore in _author_posts makes it a “hidden” template, that is, a standalone template that can’t be rendered on its own. That’s because its contents only make sense when they’re rendered as part of the author template.

The {exp:channel:entries} tag takes the value of the posts parameter from the {embed} tag and passes it into its entry_id parameter, which will return a loop consisting only of those “Blog” entries that match the delimited list of IDs generated by the {parents:entry_ids} tag. We then display the returned posts as an unordered list. And below the list, we display pagination links, in case the author has more than 10 posts assigned to them, as specified by the limit parameter on the {exp:channel:entries} tag.

It’s that last bit — the pagination links — that necessitates using the embedded _author_posts template. There is a simpler syntax for displaying parent entries:

{exp:channel:entries channel="blog_authors"}

	{parents field="authors"}
    	{parents:title}
	{/parents}

{/exp:channel:entries}

However, this code doesn’t allow for pagination, which will probably be necessary for a blog with lots of posts. Hence the embedded template approach.

And that’s really about it. So to review:

  1. We have two channels: “Blog” and “Blog Authors.”
  2. The “Blog” channel has a “relationships” field called “Authors” that’s used to relate a “Blog” entry to one or more “Blog Authors” entries. This establishes a parent/child relationship between the “Blog” entry and any related “Blog Authors” entries.
  3. We use the blog/post template to display a single “Blog” entry. It contains an {authors} tag pair that loops through and displays all “Blog Authors” entries related to the currently displayed “Blog” entry.
  4. We use the blog/author template to display a single “Blog Authors” entry and list all of its parent “Blog” entries.
  5. To display that list of parent “Blog” entries, we embed the blog/_author_posts template in the blog/author template, and pass it a delimited list of all “Blog” entries related to the currently displayed “Blog Authors” entry.

This might seem convoluted at first, and EE’s template syntax is a bit weird (e.g., the field prefixes). But once you get a handle on entry relationships, they can dramatically expand the type of sites you build with EE, and how those sites can be managed by their users.


Caveats and Conclusions

As I mentioned before, there are some caveats to this approach. The biggest caveat is that the authors in question aren’t actually members of the site; they’re just entries in the “Blog Authors” channel. As such, they don’t have any login credentials or permissions, and thus, can’t log in to the site’s admin area.

Put simply, if you want your site’s authors to be able to log in and add/edit their own posts, then this approach to multi-author entries will not work for you. Instead, you’ll probably need to purchase and install the aforementioned “User” add-on. If, however, your site is run by a handful of admins and editors who handle all of the content management, and blog authors just send in their posts to be prepared and published, then this approach could work for you.

Just keep in mind that the entry’s “real” author — the author selected in EE’s default “Author” field — could be someone other than any of the displayed authors. Since we’re not displaying the “real” author on the front-end, this isn’t a technical issue. But it’s worth keeping in mind when training users and developing editorial workflows in order to minimize confusion.

Another caveat is a potential performance hit with embedding the blog/_author_posts template and passing in the list of parent “Blog” channel entry IDs. If your blog has hundreds or thousands of posts, then you might want to keep an eye on performance and implement some more stringent caching as needed.

In my experience, however, the pros — e.g., more flexible content management and author profiles — far outweigh any potential cons. I’ve used this approach on several sites over the years and have never experienced any real downsides. This flexibility, which is part of ExpressionEngine’s core functionality and doesn’t require any third-party add-ons, is yet another reason why I’ve continued to use it over the years.

Enjoy reading Opus? Want to support my writing? Become a subscriber for just $5/month or $50/year.
Subscribe Today
Return to the Opus homepage