Skip to content

feat(ui): add admin.groups for ordering nav groups in the sidebar#17098

Open
devinoldenburg wants to merge 1 commit into
payloadcms:mainfrom
devinoldenburg:feat/admin-nav-group-ordering
Open

feat(ui): add admin.groups for ordering nav groups in the sidebar#17098
devinoldenburg wants to merge 1 commit into
payloadcms:mainfrom
devinoldenburg:feat/admin-nav-group-ordering

Conversation

@devinoldenburg

Copy link
Copy Markdown

What?

Adds an optional admin.groups array that controls the order nav groups render in the admin sidebar. Example:

admin: {
  groups: ['System', 'Animals', 'Food', 'Layout', 'Collections'],
}
  • Adds groups?: (Record<string, string> | string)[] to the top-level admin config type (packages/payload/src/config/types.ts).
  • Adds a 4th groupOrder? parameter to groupNavItems (packages/ui/src/utilities/groupNavItems.ts) and a new exported sortGroupsByOrder helper. When groupOrder is provided and non-empty, the resulting groups are sorted to match it; groups not listed keep their existing first-encounter order and are appended after the listed ones.
  • Threads config.admin?.groups through both call sites: getNavGroups (dashboard cards / dashboard view) and Nav/index.tsx (sidebar).

Why?

Today the sidebar renders nav groups in first-encounter order. Because groupNavItems seeds its reducer with [Collections, Globals], the implicit Collections group is always forced to the top and globals-only custom groups are always forced to the bottom, regardless of how the user declares their config. There is no config option to control group order (Discussion #1277, open since Oct 2022, 26+ upvotes; Discussion #14528). The only workarounds today are prefix hacks like group: '1. System' (which leak the digit into the rendered label) or swapping in a fully custom Nav component.

This has come up repeatedly for ~3.5 years without a core fix. Tracking issue: #17097.

How?

The change is intentionally minimal and backward compatible:

  1. groupNavItems keeps its existing reduce + seed-array behavior unchanged, so when groupOrder is omitted the output is byte-identical to before.
  2. After the existing filter(group => group.entities.length > 0) step, if groupOrder is a non-empty array, the filtered groups are passed through sortGroupsByOrder, which:
    • iterates groupOrder and pushes the first unmatched group whose translated label equals the wanted label,
    • then appends any remaining groups in their existing first-encounter order.
  3. Labels are compared via getTranslation(..., i18n), so the same value used in admin.group works. For the default groups, users should use the translation key ('Collections' / 'Globals') — shown in the JSDoc example.
// packages/ui/src/utilities/groupNavItems.ts
const filtered = result.filter((group) => group.entities.length > 0)

if (groupOrder && groupOrder.length > 0) {
  return sortGroupsByOrder(filtered, groupOrder, i18n)
}

return filtered

The two callers pass config.admin?.groups (sanitized config, always present) or payload.config.admin.groups (already destructured on payload.config).

Before / After

Config:

collections: [
  { slug: 'zebra',    admin: { group: 'Animals' } },
  { slug: 'apple',    admin: { group: 'Food' } },
  { slug: 'settings', admin: { group: 'System' } },
  { slug: 'banana' },                              // default Collections group
],
globals: [
  { slug: 'header', admin: { group: 'Layout' } },
],
admin: { groups: ['System', 'Animals', 'Food', 'Layout', 'Collections'] },

Before (admin.groups ignored — first-encounter order):

  1. Collections → banana
  2. Animals → zebra
  3. Food → apple
  4. System → settings
  5. Layout → header

After (admin.groups respected):

  1. System → settings
  2. Animals → zebra
  3. Food → apple
  4. Layout → header
  5. Collections → banana

Tests

Added packages/ui/src/utilities/groupNavItems.spec.ts (vitest, same style as the neighboring .spec.ts files in packages/ui/src/utilities/). Covers:

  • default order preserved when groupOrder is omitted,
  • groups reordered to match admin.groups,
  • unlisted groups appended after listed ones in first-encounter order,
  • item order within a group preserved,
  • empty admin.groups array keeps default behavior.

All 5 pass locally:

Test Files  1 passed (1)
     Tests  5 passed (5)

This PR only changes group-level ordering. Item-level ordering within a group is intentionally left as-is (definition order) to keep the change minimal and backward compatible; that can be a follow-up.

Fixes #17097

The admin sidebar renders nav groups in first-encounter order, which forces
the implicit Collections/Globals groups to the top and pushes globals-only
custom groups to the bottom. There has been no config option to control the
order of the groups themselves (Discussion payloadcms#1277, open since Oct 2022).

Add an optional `admin.groups` array to the sanitized config. When present,
`groupNavItems` sorts the resulting groups to match it; groups not listed
keep their existing first-encounter order and are appended after the listed
ones, so the default behavior is preserved when the option is omitted or
empty.

Labels are compared against the translated group label, so the same value
used in `admin.group` (or `general:collections` / `general:globals` for the
default groups) should be used.

Refs payloadcms#17097
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(admin): no way to control the order of nav groups in the sidebar

1 participant