Skip to content
← All posts

Reverse Relationships in Statamic: A Single Source of Truth

If entry B references A, why can't A see all the B's that reference it? Statamic Reverse Relationship solves bidirectional traversal without data duplication.

· 5 min read · Sylvester Damgaard
Reverse Relationships in Statamic: A Single Source of Truth

Relational data in a flat-file CMS has a fundamental asymmetry problem. When you add a "Related Articles" field to a blog post and select three articles, that relationship is stored in one direction: the blog post knows about the articles, but the articles have no idea they are being referenced.

In a relational database, you would solve this with a join table or a reverse query. In Statamic, the standard approach is to manually maintain both sides of the relationship, adding a "Referenced By" field to articles and keeping it in sync with every blog post edit. This is tedious, error-prone, and violates the single source of truth principle.

I built Statamic Reverse Relationship to solve this. It lets you query relationships in both directions without duplicating data.

The Problem in Practice

Consider a site with two collections: projects and team_members. Each project has a field called team that references one or more team members.

In your project template, listing team members is straightforward:

antlers
{{ team }}
  

{{ title }} - {{ role }}

{{ /team }}

But on a team member's profile page, you want to show "Projects this person has worked on." Without reverse relationships, you have two options:

Option 1: Maintain both sides manually. Add a projects field to team_members and keep it in sync. Every time you add someone to a project, you also have to update their profile. Editors will forget, data will drift, and you will have inconsistencies.

Option 2: Loop through all projects in the template. Query every project and check if the current team member is in their team field. This works but is slow on large collections and the template logic is ugly:

antlers
{{ collection:projects }}
  {{ team }}
    {{ if id == {current_member_id} }}
      {{# This project references the current member #}}
    {{ /if }}
  {{ /team }}
{{ /collection:projects }}

Neither option is good. Reverse Relationship provides a third way.

The Fieldtype Approach

Reverse Relationship adds a read-only fieldtype that you place on the "target" side of the relationship. On the team_members blueprint, you add a reverse_relationship field configured to look at the team field in the projects collection:

yaml
# resources/blueprints/collections/team_members/team_member.yaml
fields:
  -
    handle: projects_involved
    field:
      type: reverse_relationship
      source_collection: projects
      source_field: team
      display: 'Projects Involved'

In the control panel, this field displays all projects that reference the current team member. It's computed at render time, so there's no data to keep in sync. The single source of truth remains the team field on projects.

In your template:

antlers
{{ projects_involved }}
  {{ title }}
{{ /projects_involved }}

The Tag Approach

If you prefer not to modify blueprints, Reverse Relationship also provides an Antlers tag that you can use directly in templates:

antlers
{{ reverse_relationship
   source_collection="projects"
   source_field="team"
   target_id="{ id }" }}
  {{ title }}
{{ /reverse_relationship }}

This is useful for quick prototyping or cases where adding a field to the blueprint is overkill.

Editable Mode

By default, the reverse relationship fieldtype is read-only. It shows which entries reference the current entry but doesn't let you modify the relationship from the reverse side. This is intentional: the source of truth should be the original relationship field.

However, some editors find it more natural to manage relationships from either side. Editable mode allows this:

yaml
fields:
  -
    handle: projects_involved
    field:
      type: reverse_relationship
      source_collection: projects
      source_field: team
      editable: true

When editable is enabled, editors can add or remove references from the reverse side. Under the hood, Reverse Relationship updates the source entry's relationship field, keeping the single source of truth intact. If an editor adds "Project X" to a team member's profile, the addon actually updates Project X's team field to include that team member.

Filtering and Sorting

The fieldtype supports Statamic's standard filtering and sorting parameters:

antlers
{{ projects_involved sort="date:desc" limit="5" }}
  {{ title }}
  {{ date format="M Y" }}
{{ /projects_involved }}

You can also filter by any field on the source entries:

antlers
{{ projects_involved where="status:active" sort="title:asc" }}
  {{ title }}
{{ /projects_involved }}

Performance

Reverse Relationship queries are executed at render time by scanning the source collection for entries whose relationship field contains the target entry's ID. On flat-file Statamic installations, this means reading entry files. On database-backed installations (using Eloquent), it generates an indexed query.

For flat-file sites with large collections (500+ entries), I recommend enabling Statamic's stache caching. The reverse lookup is fast because Statamic's stache keeps all entry data in memory after the first load. Subsequent requests resolve reverse relationships without touching the filesystem.

Full documentation, including configuration options, template examples, and performance recommendations, is in the Reverse Relationship docs.


// Sylvester Damgaard