Conversation
Injects relationship fields into related collections and computes sanitized relatedCollections with fieldName and hasMany info. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Field injection happens after all collections are sanitized but before sidebar tabs are added. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update types to not allow `taxonomy: true`
- Simplify typeof checks throughout codebase
- Update sanitizeTaxonomy to not normalize boolean
- Update relatedCollections default to {} instead of []
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove runtime field traversal functions - Use sanitized relatedCollections with fieldName and hasMany - Include fieldInfo in response for client-side pagination Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove runtime field traversal. Use sanitized relatedCollections with pre-computed fieldName and hasMany values. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use fieldInfo passed from server instead of traversing fields client-side. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Users now declare `taxonomy.relatedCollections` as an explicit object
with config per collection: `{ posts: { hasMany: true } }`
- Payload auto-injects relationship fields (`_t_<taxonomySlug>`) into
related collections during config sanitization
- Moved `injectTaxonomyFields` call to before collection sanitization
loop so injected fields are properly sanitized
- All relationship metadata is computed once at sanitization time,
eliminating runtime field traversal
- Removed boolean `taxonomy: true` support - config must be an object
- Updated tests to use auto-injected `_t_tags` field name
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make root breadcrumb always clickable when viewing nested taxonomy items - Allow last StepNav item to be a link if it has a URL - Add mergeCheckboxHeader prop to SlotTable for spanning Name header across checkbox and content columns - Remove unused replace prop from StepNavItem type Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add URL-based search with proper debouncing and page reload prefill - Use startRouteTransition for loading state during search navigation - Update breadcrumbs to show TagIcon + collection label - Search state now persists in URL query params Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add color prop to TagIcon with 'muted', 'dark', 'default' variants - Apply flex layout with gap for step nav icon + label - Use muted color for TagIcon in taxonomy list breadcrumbs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace hasMany prop with fieldOverrides for full relationship field customization
- fieldOverrides accepts all SingleRelationshipField props except name, type, relationTo
- Remove buildTaxonomyRelationshipField helper (inlined in injectTaxonomyFields)
- Export new types: TaxonomyRelatedCollectionConfig, TaxonomyRelationshipFieldOverrides
- Update test config to use new fieldOverrides pattern
BREAKING CHANGE: taxonomy.relatedCollections now uses fieldOverrides instead of hasMany
Before: { posts: { hasMany: true } }
After: { posts: { fieldOverrides: { hasMany: true } } }
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace magic field injection with explicit field creation:
- Add createTaxonomyField() helper function for creating taxonomy relationship fields
- Change relatedCollections from object config to simple string[] of collection slugs
- Add validateTaxonomyFields() to validate fields exist and throw helpful errors
- Remove injectTaxonomyFields() - no more magic field injection
New API:
```typescript
// Taxonomy collection:
taxonomy: {
relatedCollections: ['posts', 'pages'],
}
// Related collection - add field explicitly:
fields: [
createTaxonomyField({ taxonomySlug: 'tags', hasMany: true }),
]
```
Benefits:
- Users see exactly what fields are in their collections
- Full control over field placement and ordering
- Clear error messages if field is missing
- No magic injection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…mentButton - Add allowHasMany config option to taxonomy to control relationship cardinality - Refactor TaxonomyViewData types and rename relatedDocuments to relatedDocumentsByCollection - Add CreateDocumentButton component for multi-collection create dropdown - Improve TaxonomyList with empty states, single-item edit, and show all related collections - Cache sidebar tab content with Activity component for better performance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add dynamic property to SidebarTab config type - Mark hierarchy tabs as dynamic so they reload with fresh baseFilter - Add SidebarTabsProvider for tab control functions - Remove stale detection logic from HierarchySidebarTab
- Fix cannot-collapse bug: contextSeededRef tracks whether context has been seeded since last navigation, preventing fallback to initialExpandedNodesProp after user collapses all nodes - Fix children flash on expansion: useChildren returns effectiveChildren (children ?? cache fallback) so cache pre-populated by Tree's useMemo is used synchronously before state is set - Fix tree clearing after refreshTree: loadRootNodes effect fetches root nodes when initialData is unavailable post-refresh - Fix post-refresh navigation flashes: effectiveInitialData blocks only the stale snapshot captured at refresh time; fresh initialData from router.refresh() is used once it arrives - Add TreeNode useEffect to auto-load children when expanded with no data, handling both programmatic expansion and post-refresh remounts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace refreshTree() + router.refresh() race with a direct call to useSidebarTabs().reloadTabContent(), which re-runs HierarchySidebarTabServer and replaces the tab with fresh initialData in one step. Removes all complexity that worked around the race: - treeRefreshKey / effectiveInitialData blocking in HierarchySidebarTab - loadRootNodes effect in Tree - TreeNode useEffect for post-refresh child loading Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vider HierarchyListHeader is in the main content area, outside SidebarTabsProvider, so useSidebarTabs() returns null there. Instead, HierarchySidebarTab (which renders inside the provider) watches treeRefreshKey and calls reloadTabContent on itself when it changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… tree Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sContext Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
If there are 200 items in a folder, does this paginate? |
It's a "load more" approach currently, so yes. Might bring pagination in if we need to. It would have to be per table which could flood the url. It's definitely an option if we need it. |
|
Hi @JarrodMFlesch, thank you for the incredible work on this! I've pulled down the branch and tested it a bit. I have some questions and feedback: Questions
Feedback
Thanks again, appreciate any response! |
I'm not actually sure. I know we were not initially planning on this. I will have to think through how this would work. If you have any thoughts or input I am all ears! Im not sure how you would configure this either.
I agree, I thought about this while I was building it out. It would be nice to still have the ability to get to the default list view on these collections.
Good call, it would be nice to have this.
Also sounds like a good idea, I feel like there was a reason I did not do this to start, but agree that we should look into it.
Yes, I was thinking it might be nice to have the fields have like a "generate" or "show" type of toggle that would allow them to populate. I don't really want to query the document with the flag.
Yes, internally I do this by selecting the fields. But we could be smart about it probably and select the fields we know we will need.
Than you for your feedback! |
- Collections with sidebar tabs are automatically hidden from nav - Collections with explicit `group` appear in both nav and sidebar tab - Add e2e tests for sidebar tab visibility behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Allows using a dedicated field (e.g., 'slug') for _h_slugPath instead of slugifying the title. Supports localized slug fields. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…parameter - Add beforeOperation hook to detect select queries with path fields - Auto-include parent/title/slug fields needed for path computation - Strip auto-added fields from response to respect user's select - Add computeHierarchyPathsViaSelect context flag for afterRead Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@jhb-dev update here, I added support for items 3, 4, 5, 7 from above. Still thinking about 6. |
|
Hi @JarrodMFlesch, thanks for implementing the feedback! Regarding the view mode switch on the collection page, I see the intent behind "by {collection_label}", but I'm not sure users will immediately understand what it means. The concatination also feels fragile for localization. What do you think of these three view modes:
The way I think about it: hierarchy is the underlying data model (a collection with a parent field), while tree and folder are two distinct views/use cases built on top of it. For example, a media collection benefits most from the folder view, while tags or web pages are better served by a tree view. Making these explicit view modes would let users pick the one that fits the mental model of the collection. |
| }), | ||
| position: 'sidebar', | ||
| }, | ||
| hasMany: false, |
There was a problem hiding this comment.
Adding filterOptions: ({ id }) => (id ? { id: { not_equals: id } } : true) here to exclude the current document from the dropdown would be a nice UX improvement.
The beforeChange hook prevents self-referencing at the data level anyway.
| /** | ||
| * beforeOperation Hook Responsibilities: | ||
| * - Detect when path fields (_h_slugPath, _h_titlePath) are selected | ||
| * - Auto-include required fields (parent, title, slugField) for ancestor traversal | ||
| * - Track auto-added fields in context so afterRead can strip them from response | ||
| */ |
There was a problem hiding this comment.
Nice addition! I implemented something similar in my plugin.
One thing to watch out for: this currently only handles select include mode, not exclude.
But ideally this would be solved at the framework level for all virtual fields, not just hierarchy. See #14468
There was a problem hiding this comment.
ooo the force select fn is interesting!
Hierarchy Feature
This PR introduces a comprehensive hierarchy system for Payload that enables collections to have parent-child relationships with automatic path generation, dedicated sidebar navigation, and specialized UI components for folder and tag patterns.
Overview
The hierarchy feature allows any collection to define parent-child relationships through a declarative
hierarchyconfig property. When enabled, Payload automatically handles relationship management, path computation, circular reference prevention, and injects a dedicated sidebar tab with a tree view for navigation.At its core, hierarchy manages a
parentFieldNamerelationship field that references documents within the same collection. The system automatically creates this field if it doesn't exist, adds validation to prevent circular references (you can't move a folder into its own subfolder), and computes virtual path fields that provide breadcrumb-style paths from root to each document.Path Generation
Two virtual fields are automatically added to hierarchy-enabled collections:
_h_slugPathand_h_titlePath. These compute breadcrumb paths by walking up the parent chain to root during read operations. For example, a document nested three levels deep might have paths likeengineering/frontend/componentsandEngineering / Frontend / Components. Path computation is cached per-request to avoid redundant ancestor queries, and usesoverrideAccess: trueto ensure complete paths even when users lack read permission on intermediate ancestors.The field names are customizable via
slugPathFieldNameandtitlePathFieldNamein the hierarchy config. Path generation also respects localization, returning localized strings when the collection uses localized title fields.Sidebar Tabs
The hierarchy feature builds on a new sidebar tabs system that allows rendering custom tabs alongside the default Collections tab. Each hierarchy collection automatically gets its own tab injected during config resolution. The tab displays a tree view of the hierarchy with expand/collapse functionality, search, and optional collection-type filtering.
When you click a node in the tree, the list view filters to show only that node's children and related documents. The URL updates with a
?parent=<id>parameter, and the tree highlights the currently selected node. Expanded node state persists across sessions via payload-preferences.Helper Functions
Four helper functions simplify common hierarchy patterns:
createFoldersCollectioncreates a folder-style hierarchy where each document can have only one parent (single-select). It enforcesallowHasMany: false, hides the collection from the main nav (folders are accessed via their sidebar tab), sets a default folder icon, and enables the miller columns header button by default.createFolderFieldcreates a relationship field for assigning a single folder to documents in other collections. The field renders as a header button that opens a miller columns drawer for folder selection instead of the standard relationship dropdown.createTagsCollectioncreates a tag-style hierarchy where documents can have multiple parents (multi-select by default). This is useful for categorization systems where items can belong to multiple categories.createTagFieldcreates a relationship field for assigning multiple tags to documents. Unlike folder fields, tag fields use the standard relationship UI with hierarchy-aware features.Setup
To add hierarchy to a collection, add the
hierarchyproperty to your collection config:For folder patterns, use the helper functions:
Migration from Previous Folders Implementation
If you used the previous
foldersconfig key, migration to the new hierarchy system is straightforward. The old system usedpayload-foldersas the collection slug andfolderas the relationship field name. SetparentFieldName: 'folder'on your folders collection to preserve compatibility with existing data:The
parentFieldNamesetting controls the field name across all related collections. When set tofolder, the field created bycreateFolderFieldautomatically gets renamed from its default (_h_payload-folders) tofolderduring config resolution, matching your existing database schema.Collection-Specific Folders
Folders can optionally restrict which collection types they accept. Enable this with
collectionSpecific:This adds a multi-select field to folders where you can specify which collections can be placed in that folder. When filtering the sidebar tree, only folders that accept the current collection type are shown.
Join Field
For querying all children of a hierarchy item (both nested items and related documents from other collections), configure the
joinFieldoption:This creates a virtual join field that aggregates all documents referencing each hierarchy item as their parent.
CleanShot.2026-03-17.at.08.26.22.mp4