/key-features edits the public Key Features marketing pages (e.g.
/key-features/ai-data-detection-response) with no code change. It follows the
CPT-backed module pattern: content is stored as
JSON in content.raw of the private WordPress key_feature CPT, and writes bust
the key-features cache tag so the public site revalidates.
| Route | Purpose | Guard |
|---|---|---|
/key-features (workspace) | CptDocEditor list + JSON editor | org:content:view (layout.tsx) |
/key-features/[slug] (public, root app) | Rendered marketing page (KeyFeaturePage template) | Public (no login) |
The workspace
page.tsx
is a client component rendering CptDocEditor with apiBase="/api/workspace/key-features"
and an EMPTY_KEY_FEATURE hero template; the
layout.tsx
gates on requirePermission("org:content:view") with dynamic = "force-dynamic".
| Route | Purpose | Clearance |
|---|---|---|
GET /api/workspace/key-features | List CPT items | org:content:view |
POST /api/workspace/key-features | Create one (cptUpsertSchema) | org:content:edit |
GET / PUT / DELETE /api/workspace/key-features/[id] | Fetch (by numeric id or slug), update, trash | view / edit / delete (falls back to edit) |
Handlers use the shared KEY_FEATURE_CPT descriptor and listCptItems /
getCptItem / createCptItem / updateCptItem / deleteCptItem helpers from
lib/services/cpt.service.ts.
app/key-features/[slug]/page.tsx (root app) fetches
`${env.WORDPRESS_URL}/wp-json/wp/v2/key_feature?slug=${slug}&context=edit&status=any`
with next: { revalidate: 3600, tags: ["key-features"] }, parses content.raw as
KeyFeatureData, resolves icons, and renders the KeyFeaturePage template with
VideoObject/ItemList SEO metadata. Drafts and trashed posts return 404. Static
slugs (e.g. ai-data-detection-response, data-security-intelligence) exist as
checked-in fallbacks.
Related: CPT pattern · Platform · Content Studio