Reference → Categories — Reference

Categories — Reference

The Categories surface is the content-organization plane for an SGEN site. It owns the taxonomy records that classify posts, products, and custom-object entries, the hierarchical parent / child structure that nests categories inside one another, and the per-category settings (slug, description, archive page reference, SEO metadata) that decide how a category surfaces on the public site. Anything that answers a question of the shape which records belong to this topic traces back to a record managed here.

This page is a reference for platform engineers and integrators who need to understand the surface before extending it, scripting against it, or porting a taxonomy from a legacy CMS. Customer-facing walkthroughs for creating a category and assigning records to it live in the customer docs set; this page describes the shape of the surface, not the steps to drive it.


Overview

Categories live under the Categories module in SG-Admin. The module renders three primary views — the category list with tree expansion, the category create / edit form, and the per-category assignment panel — and exposes a small set of write operations for create, update, reparent, merge, soft delete, restore, and permanent delete.

The module is paired by convention: one half renders the views and prepares the data, the other half handles writes and returns AJAX responses. The pairing matches the broader SG-Admin convention used across other modules.

A second surface — taxonomy scope — lives at the platform level. Each category record is scoped to one content type (posts, products, or a registered custom object). The same slug may exist under two different taxonomies without collision; a "Tutorials" category for posts is distinct from a "Tutorials" category for products even though both carry the same slug. Cross-scope merging is not supported.

Where it lives in SG-Admin:

  • Sidebar: SG-Admin → Categories
  • URL prefix: /sg-admin/categories/
  • View templates: application/views/Admin/Categories/
The module surface is intentionally narrow. Heavier classification (tags, custom-field-driven facets, machine-classified labels) lives in adjacent surfaces — this page covers only the hierarchical taxonomy plane.
┌──────────────────────────────────────────────────────────────────────┐│ SG-Admin → Categories Scope: [Posts ▾] [+ New category] │├──────────────────────────────────────────────────────────────────────┤│ Name Slug Records Parent Status ││ ──────────────────── ───────────── ──────── ─────── ──────── ││ ▾ Engineering engineering 48 — active ││ Architecture architecture 18 Engineering active ││ Performance performance 14 Engineering active ││ Testing testing 16 Engineering active ││ ▾ Customer stories customer-stor 27 — active ││ Agencies agencies 9 Customer s active ││ In-house teams in-house 18 Customer s active ││ News news 112 — active ││ Launches launches 22 News active ││ Old launches old-launches 18 — trash ││ ││ [⋯ Edit] [⋯ Reparent] [⋯ Merge] [⋯ Delete] │└──────────────────────────────────────────────────────────────────────┘

Actions

The Categories surface exposes the following operations. Each is described by what it does to the site state, not by its internal method name.

List categories

Returns the categories in the current taxonomy scope, with name, slug, record count, parent reference, and status columns. The default view is a tree with expandable nodes; a flat-list toggle is available for free-text filtering across the full tree. Default scope is the Posts taxonomy; a scope selector switches between Posts, Products, and registered custom objects.

Create category

Opens an empty category form. Required fields at minimum: name, slug, scope. Optional fields cover parent reference, description, archive page reference, hero image, and SEO metadata. On submit, the surface validates uniqueness of slug within the scope (the same slug may live under a different scope), persists the record, and returns the new category identifier.

Edit category

Loads an existing category into the same form shape used for create, pre-populated. Submit replaces the stored configuration. A slug change rewrites the category archive URL and triggers a 301 redirect from the old path; record permalinks that include the category slug update to match.

Slug changes are reversible inside a 30-day window — the prior slug is held in a redirect table. After 30 days the prior slug is released and may be reused.

Reparent category

A standalone write that moves a category to a new parent (or to root level). Reparenting recomputes the category's depth, validates against the maximum-depth limit (default 5), and rewrites breadcrumb-aware permalinks for every assigned record. The operation is atomic per category — partial moves do not persist.

Merge categories

Combines two categories in the same scope. The source category's record assignments transfer to the target, the source's children optionally reparent to the target, and the source category enters trashed state. Used to clean up taxonomy drift after a content audit.

Per-category assignment

Opens a record-assignment panel scoped to one category. Lists every record currently assigned, with the option to bulk-assign or bulk-remove. Supports cross-record bulk operations (assign 40 posts to Engineering / Performance in one write).

Soft delete

Marks the category as trashed. Trashed categories disappear from the default list, the category archive page returns a 410 response, and records that had the category as their only classification fall back to the scope's default category (configurable in Settings). Record-level data is not affected.

Restore

Returns a trashed category to active status. The archive page resumes responding, records that fell back to the default category retain their fallback assignment until manually reassigned (the surface surfaces a notification with the affected record count).

Permanent delete

Hard-removes the category record. Available only after a soft delete. Records previously assigned to the category retain their fallback-default assignment from the soft-delete step. The 301 redirect from the old archive URL drops; subsequent requests return a 410.

Reorder categories

Sets the display order within a parent. Drag-handle interface; on submit, the surface writes a sort-order integer per category within the parent. Sort order is scoped per parent — root-level categories sort independently from each parent's children.

Bulk reassign records

A maintenance operation that moves every record from one category to another in a single transaction. Used when a category is being retired but the records should not fall back to the default. The source category remains intact (it does not auto-trash).


Data model

A category record carries the following fields. Field names below are the conceptual shape — the on-disk column names match closely but are not contractually stable across releases.

