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

@0x30-ch/collection-tree

v2.0.2

Published

Strapi plugin to easily sort your collections

Readme

Strapi Collection Tree Plugin

A Strapi 5 plugin that enables hierarchical tree structures and flat sorting for your collections with drag-and-drop. Manage categories, menus, nested content, tags, and any data that benefits from ordered or hierarchical organization.

Plugin Demo License TypeScript Version

Features

  • Hierarchical Tree Structures: Transform any collection into a nested tree using self-referential relations
  • Flat Sorting: Collections without parent/children relations get flat drag-and-drop reordering (root-level only)
  • Drag & Drop Interface: Reorder and nest entries with visual feedback (powered by SortableJS)
  • Tree Connector Lines: Visual L-shaped connectors showing parent-child relationships
  • Expand / Collapse: Per-node and bulk expand/collapse controls (auto-collapses above 100 nodes)
  • Per-Locale Tree Ordering: Manage independent tree orders per locale — a locale dropdown appears when the content type is localized
  • Scope Filtering: Filter tree by published status (published or all)
  • Configurable Label Field: Choose which attribute (title, name, etc.) to display as the node label
  • Full i18n: All UI strings use react-intl for internationalization
  • Content API: Public endpoints for flat and nested tree data retrieval with locale support

Installation

npm install @0x30-ch/collection-tree
# or
yarn add @0x30-ch/collection-tree
# or
pnpm add @0x30-ch/collection-tree

Configuration

1. Add the Custom Field

In the Strapi Content-Type Builder, add the Tree custom field to your collection type. Configure:

  • Label field: Which attribute to show as the node label (e.g., title, name)
  • Scope: Which entries participate in the tree (published or all)

2. Set Up Relations (Optional — for Nesting)

For hierarchical (nested) trees, your content type must have self-referential relations:

  • A manyToOne relation named parent pointing to itself
  • A oneToMany relation named children pointing to itself (inverse of parent)

The plugin uses the join table created by these relations to store sort order. Root node sort order is stored in the tree integer custom field itself.

If no parent/children relations are defined, the plugin works in flat mode — all entries are sorted at root level using only the tree column. The admin UI hides nesting controls (chevrons, child drop zones, expand/collapse all) automatically.

3. Restart Your Server

After configuration, restart your Strapi server to apply the schema changes.

Usage

Admin Interface

  1. Navigate to the Collection Tree page in the admin panel sidebar
  2. Select a collection from the left sidebar
  3. Switch locale using the dropdown in the toolbar (appears only for localized content types with multiple locales)
  4. Drag & drop entries to reorder them at the same level or nest them under a parent (nesting only available when parent/children relations exist)
  5. Expand/collapse nodes using the chevron or the bulk toggle button
  6. Save your changes — order is saved independently per locale

Per-Locale Tree Ordering

Strapi 5 i18n creates separate database rows per locale, so each locale naturally has its own tree order. The plugin:

  • Fetches available locales from the i18n plugin on mount
  • Shows a locale dropdown in the header toolbar when the active content type is localized and multiple locales exist
  • Defaults to the default locale
  • Passes the selected locale to both fetch and save API calls
  • Non-localized content types show no locale dropdown

Admin API

The plugin exposes three admin API endpoints (authenticated via admin::isAuthenticatedAdmin):

| Method | Path | Purpose | |--------|------|---------| | GET | /collection-tree/content-types | List content types with tree fields (includes isLocalized and supportsNesting flags) | | GET | /collection-tree/nodes | Fetch tree nodes (uid, field, labelField, scope, locale params) | | PUT | /collection-tree/nodes | Save tree order (uid, field, nodes[], scope, locale body) |

Content API (Public)

Two public endpoints for retrieving sorted tree data in your frontend:

| Method | Path | Purpose | |--------|------|---------| | GET | /api/collection-tree/flat | Flat list with depth and parentId fields | | GET | /api/collection-tree/nested | Nested structure with children arrays |

Query parameters (both endpoints):

