dot CMS

How to fetch related content in dotCMS

How to fetch related content in dotCMS

Share this article on:

Understanding how to retrieve related content after defining your relationships can be complex. With several methods available, choosing the most optimal one can be a challenge.

You're likely curious about which option is the best for you! While there isn't a single, one-size-fits-all answer, the ideal choice truly depends on your unique use case. In this article, we will explore the most commonly used methods and showcase the scenarios where each shines the brightest.

Consider a scenario where you have a Blog that is related to one or more Authors. The content types might look like this:

Content Type

Fields

Blog

title, author (relationship field to Author)

Author

firstName, lastName, blog (relationship field to Blog)

 

And let's assume that we have a Blog called "French Polynesia Everything You Need to Know", which is related to the author “John Smith”.

// Blog Content
{
  "title": "French Polynesia: Everything You Need to Know",
  "identifier": "2b100ac7-07b1-48c6-8270-dc01ff958c69",
  "author": {
    "identifier": "f851e34a-d128-408e-a75a-4fa1c33201b8",
    "firstName": "John",
    "lastName": "Smith"
  }
}
// Author Content
{
  "identifier": "f851e34a-d128-408e-a75a-4fa1c33201b8",
  "firstName": "John",
  "lastName": "Smith",
  "blog": {
    "title": "French Polynesia: Everything You Need to Know",
    "identifier": "2b100ac7-07b1-48c6-8270-dc01ff958c69"
  }
}

How can you get the related content from each side of the relationship? Let's explore the options.

Option 1: Using the GraphQL API

Getting the author (child) having the blog identifier (parent)

GET Request: http://{host}/api/v1/graphql

query AuthorByBlog {
  search( query:"+identifier:2b100ac7-07b1-48c6-8270-dc01ff958c69") {
    title,
    ... on Blog {
      author {
         firstName,
         lastName
      }
    }
  }
}

Result

{
   "data": {
       "search": [
           {
               "title": "French Polynesia Everything You Need to Know",
               "author": [
                   {
                       "firstName": "John",
                       "lastName": "Smith"
                   }
               ]
           }
       ]
   }
}

 

Getting the blog (parent) given the author identifier (child)

GET Request: http://{host}/api/v1/graphql

query BlogsByAuthor {

    BlogAuthorCollection(query: "+identifier:f851e34a-d128-408e-a75a-4fa1c33201b8") {
        firstName
        lastName
        ... on BlogAuthor{
            blog{
                title
            }
        }
    }
}

Result

{
    "data": {
        "BlogAuthorCollection": [
            {
                "firstName": "John",
                "lastName": "Smith",
                "blog": [
                    {
                        "title": "French Polynesia Everything You Need to Know"
                    },
                    //other blogs
                ]
            }
        ]
    }
}

Pros:

  • You can ask only for the fields you need, reducing payload size

  • Nested queries are easy (e.g., blog → author → related blogs)

  • Strong typing helps catch errors early

 

Cons:

 

🎯 Use GraphQL when:

  • You’re building headless frontends with React/Astro/Next.js/etc

  • You need complex nested related content in one request

  • You want better performance by reducing over-fetching

  • You want flexibility and predictable schemas


Option 2: Using the REST APIs

We have multiple ways to retrieve related content using the different dotCMS APIs, but in this case, we will focus on the Content API with the “depth” and “related” parameters.

 

Case I: When “Depth” is important

The “depth” parameter will be helpful when you want to retrieve the original piece of content plus its related content. For example, we will get the author having the blog identifier and specifying the `depth=1` parameter to get related content at the first level (up to 3 for nested relationships).

GET Request: http://{host}/api/v1/content/2b100ac7-07b1-48c6-8270-dc01ff958c69?depth=1

 

Result

{
    "entity": [
        {
            "__icon__": "contentIcon",
            "archived": false,
            "baseType": "CONTENT",
            "contentType": "BlogAuthor",
            "contentTypeIcon": "person",
            "creationDate": 1600352407112,
            "disabledWYSIWYG": [],
            "firstName": "John",
            "folder": "SYSTEM_FOLDER",
            "hasLiveVersion": true,
            "hasTitleImage": true,
            "host": "48190c8c-42c4-46af-8d1a-0cd5db894797",
            "hostName": "demo.dotcms.com",
            "identifier": "f851e34a-d128-408e-a75a-4fa1c33201b8",
            "inode": "da883772-bc17-4adb-9e04-b49ffaafc792",
            "languageId": 1,
            "lastName": "Smith",
            //many other fields
        }
    ],
    "errors": [],
    "i18nMessagesMap": {},
    "messages": [],
    "pagination": null,
    "permissions": []
}

 

Case II: When “Related” is important

This is the case when you want to filter the related content based on a condition. The output will be a bit less verbose than in the previous example, because the details of the original content will be retrieved as well, but filtering the related content by the given criteria. Let's get our blog given the author identifier and an additional condition.

POST Request: http://localhost:8082/api/v1/content/related

//Request Body

{
 "fieldVariable": "blog",
 "identifier": "f851e34a-d128-408e-a75a-4fa1c33201b8",
 "condition": "+title:*Polynesia*" —> to filter our blog only
}

Result

{
    "entity": [
        {
            "author": [
                {
                    "baseType": "CONTENT",
                    "contentType": "BlogAuthor",
                    "firstName": "John",
                    "identifier": "f851e34a-d128-408e-a75a-4fa1c33201b8",
                    "inode": "da883772-bc17-4adb-9e04-b49ffaafc792",
                    "lastName": "Smith",
                    //many other fields
                }
            ],
            "baseType": "CONTENT",
            "identifier": "2b100ac7-07b1-48c6-8270-dc01ff958c69",
            "inode": "4b337160-a5a3-42a7-b272-7b506603cb72",
            "languageId": 1,
            "live": true,
            "title": "French Polynesia Everything You Need to Know",
            "working": true
            //many other fields
        }
    ],
    "errors": [],
    "i18nMessagesMap": {},
    "messages": [],
    "pagination": null,
    "permissions": []
}