FieldTypeNotes
idintegerPrimary key. Stable across edits.
scopeenumposts, products, or a registered custom-object slug.
namestringDisplay name. Unique within the scope at a given parent level.
slugstringURL segment. Unique within the scope.
parent_idintegerOptional. Foreign key to another category in the same scope. Null at root.
depthintegerComputed. Root = 0. Maximum depth = 5 by default.
descriptionstringOptional. Renders in the archive header and structured data.
archive_page_idintegerOptional. Foreign key to a custom archive page; null means the platform's default archive template renders.
hero_image_idintegerOptional. References a Media library record.
seo_metaJSONTitle, description, OG image for the archive page itself.
sort_orderintegerDisplay order within the parent.
record_countintegerComputed. Refreshed on assignment writes.
statusenumactive or trash.
created_attimestampImmutable.
updated_attimestampTouched on any edit.
Soft-delete semantics: status = trash is the soft-deleted state. Trashed categories retain all other fields, but their archive URL stops responding and their record-count column stops refreshing. Permanent delete removes the row entirely.

Slug uniqueness scope: unique within the taxonomy scope but not across scopes. A category at /posts/tutorials does not collide with a category at /products/tutorials. Page slugs also do not collide with category slugs — the platform's URL router resolves the two from different namespaces.

Hierarchy semantics: the parent / child relationship is acyclic — the surface rejects a reparent that would create a cycle. Maximum depth is configurable per scope under Settings → Taxonomies. Records are assigned at any depth; assignment does not implicitly cascade to ancestors or descendants (a record in Engineering / Performance is not also in Engineering unless explicitly assigned).

Record assignment storage: stored as a separate join table, not on the category record itself. The join row carries category_id, record_id, record_type, assigned_at, and assigned_by_user_id. Assignment is many-to-many — a record may belong to several categories.

CATEGORY RECORD RELATED RECORDS┌─────────────────────────────┐│ id ││ scope (posts|products|...) │ ──────► RECORDS (via join)│ name, slug │ record_id + category_id│ parent_id ──────────────────┼──┐ record_type + assigned_*│ depth (computed) │ ││ description │ │ CATEGORY (self-ref parent)│ archive_page_id ────────────┼──┼────► PAGES (optional override)│ hero_image_id ──────────────┼──┼────► MEDIA│ seo_meta (JSON) │ ││ sort_order │ ││ record_count (computed) │ ││ status │ ││ timestamps │ │└─────────────────────────────┘ │▼┌─────────────────────┐│ TAXONOMY TREE ││ Engineering ││ ├─ Architecture ││ ├─ Performance ││ └─ Testing ││ Customer stories ││ ├─ Agencies ││ └─ In-house teams ││ News ││ └─ Launches │└─────────────────────┘↓ on permanent deleteDEFAULT CATEGORY (Settings → Taxonomies)receives records whose only category was deleted

Permissions

Access to the Categories surface is gated at two layers.

Layer 1 — admin gate. Every action under SG-Admin passes through the platform's standard admin access check at request entry. An unauthenticated request never reaches the Categories surface.

Layer 2 — per-action capability. Within SG-Admin, each Categories action checks a capability associated with the calling operator's role. The default role configuration ships with three roles — Administrator, Editor, Viewer — and the capability map is:

CapabilityAdministratorEditorViewer
List categories
Create category
Edit category
Reparent category
Merge categories
Per-category assignment
Bulk reassign records
Soft delete
Restore
Permanent delete
Reorder categories
Custom roles defined under Settings → Roles override the default map. The capability slugs are stable; the role slugs are configurable.

Self-protection rules. A scope cannot soft-delete its default category — the surface returns a structured rejection. Reparenting that would exceed the maximum-depth limit is rejected; reparenting that would create a cycle is rejected. A merge into a trashed category is rejected; restore the target first.

Audit. Every write — create, edit, reparent, merge, assignment change, bulk reassign, soft delete, restore, permanent delete, reorder — emits an Activity Log entry. The log records the acting operator, the target category, and the change shape (field-level diff for edits, source / target for merges and reparents, record-count delta for assignment writes). Activity Log retention follows the site's general settings.

REQUEST → /sg-admin/categories/...│▼┌────────────────────────┐│ Admin gate │ unauth → reject└──────────┬─────────────┘│ authed▼┌────────────────────────┐│ Role capability check │ role lacks cap → reject└──────────┬─────────────┘│ allowed▼┌────────────────────────┐│ Hierarchy + scope │ cycle / max-depth / cross-scope│ validation │ merge → reject└──────────┬─────────────┘│ allowed▼┌────────────────────────┐│ Self-protection rules │ delete default / merge into trash│ │ → reject└──────────┬─────────────┘│ passes▼write executes│▼Activity Log entry(field-level diff or merge shape)

Related references

  • Posts — Reference. Post records carry category assignments via the categories join table. Default category fallback applies on category permanent delete.
  • Products — Reference. Product records share the same join-table shape. Product taxonomies are scoped separately from post taxonomies.
  • Custom Objects — Reference. Registered custom objects can opt into the Categories surface by declaring a category scope at registration time.
  • Pages — Reference. Custom archive pages are referenced from the category record. Page deletion cascades to the category's archive_page_id, which falls back to the platform default.
  • Media — Reference. Hero images reference Media records. Media deletion cascades to a placeholder.
  • Settings — Reference. Default category per scope, maximum-depth limit per scope, and slug-redirect window all live in Settings.
  • SEO — Reference. Archive-page meta and structured data resolve through SEO defaults if the per-category seo_meta is empty.
For the corresponding customer-facing walkthrough — creating a category tree, assigning posts to a category, merging duplicate categories — see the Categories section of the customer docs at /docs/categories.
On this page