Skip to main content

Content Category

Overview

Content Category is the top-level (level 1) grouping of the content system. It organises content pages and acts as the root for Content Subcategories, which can nest up to four levels deep.

The screen is aimed at administrators and content teams who need to lay out the taxonomy before authoring actual content pages, since the categories created here become selectable options across several other screens.

Each category holds the following data:

FieldDescription
NameRequired, up to 255 characters
SlugRequired, up to 255 characters, restricted to lowercase letters, digits, and hyphens
DescriptionOptional, up to 1,000 characters
ThumbnailAn illustrative image for the category
Sort orderA non-negative integer controlling the position in listings
StatusActive / inactive, defaulting to active

Key points to be aware of:

  • List responses include each category's subcategories, so the subcategory count can be displayed immediately without an extra request.
  • Deleting a category affects both its subcategories and any content pages bound to it, so a warning is always shown before confirmation.
  • Beyond the standard list, this module serves a dropdown endpoint that many other screens depend on.

Business Flow

Managing the category list

  1. Opening /content-category loads a paginated list with a search term and a status filter.
  2. The table shows the category name (clickable to open the edit form), slug, subcategory count, sort order, status, last-updated date, and an action menu.
  3. Categories can be searched by keyword and filtered by status; running a search always returns to the first page.
  4. Clicking the subcategory count, or choosing "View subcategories" from the action menu, navigates to the Content Subcategory screen pre-filtered to the selected category.
  5. Deletion opens a confirmation dialog warning about the impact on linked subcategories and content, and refreshes the table once completed.

Creating and editing a category

  1. The form at /content-category/form distinguishes create from edit mode based on the category ID in the query string.
  2. Edit mode loads the existing record into the form; if loading fails, an error is reported and the user is returned to the list.
  3. As the name is typed, a slug is generated automatically — lowercased, stripped of characters other than letters and digits, with spaces replaced by hyphens. Auto-generation only applies when creating a new category or when no slug exists yet, so slugs of already-published categories are never changed unintentionally.
  4. The remaining fields — description, sort order, and status — are filled in as needed.
  5. Saving creates or updates the category and returns to the list on success.

Reusing categories in other features

  1. Existing categories are exposed to other screens through a dropdown endpoint that returns display-label and identifier pairs.
  2. The content page create and edit forms use this list as the first level of classification, then load the selected category's subcategories.
  3. The Content Subcategory list and form use the same list to indicate which category a subcategory belongs to.
  4. The Content Links screen uses categories as a filter criterion for link lists.
  5. Deactivating a category keeps the record in the system rather than removing it, but it is worth checking beforehand that no active content still depends on it.

Key Screens & Components

List page (/content-category)

  • Filter bar — a search field and a status selector, using the same standard control styling as other CMS list pages.
  • Category table — name, slug, subcategory count (rendered as a clickable badge that jumps to the subcategory screen), sort order, status, and last-updated date, with pagination and responsive horizontal scrolling.
  • Action menu — edit, view subcategories, and delete.
  • Delete confirmation dialog — spells out the impact on related records before proceeding.

Primary file: src/app/content-category/page.tsx

Form page (/content-category/form)

  • Name field — required, and the source for slug auto-generation.
  • Slug field — required, validated to contain only lowercase letters, digits, and hyphens.
  • Description field — a multi-line description of the category.
  • Sort order field — a non-negative number.
  • Status selector — defaults to active.

Field rules at a glance:

FieldRule
NameRequired, up to 255 characters
SlugRequired, up to 255 characters, lowercase letters, digits, and hyphens only
DescriptionOptional, up to 1,000 characters
Sort orderA number of 0 or greater
StatusActive or inactive, defaulting to active

Primary file: src/app/content-category/form/page.tsx

API service

All calls live in src/services/content-category.service.ts under the content-categories base path.

CapabilityEndpoint
List categoriesGET /content-categories
Dropdown optionsGET /content-categories/dropdown
Get a single categoryGET /content-categories/{id}
Create a categoryPOST /content-categories
Update a categoryPATCH /content-categories/{id}
Delete a categoryDELETE /content-categories/{id}

The service also defines endpoints for grouped dropdown options and category statistics, though no screen currently consumes them.

Dependencies

  • Permissions — this is a child menu gated by the content-category module permission, and it appears only when the user can also access the parent content-management menu; both are unlocked by the backend line-oa module.
  • Content Subcategory — the child tree rooted at each category, and the destination of the "view subcategories" action.
  • Content Management — consumes the category dropdown so authors can classify content pages while creating or editing them.
  • Content Links — uses categories as a filter criterion for content link lists.
  • Category cascader component — a shared control that pulls top-level categories from this module and extends them with hierarchical subcategories.
  • Shared infrastructure — the CMS HTTP client (automatic bearer token and sign-out on expiry), the breadcrumb and side-menu system, shared table and filter constants, and the common translation namespaces.

Backend Details (CMS API)

The backend lives in internal/modules/contentcategory/. It is a small module that, beyond ordinary CRUD, acts as the category option provider for several other screens.

Required permissions

  • Every route requires the shared authentication check (JWT), but is not wrapped in a module gate, and the declared policy references the line-oa module as metadata that is not yet enforced.
  • Per-menu access control therefore happens mainly on the front end; any signed-in user can still call the category endpoints directly.
  • Data is always scoped to the LINE OA and organisation currently in use.

Endpoints the backend offers beyond what the UI calls

Alongside the list and dropdown endpoints the UI already uses, the API exposes two more:

EndpointPurpose
GET /api/content-categories/dropdown-groupReturns grouped options for dropdowns that need option group headings
GET /api/content-categories/statsReturns the content count per category

Categories are also surfaced on a public route for readers, so they are not exclusive to the admin screens.

What to watch out for around deletion

  • The content_category table uses a plain deletion-timestamp column rather than the ORM's automatic soft-delete mechanism. Every read query must spell out the "not deleted" condition itself; miss it in one place and deleted categories reappear as options on other screens.
  • A deliberately preserved edge case — the update operation does not filter out already-deleted rows (retaining the previous system's behaviour). Updating by the id of a deleted category therefore still succeeds, even though that category appears in no listing.
  • Deletion responds with a no-content success status (204) and does not return the deleted record.
  • The backend runs no reference check before deleting. The warning about the impact on subcategories and content pages shown in the confirmation dialog comes from the front end, not from a server-side guard, so bound subcategories and pages can end up referencing a category that no longer exists.

What gets stored, and the side effects

  • Data lives in the content_category table, referencing content_subcategory and content_page.
  • This module writes no cache and publishes no queue work, so adding or editing a category is reflected in other screens' dropdowns as soon as they reload.
  • The list response embeds each category's subcategories, which removes the need for a second request to count them — but it also means the response grows with the number of subcategories.

A technical note

The fixed-name routes dropdown, dropdown-group, and stats are registered before the route that takes a category id, so the router resolves them correctly; otherwise dropdown would be interpreted as a category id. This ordering must be preserved when adding new endpoints to the module.