Pros:

  • Simple to use

  • Fast for single-content lookups (if you know the identifiers or want easy search queries)

  • You can get all the details of the original piece of content and related content in just one request

 

Cons:

  • Verbose responses because you will get more fields than needed

  • The queries are not as flexible as GraphQL

  • The depth parameter can be heavy and hit performance if not used wisely

 

🎯 Use Rest APIs when:

  • You want quick, simple REST requests.

  • You need to expose content to external systems not using GraphQL.

  • You need relationship resolution but not highly-custom nested queries.


Option 3: Velocity Tools

Case I: Pulling a piece of content and iterating over the related content

 

In this case, we are pulling the blog and iterating over the authors:

#foreach($blog in $dotcontent.pull("+contentType:Blog",3,"modDate desc"))
    <h3>$blog.title</h3>
    #set($authors = $!{blog.author})
    <h4>Author(s):</h4>
    <ul>
        #foreach($author in $authors)
        	<li>$author.firstName $author.lastName</li>
        #end
    </ul>
#end

 

Case II: Pulling related content only

In this case, we pull authors only and iterate over their fields:

#foreach($content in $dotcontent.pullRelated("blog.author","2b100ac7-07b1-48c6-8270-dc01ff958c69",false,10))
    <li>$content.firstName</li>
    <li>$content.lastName</li>
#end

For further details on how to use the velocity viewtool, you can visit the dotCMS documentation.

Pros:

  • Full power of dotCMS server-side logic

  • Best performance when rendering HTML inside dotCMS pages

  • Direct access to contentlet objects and context (user session, permissions, current page context)

 

Cons:

  • Not headless: Tied to dotCMS backend rendering

  • Requires template editing in the CMS

  • Harder to reuse outside the CMS environment

 

🎯 Use Velocity when:

  • You are rendering server-side HTML pages inside dotCMS

  • You want maximum control over related content lookups without exposing APIs

  • You are using Velocity Widgets, Containers, or Templates


Wrapping up

There isn't a “best” way to pull related content. It will always depend on your use cases and the resources you have. There are other ways to pull relationships, but we can cover them in another article. In the meantime, these questions can help you with the decision, but remember that there is no rule of thumb:

1. Where will the content be consumed?

  • Headless frontend (React/Next/Gatsby/mobile)? → GraphQL or ContentAPI

  • dotCMS-rendered site? → Velocity Tools

 

2. Do you need nested related content (e.g., blog → author → related blogs → tags)?

  • Yes → GraphQL or Velocity

  • No → ContentAPI is enough

 

3. Do you need to control exactly which fields come back (to avoid over-fetching)?

  • Yes → GraphQL

  • No → ContentAPI or Velocity

 

4. Does your team have experience with GraphQL or want a typed/introspective schema?

  • Yes → GraphQL

  • No → ContentAPI or Velocity

 

5. Are you building a long-term scalable API layer (vs. a single internal template)?

  • Long-term, flexible, scalable → GraphQL

  • Short-term or CMS-only → ContentAPI or Velocity

Recommended Reading
  • Migrating Your OSGi Plugins to dotEvergreen: Adapting to the New Index API
    24 Mar 26
    Technical Guides

    Migrating Your OSGi Plugins to dotEvergreen: Adapting to the New Index API

    An update on infrastructural changes, information on a breaking change introduced that may affect some plugins, and a migration guide for those affected.

    Fabrizzio

    Fabrizzio Araya

    Software Engineer

  • What Is Rich Text? How It Works in a Headless CMS
    23 Mar 26
    Content Management

    What Is Rich Text? How It Works in a Headless CMS

    What is rich text, and how does it differ from Rich Text Format (.rtf)? Learn how rich text works in content management systems, how headless CMS platforms store it as structured data, and why the format matters for omnichannel delivery.

    Fatima

    Fatima Nasir Tareen

    Marketing Specialist

  • Structured Content for GEO: How dotCMS Powers AI-Ready Digital Experiences
    21 Mar 26
    AI in CMS

    Structured Content for GEO: How dotCMS Powers AI-Ready Digital Experiences

    Discover how dotCMS revolutionizes AI-driven digital experiences with structured content for Generative Engine Optimization (GEO). Learn how our enterprise solution enhances AI visibility, enabling large language models to accurately process and cite machine-readable data. Dive into best practices for creating AI-ready content and explore the benefits of a headless CMS model. Optimize your content for AI discovery and experience seamless omnichannel delivery. Contact us to leverage dotCMS for your AI-powered search needs.

    Fatima

    Fatima Nasir Tareen

    Marketing Specialist

  • AI Content Governance for Content Teams: A Practical Framework
    9 Mar 26
    AI in CMS

    AI Content Governance for Content Teams: A Practical Framework

    Learn why AI content governance is essential for content teams. Discover how to protect brand consistency, reduce legal risk, and manage AI across dozens of sites with dotCMS’s built-in governance tools.

    Fatima

    Fatima Nasir Tareen

    Marketing Specialist

Explore dotCMS for your organization

image

dotCMS Named a Major Player

In the IDC MarketScape: Worldwide AI-Enabled Headless CMS 2025 Vendor Assessment

image

Explore an interactive tour

See how dotCMS empowers technical and content teams at compliance-led organizations.

image

Schedule a custom demo

Schedule a custom demo with one of our experts and discover the capabilities of dotCMS for your business.