Skip to content

Strapi Middlewares


This middleware enriches collection-type API responses in Strapi 5 by automatically injecting two types of related content when applicable:

  1. related – Based on shared tags between the current document and others of the same content type.
  2. latest – The most recently published entries from the same content type.

This is primarily useful for content blocks like Related Articles, Latest News, or More Like This sections.

The middleware kicks in when:

  • The API response contains data (i.e., a collection).
  • The query includes a filters.slug parameter.

It then:

  • Resolves the UID of the collectionType by slug.
  • Detects tag relations (if any) and fetches related entries.
  • Fetches the latest entries as a fallback.
  • Populates key attributes (e.g., hero, salon, function) to allow richer display on the frontend.

In the section:

if (uid === "api::vacancy.vacancy") {
populate["salon"] = { populate: "*" };
populate["function"] = { populate: "*" };
}

You can add more UIDs and define which fields to populate depending on the structure of your content type.

Example:

if (uid === "api::event.event") {
populate["location"] = { populate: "*" };
populate["speakers"] = { populate: "*" };
}

Section titled “2. Customize how related items are selected”

The logic currently checks if the current entry has tags:

if (data[0].tags) {
populate['tags'] = { populate: '*' };
relatedFilter['tags'] = { slug: { $eq: data[0].tags.map((tag: any) => tag.slug) } };
...
}

You can extend this to include other logic, like shared categories or authors.

Example:

if (data[0].category) {
relatedFilter["category"] = { id: data[0].category.id };
}

3. Customize the returned shape of related/latest
Section titled “3. Customize the returned shape of related/latest”

This determines how the frontend receives the related and latest objects:

return {
image: (hero && hero.image) ?? record.image,
title: record.title,
text: hero?.text ?? record.text,
uid: findRoute.uid ?? null,
publishedAt: record.publishedAt,
slug,
};

You can change or add fields here based on your frontend display needs.

Example:

return {
image: hero?.image ?? record.image,
title: record.title,
description: record.description ?? record.text,
slug,
category: record.category?.title,
};

FeatureDescription
relatedAdds similar entries based on shared tags or fallback content
latestAlways includes the latest entries from the same collection
UID detectionUses the link plugin to resolve UID from slug
PopulatesCan be customized per UID to include rich fields (hero, relations, etc.)
Route mappingMatches content entries with frontend slugs from the link service
Easy to extendFully open to customization depending on project needs

Path: apps/cms/src/middlewares/components/teaser.latest-teaser.ts
Middleware Type: transform for components inside flexContent


This middleware enriches the teaser.latest-teaser component inside a dynamic zone (flexContent) with actual content fetched from the CMS.

It reads the component’s latestTeasers property (formatted as a pipe-delimited string like uid|limit|sortBy) and:

  • Dynamically resolves the UID, item limit, and sorting field.
  • Fetches documents of the specified UID from the CMS, sorted accordingly.
  • Auto-populates fields like hero, tags, and flexContent if they exist.
  • Resolves the frontend slug from the link plugin.
  • Replaces the latestTeasers string with an array of teaser objects, ready for rendering.

The buildPopulate(uid) function checks if certain attributes exist in the UID’s schema and only includes those in the populate object:

return {
flexContent: { populate: "*" },
...(attributes.hero && { hero: { populate: "*" } }),
...(attributes.tags && { tags: { populate: "*" } }),
};

You can extend this with more specific or custom logic for your use case.

Example:

...(attributes.category && { category: { populate: '*' } }),

The structure of the returned teaser object is determined here:

return {
...record,
image: record.image ?? hero?.image,
title: record.title ?? hero?.title,
text: record.text ?? hero?.text,
uid,
slug,
};

You can modify this to include or override fields specific to your frontend needs.

Example:

return {
title: record.title,
image: hero?.image,
slug,
summary: record.summary,
};

To use this transform, your component must include a property like:

"latestTeasers": "api::post.post|3|publishedAt"

Where:

  • api::post.post = UID of the collection type
  • 3 = number of items to show
  • publishedAt = field to sort by (descending)

FeatureDescription
Dynamic UIDDetermines which collection to query from a pipe-delimited string
Auto PopulatePopulates hero, tags, flexContent, etc. based on contentType schema
Slug ResolutionUses link plugin to resolve slugs for rendering
Frontend ReadyInjects clean teaser objects into flexContent
ExtensibleEasily extend with more populate fields or teaser structure