Replies: 3 comments
CompletedManual (drag) ordering for content entries: six things reorder in the admin, entries are not one of them
Outcome: Confirmed: the repository already has six drag-to-reorder admin flows (menus, widget-areas, taxonomies, byline-fields, sidebar collections, field rows), but there is no equivalent for content entries. `packages/admin/src/components/ContentList.tsx` currently has no `DndContext`, and the content repository's orderable columns are limited to system fields plus the configured title/date field, with no position support. This is a substantial cross-stack feature, not a localized fix. Implementing it would need, at minimum:
Several design decisions need maintainer input before coding: the exact opt-in API, storage type (float vs lexorank), whether ordering is per-locale or per-translation-group, how to backfill/initialize positions safely, and the interaction with pagination, locale switching, and filtered views. This should go through a design Discussion or maintainer approval before implementation; it is not appropriate for bounded auto-work. Mode: triage View live dashboard · Run: |
|
Whilst a separate PR/feature, are Hierarchical pages/CPT on the horizon for EmDash? Not having (opt-in) hierarchical structures for content types is a blocker for me migrating WP sites over, as well as administering. WP has this option when configuring a CPT, and shows a 'Parent page' UI for the backend when enabled for CPT entries. I'm specifically mentioning this here as 'drag and drop' for hierarchical CPTs in WP is something I use a lot and I would imagine so do a lot of administrators/editors. |
|
I have converted this to a discussion as it is a feature request. |
Uh oh!
There was an error while loading. Please reload this page.
Description
Six things in the admin can be reordered by dragging. Content entries are not one of them, and there is no API for it either.
Present today:
POST /api/menus/:name/reorderPOST /api/widget-areas/:name/reorderPOST /api/taxonomies/:name/reorderPOST /api/admin/byline-fields/reorderPOST /api/schema/collections/reorderPOST /api/schema/collections/:slug/fields/reorderAbsent: any equivalent for the rows of a collection.
ContentList.tsxhas noDndContext(the admin uses dnd-kit in eight other components, includingContentTypeList), and the list can only be sorted by a closed set of columns —ORDER_FIELD_COLUMNSinpackages/core/src/database/repositories/content.tsiscreatedAt, updatedAt, publishedAt, scheduledAt, deletedAt, title, name, slug, status, locale, plus the collection'stitle_fieldanddate_field. None of those is a position.So a Content Types screen can be dragged into the order the editor wants, and the entries inside those types cannot.
Why this matters outside of blogs
For a date-ordered blog,
publishedAtis the right order and nothing is missing. The gap shows up in curated collections, where the order is content: practice areas on a homepage, "how it works" steps, service tiers, featured items, team members, FAQ entries. Those are collections in the CMS sense — same editor, same revisions, same taxonomy — but their sequence is an editorial decision, not a timestamp.The workaround today is to add a numeric field (
sort_order) to every such collection and have the editor type numbers. In practice this costs:A client who has used WordPress with any page-ordering plugin expects to drag. Explaining "type 30 here, and now edit the other four" is where the CMS stops feeling finished.
Proposal
Follow the shape that already exists for terms, so this is mostly assembly rather than invention.
1. Opt-in per collection. A collection declares manual ordering (
manualOrder: true, ordefaultOrder: "manual"alongside the existingsortOrderthat positions collections in the sidebar). Collections that do not declare it behave exactly as today — no column, no handle, no cost.2. Storage. One nullable column on the
ec_*table for the position. A float (or a lexorank-style string) rather than a dense integer, so inserting between two neighbours writes one row instead of renumbering the tail. Null means "never positioned", which sorts last.3. API, mirroring
reorderTermsBody.POST /api/content/:collection/reorderwith{ ids: string[] }, where the listed ids are permuted within the positions they already occupy and everything else keeps its place, and an id outside the set is rejected withREORDER_MISMATCH. That subset semantics is exactly what makes the taxonomy endpoint safe under pagination, and it is the same problem here.4. UI.
DndContextinContentList, with the handle rendered only when the collection is manually ordered and the list is currently sorted by position with no active search or filter. Dragging row 3 while the list is filtered by status and sorted by title has no defensible meaning; the honest move is to hide the handle and say why, the way the drag handle disappears in other filtered lists.5. Locale. Terms deliberately hold one position across locales. Content is different — a translation is its own row — so position is probably per locale. Worth deciding explicitly rather than inheriting the taxonomy answer by accident.
6. Public read path.
orderBy: "position"accepted in the content API and in the plugin/MCP reads, so a site template can ask for the editor's order without knowing the storage detail.Alternatives considered
sort_orderfield. Works, and is what everyone does now, but it is a per-site reinvention of a core concern, and the renumbering cost is real for a non-technical editor.updatedAtand let people re-save to reorder. Destroys real metadata to encode order. Mentioned only to reject it.Prior art
WordPress ships
menu_orderon every post type and the whole plugin ecosystem for page ordering exists on top of it. Sanity, Contentful and Strapi all offer manual ordering per collection with drag in the list view. The expectation is well established; the EmDash admin already has every ingredient (dnd-kit, a reorder request shape, a per-collection settings surface) — what is missing is the column, one endpoint and the handle.Happy to open a Discussion first if this needs a design decision before code, and happy to implement it.
All reactions