@0x30-ch/collection-tree
v2.0.2
Published
Strapi plugin to easily sort your collections
Maintainers
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.
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 (
publishedorall) - Configurable Label Field: Choose which attribute (
title,name, etc.) to display as the node label - Full i18n: All UI strings use
react-intlfor 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-treeConfiguration
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 (
publishedorall)
2. Set Up Relations (Optional — for Nesting)
For hierarchical (nested) trees, your content type must have self-referential relations:
- A manyToOne relation named
parentpointing to itself - A oneToMany relation named
childrenpointing to itself (inverse ofparent)
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
- Navigate to the Collection Tree page in the admin panel sidebar
- Select a collection from the left sidebar
- Switch locale using the dropdown in the toolbar (appears only for localized content types with multiple locales)
- Drag & drop entries to reorder them at the same level or nest them under a parent (nesting only available when parent/children relations exist)
- Expand/collapse nodes using the chevron or the bulk toggle button
- 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=imageDevelopment
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 verifyProject 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.jsonArchitecture
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 anordercolumn - Root sort order: Root nodes (no parent) store their sort index in the
treecustom 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
treecustom 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
treecolumn (root ordering) is per-row, so per-locale by nature - The service accepts an optional
localeparameter — 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
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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
