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:
Requires GraphQL knowledge
Performance risk on large nested queries
🎯 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>
#endFor 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