npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strapi-plugin-recursive-populate

v1.0.0

Published

Deep recursive population for Strapi content type queries

Readme

Strapi Plugin - Recursive Populate

A Strapi v5 plugin that adds deep recursive population for content type queries. Use the special populate=*.* value to fully populate every relation, component, dynamic zone, and media field — automatically — without hand-writing nested populate objects.

🚀 Features

  • 🌳 Deep, recursive populate for components, dynamic zones, relations, and media
  • 🎯 Triggered by populate=*.* — works in REST API requests and in programmatic strapi.documents() / service calls
  • ⚙️ Configurable max recursion depth and max locale-traversal depth
  • 🪶 Zero admin UI, zero database tables, zero runtime overhead when not invoked
  • 🧩 Headless: enable in config/plugins.ts and you're done

⚙️ Installation

# Using npm
npm install strapi-plugin-recursive-populate

# Using yarn
yarn add strapi-plugin-recursive-populate

🔧 Configuration

Enable and configure the plugin in config/plugins.ts (or config/plugins.js):

export default ({ env }) => ({
  'recursive-populate': {
    enabled: true,
    config: {
      maxDepth: 20,
      maxLocale: 1,
    },
  },
});

Options

| Option | Type | Default | Description | | ----------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | maxDepth | number | 20 | Maximum recursion depth when expanding components / relations / dynamic zones. Higher values populate more, at increased query cost. | | maxLocale | number | 1 | How many levels of relation traversal are allowed before the recursion bottoms out. 0 populates localizations only, no deep relation walk. |

🎯 Usage

REST API

Send populate=*.* as the populate value on any find / findOne request:

GET /api/pages?populate=*.*
GET /api/pages/abc123?populate=*.*

The plugin intercepts the query, builds a fully-recursive populate object based on the content type's schema, and hands it to Strapi's core controller.

Programmatic (services / lifecycle hooks)

const result = await strapi.documents('api::page.page').findOne({
  documentId,
  populate: '*.*',
});

The same populate=*.* marker works through the document service. The plugin's document middleware detects it and rewrites params.populate with the full recursive tree.

Building a populate object directly

If you need the populate object without executing a query (for caching, inspection, or composition):

const populateObj = strapi
  .plugin('recursive-populate')
  .service('population')
  .getFullPopulate('api::page.page');

// Optionally override config values per-call:
const shallow = strapi
  .plugin('recursive-populate')
  .service('population')
  .getFullPopulate('api::page.page', 5, 0);

Wrapping an existing core service

The plugin exposes find and findOne helpers that wrap a core service call with deep-populate handling and reset localizations arrays on the result:

import { factories } from '@strapi/strapi';

export default factories.createCoreController('api::page.page', ({ strapi }) => ({
  async findOne(ctx) {
    const { id } = ctx.params;
    const populationService = strapi.plugin('recursive-populate').service('population');
    const coreService = strapi.service('api::page.page').findOne;

    const data = await populationService.findOne(coreService, 'api::page.page', id, ctx.query);
    return { data };
  },
}));

🧠 How It Works

The plugin walks the requested model's schema (not its data) to build a populate tree:

  • Components are recursively expanded
  • Dynamic zones are expanded per-component into a { on: { ... } } map
  • Relations are expanded as long as maxLocale budget remains
  • Media fields are populated as true
  • admin::user and the related field on plugin::upload.file are excluded

Recursion is bounded by maxDepth (overall) and maxLocale (relation hops). The schema walk is in-memory only — no database hit. The actual query cost is determined by Strapi's normal populate execution after the populate object is supplied.

The plugin runs for any content type that requests populate=*.*. Trusted internal callers benefit from convenience; if populate=*.* is reachable from untrusted clients, gate it at your API layer (route policies, role permissions) just like any expensive query.

🤝 Contributing

Feel free to contribute to this plugin by:

  1. Creating issues for bugs or feature requests
  2. Submitting pull requests for improvements
  3. Providing feedback and suggestions

📄 License

MIT License - Copyright (c) Jorge Pizzati