Enonic XP Controller Generator
Procedures
Step 1: Detect Enonic XP Project
- Execute
node scripts/find-enonic-targets.mjs <workspace-root> to locate the project root and existing components.
- If the script exits with code 1 (no markers found), inform the user that no Enonic XP project was detected and stop.
- Record the
sitePath from stdout — all generated files target directories relative to this path.
Step 2: Determine Component Type
- Identify which component type the user requires:
- Page — renders a full page, may declare one or more regions.
- Part — a leaf component with a config form, no regions.
- Layout — organizes other components via named regions.
- Response processor — a site-level filter that modifies the HTTP response (e.g., inject scripts).
- If the request is ambiguous, ask the user to clarify the component type before proceeding.
Step 3: Gather Component Details
- Ask for or infer the following:
- Component name (kebab-case, e.g.,
hero-banner).
- Display name (human-readable, e.g.,
Hero Banner).
- Language — TypeScript (
.ts, default) or JavaScript (.js). Read references/compatibility.md for guidance on TS vs JS differences.
- Template engine — Thymeleaf (default) or Mustache.
- For pages/layouts: region names (default:
main for pages).
- For parts: form fields (name, type, occurrences).
- For response processors: page contribution target (
bodyEnd, headEnd, etc.) and the content to inject.
Step 4: Generate the XML Descriptor
- Read
references/controller-reference.md for the XML descriptor schema.
- Create the descriptor file:
- Page:
<sitePath>/pages/<name>/<name>.xml
- Part:
<sitePath>/parts/<name>/<name>.xml
- Layout:
<sitePath>/layouts/<name>/<name>.xml
- Include
<display-name>, <description>, <form> (with inputs for parts), and <regions> (for pages and layouts).
Step 5: Generate the Controller
- Read the appropriate template from
assets/:
assets/page-controller.template.ts for pages.
assets/part-controller.template.ts for parts.
assets/layout-controller.template.ts for layouts.
- Replace placeholders with the actual component name, config field mappings, region names, and library imports.
- If JavaScript was requested, convert the ES module syntax to CommonJS (
require/exports).
- Read
references/controller-reference.md for the Portal API surface (functions, import paths).
- Place the controller at:
- Page:
<sitePath>/pages/<name>/<name>.ts (or .js)
- Part:
<sitePath>/parts/<name>/<name>.ts (or .js)
- Layout:
<sitePath>/layouts/<name>/<name>.ts (or .js)
- Processor:
<sitePath>/processors/<name>.js
Step 6: Generate the View (if applicable)
- For pages, parts, and layouts using Thymeleaf or Mustache, generate a paired
.html view file in the same directory as the controller.
- For pages and layouts, include
data-portal-region="<region-name>" attributes on container elements.
- For region iteration, use
data-th-each="component : ${region.components}" with data-portal-component="${component.path}".
Step 7: Wire Response Processors (if applicable)
- If generating a response processor, check whether
<sitePath>/site.xml exists.
- If it exists, add a
<response-processor> entry inside the <processors> block.
- If it does not exist, create
<sitePath>/site.xml with the processor declaration.
Step 8: Update build.gradle Dependencies
- Check the project's
build.gradle for existing library includes.
- Add any missing dependencies:
com.enonic.xp:lib-portal:${xpVersion} — required for all controllers.
com.enonic.xp:lib-content:${xpVersion} — if the controller uses content queries.
com.enonic.lib:lib-thymeleaf:2.0.0 — if using Thymeleaf rendering.
com.enonic.lib:lib-mustache:2.1.0 — if using Mustache rendering.
com.enonic.lib:lib-asset:${libVersion} — if the controller generates asset URLs (replaces the deprecated portalLib.assetUrl in XP 7.15+).
Step 9: Validate Output
- Verify the descriptor file name matches the parent directory name exactly.
- Verify the controller file name matches the descriptor directory name.
- Verify all region names in the controller/view match those declared in the XML descriptor.
- Read
references/examples.md to cross-check the generated code against known-good patterns.
Error Handling
- If
scripts/find-enonic-targets.mjs exits with code 1 (NO_PROJECT), inform the user that no Enonic XP project was found and suggest creating the standard directory structure under src/main/resources/site/.
- If a component with the same name already exists at the target path, warn the user and ask whether to overwrite or rename.
- If the user reports a 404 on a part or missing regions, read
references/troubleshooting.md to diagnose common causes.
- If the generated controller fails at runtime with view resolution errors, verify the view file is co-located with the controller and the
resolve() call uses the correct filename.
1---2name: enonic-controller-generator3description: Generates Enonic XP controller files (TypeScript/JavaScript) and paired XML descriptors for pages, parts, and layouts. Covers lib-portal imports, HTTP handler exports, region definitions, Thymeleaf/Mustache rendering, and response processors. Use when scaffolding page controllers with regions, part controllers with config access, layout controllers with multi-region support, or response processors for Enonic XP sites. Do not use for content type schemas, headless Next.js/React frontends, GraphQL Guillotine queries, or non-Enonic web frameworks.4license: MIT5---6
7# Enonic XP Controller Generator
8
9## Procedures
10
11**Step 1: Detect Enonic XP Project**
121. Execute `node scripts/find-enonic-targets.mjs <workspace-root>` to locate the project root and existing components.
132. If the script exits with code 1 (no markers found), inform the user that no Enonic XP project was detected and stop.
143. Record the `sitePath` from stdout — all generated files target directories relative to this path.
15
16**Step 2: Determine Component Type**
171. Identify which component type the user requires:
18 - **Page** — renders a full page, may declare one or more regions.
19 - **Part** — a leaf component with a config form, no regions.
20 - **Layout** — organizes other components via named regions.
21 - **Response processor** — a site-level filter that modifies the HTTP response (e.g., inject scripts).
222. If the request is ambiguous, ask the user to clarify the component type before proceeding.
23
24**Step 3: Gather Component Details**
251. Ask for or infer the following:
26 - **Component name** (kebab-case, e.g., `hero-banner`).
27 - **Display name** (human-readable, e.g., `Hero Banner`).
28 - **Language** — TypeScript (`.ts`, default) or JavaScript (`.js`). Read `references/compatibility.md` for guidance on TS vs JS differences.
29 - **Template engine** — Thymeleaf (default) or Mustache.
30 - For pages/layouts: **region names** (default: `main` for pages).
31 - For parts: **form fields** (name, type, occurrences).
32 - For response processors: **page contribution target** (`bodyEnd`, `headEnd`, etc.) and the content to inject.
33
34**Step 4: Generate the XML Descriptor**
351. Read `references/controller-reference.md` for the XML descriptor schema.
362. Create the descriptor file:
37 - Page: `<sitePath>/pages/<name>/<name>.xml`
38 - Part: `<sitePath>/parts/<name>/<name>.xml`
39 - Layout: `<sitePath>/layouts/<name>/<name>.xml`
403. Include `<display-name>`, `<description>`, `<form>` (with inputs for parts), and `<regions>` (for pages and layouts).
41
42**Step 5: Generate the Controller**
431. Read the appropriate template from `assets/`:
44 - `assets/page-controller.template.ts` for pages.
45 - `assets/part-controller.template.ts` for parts.
46 - `assets/layout-controller.template.ts` for layouts.
472. Replace placeholders with the actual component name, config field mappings, region names, and library imports.
483. If JavaScript was requested, convert the ES module syntax to CommonJS (`require`/`exports`).
494. Read `references/controller-reference.md` for the Portal API surface (functions, import paths).
505. Place the controller at:
51 - Page: `<sitePath>/pages/<name>/<name>.ts` (or `.js`)
52 - Part: `<sitePath>/parts/<name>/<name>.ts` (or `.js`)
53 - Layout: `<sitePath>/layouts/<name>/<name>.ts` (or `.js`)
54 - Processor: `<sitePath>/processors/<name>.js`
55
56**Step 6: Generate the View (if applicable)**
571. For pages, parts, and layouts using Thymeleaf or Mustache, generate a paired `.html` view file in the same directory as the controller.
582. For pages and layouts, include `data-portal-region="<region-name>"` attributes on container elements.
593. For region iteration, use `data-th-each="component : ${region.components}"` with `data-portal-component="${component.path}"`.
60
61**Step 7: Wire Response Processors (if applicable)**
621. If generating a response processor, check whether `<sitePath>/site.xml` exists.
632. If it exists, add a `<response-processor>` entry inside the `<processors>` block.
643. If it does not exist, create `<sitePath>/site.xml` with the processor declaration.
65
66**Step 8: Update build.gradle Dependencies**
671. Check the project's `build.gradle` for existing library includes.
682. Add any missing dependencies:
69 - `com.enonic.xp:lib-portal:${xpVersion}` — required for all controllers.
70 - `com.enonic.xp:lib-content:${xpVersion}` — if the controller uses content queries.
71 - `com.enonic.lib:lib-thymeleaf:2.0.0` — if using Thymeleaf rendering.
72 - `com.enonic.lib:lib-mustache:2.1.0` — if using Mustache rendering.
73 - `com.enonic.lib:lib-asset:${libVersion}` — if the controller generates asset URLs (replaces the deprecated `portalLib.assetUrl` in XP 7.15+).
74
75**Step 9: Validate Output**
761. Verify the descriptor file name matches the parent directory name exactly.
772. Verify the controller file name matches the descriptor directory name.
783. Verify all region names in the controller/view match those declared in the XML descriptor.
794. Read `references/examples.md` to cross-check the generated code against known-good patterns.
80
81## Error Handling
82
83* If `scripts/find-enonic-targets.mjs` exits with code 1 (`NO_PROJECT`), inform the user that no Enonic XP project was found and suggest creating the standard directory structure under `src/main/resources/site/`.
84* If a component with the same name already exists at the target path, warn the user and ask whether to overwrite or rename.
85* If the user reports a 404 on a part or missing regions, read `references/troubleshooting.md` to diagnose common causes.
86* If the generated controller fails at runtime with view resolution errors, verify the view file is co-located with the controller and the `resolve()` call uses the correct filename.