| Param | Type | Description | |-------|------|-------------| | uid | string | (required) Content type UID (e.g. api::category.category) | | field | string | Tree field name (auto-detected if omitted) | | labelField | string | Label attribute to use | | scope | string | published (default) or all | | locale | string | Locale code (e.g. en, fr) — defaults to default locale for localized types | | fields | string | Comma-separated fields to include | | populate | string | Comma-separated relations to populate, or * |

Example:

# Flat list for French locale
GET /api/collection-tree/flat?uid=api::category.category&locale=fr

# Nested tree, published only
GET /api/collection-tree/nested?uid=api::category.category&scope=published&fields=title,slug&populate=image

Development

Prerequisites

  • Node.js >= 18.0.0
  • Strapi 5.x
  • TypeScript support

Setup

git clone https://github.com/0x30-ch/strapi-plugins.git
cd strapi-plugins
pnpm install

# Start development
pnpm dev

# Build plugin
cd packages/collection-tree
npm run build

# Type checking
npm run test:ts:front  # Frontend types
npm run test:ts:back   # Backend types

# Verify plugin structure
npm run verify

Project Structure

packages/collection-tree/
├── admin/                     # Frontend (React + Strapi Design System v2)
│   ├── src/
│   │   ├── components/
│   │   │   └── tree/          # Tree UI components
│   │   │       ├── styles.ts         # Styled-components (connectors, cards)
│   │   │       ├── TreeNodeItem.tsx   # Single node (chevron, handle, label, badge)
│   │   │       ├── TreeList.tsx       # Recursive ReactSortable wrapper
│   │   │       ├── TreeSidebar.tsx    # Left sidebar with content type list
│   │   │       └── TreeContent.tsx    # Main area (header, expand/collapse, tree)
│   │   ├── hooks/
│   │   │   └── useTreeData.ts        # Fetch/save logic + state management + locale
│   │   ├── pages/
│   │   │   └── TreePage.tsx          # Thin orchestrator with locale dropdown
│   │   ├── types/
│   │   │   └── index.ts              # Shared types (ContentTypeSummary, FlatNode, TreeNode, Locale)
│   │   ├── utils/
│   │   │   ├── getTranslation.ts     # i18n helper
│   │   │   └── treeUtils.ts          # Pure functions (buildTree, flattenTree, etc.)
│   │   └── translations/
│   │       └── en.json               # English translations
│   └── tsconfig.json
├── server/                    # Backend logic
│   ├── src/
│   │   ├── controllers/       # API controllers (tree.ts, tree-public.ts)
│   │   ├── services/          # Core service (resolveTreeModel, getNodes, updateNodes)
│   │   ├── routes/            # Route definitions (admin-api.ts, content-api.ts)
│   │   ├── utils/             # Tree helpers
│   │   └── config/            # Plugin configuration
│   └── tsconfig.json
└── package.json

Architecture

Relation-Based Tree Model (Nested Mode)

The plugin uses self-referential relations for hierarchical trees:

  • Parent relation (manyToOne): Each entry can have one parent
  • Children relation (oneToMany): Each entry can have many children (inverse of parent)
  • Join table: Strapi creates a join table for the relation, storing parent_id, child_id, and an order column
  • Root sort order: Root nodes (no parent) store their sort index in the tree custom field column
  • Nested sort order: Child nodes store their sort index in the join table's order column

Flat Mode (No Relations)

When a content type has a tree custom field but no self-referential parent/children relations:

  • All entries are treated as root nodes
  • Sort order is stored solely in the tree custom field column
  • The admin UI disables nesting: no chevrons, no child drop zones, no expand/collapse controls
  • Drag-and-drop reordering works at root level only

Locale Support

  • Strapi 5 i18n creates separate database rows per locale (each with its own id)
  • Join table parent-child relationships are implicitly per-locale since the IDs are locale-specific
  • The tree column (root ordering) is per-row, so per-locale by nature
  • The service accepts an optional locale parameter — when provided, queries filter by locale; when omitted, falls back to the default locale

Scope Support

| Scope | Description | |-------|-------------| | published | Published entries only (default) | | all | All entries (drafts + published) |

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE file for details.

Acknowledgments

  • Strapi - Headless CMS framework
  • SortableJS - Drag-and-drop functionality
  • 0x30 - Original plugin author

Support