Enonic Content Management
Procedures
Step 1: Identify the Enonic XP project and operation scope
- Inspect the workspace for Enonic XP project markers:
build.gradlewithcom.enonic.xpdependencies,src/main/resources/directory structure, orgradle.propertieswithxpVersion. - Execute
node scripts/find-enonic-targets.mjs .to scan for Enonic XP project markers and existing content operation files when a Node runtime is available. - If no Enonic XP project is detected, stop and explain that this skill targets Enonic XP content operations only.
- Determine the target XP version from
gradle.propertiesorbuild.gradleto select the correct API surface. - Classify the requested operation:
- Bulk create: Importing content from external sources (JSON, CSV, APIs).
- Bulk update: Modifying existing content matching query criteria.
- Bulk delete: Removing content matching query criteria.
- Migration: Moving or transforming content between paths, sites, or environments.
- Query/Aggregation: Retrieving and analyzing content with NoQL and aggregations.
- Long-running task: Any operation processing more than ~100 items that needs progress reporting.
Step 2: Select the API layer and context
- Read
references/migration-reference.mdfor query DSL patterns, batch processing strategies, and branch handling rules. - Choose
lib-contentwhen the operation works with CMS content and needs publish/unpublish, content-type validation, and the content domain abstraction. - Choose
lib-nodewhen the operation needs low-level node manipulation, custom repositories, or bypasses content-type validation for raw data migration. - Determine the required context:
- Use
lib-contextto run operations indraftbranch for modifications andmasterbranch for reading published content. - Use
lib-contextwithrole:system.adminprincipal when the operation requires elevated permissions.
- Use
- If the operation processes more than ~100 items, wrap it in a task controller using
lib-task. Readreferences/migration-reference.mdsection on task controllers for the pattern.
Step 3: Build the query
- Construct the NoQL query string or DSL expression to match the target content.
- For content-type filtering, use the
contentTypesparameter oncontentLib.query()or atypeproperty comparison in node queries. - For date-range queries, use
instant()ordateTime()functions and therange()query function. - For full-text search, use
fulltext()with the appropriate field paths and operator (AND/OR). - For path-scoped operations, use
_path LIKE '/content/site-path/*'to match descendants. Note: the_pathproperty in NoQL queries includes the internal/content/prefix, buthit._pathin results returns the content-domain path without it. Always prepend/content/when building queries from result paths. - Add
filtersfor efficient post-query narrowing usingexists,notExists,hasValue, orbooleancombinations. - Add
aggregationswhen the operation needs grouped statistics (term counts, date histograms, numeric ranges, stats). - Read
references/examples.mdfor complete query and aggregation patterns.
Step 4: Implement batch processing
- Use paginated retrieval with
startandcountparameters to avoid loading all results into memory. - Set
countto a batch size between 50 and 200 depending on the complexity of per-item processing. - Loop until
result.hits.length === 0orstart >= result.total, incrementingstartby the batch size each iteration. - For
contentLib.create()in bulk, setrefresh: falseto avoid per-item index refresh; call a manual refresh after the batch completes. - For
contentLib.modify(), use the editor callback pattern to safely transform each content item. - For
contentLib.publish(), batch keys into groups of 50–100 to avoid timeout on large publish sets. - Track success and failure counts for reporting.
- Read
assets/bulk-update.template.tsfor the reusable batch update controller template. Note: templates use TypeScript/ESM syntax (import,const, arrow functions); adapt to CommonJS JavaScript (require(),var,function()) for XP runtime deployment as.jsfiles.
Step 5: Handle branch operations and publishing
- Run content modifications in the
draftbranch context. - After modifications complete, publish changed items to
masterusingcontentLib.publish(). On XP < 7.12, passsourceBranch: 'draft'andtargetBranch: 'master'. On XP 7.12+, these parameters are ignored (publish always goes draft→master). - Set
includeDependencies: falsewhen publishing bulk-updated items to avoid unintended dependency publishing. - For operations comparing draft and master state, use
repo.diff()fromlib-nodewithtarget: 'master'andincludeChildren: true.
Step 6: Wrap long-running operations in a task controller
- Use
taskLib.executeFunction()for inline task functions ortaskLib.submitTask()for named task descriptors. - Report progress using
taskLib.progress({ info, current, total })at regular intervals during batch processing. - Read
assets/task-migration.template.tsfor the reusable task controller template with progress reporting. - Check for existing running instances with
taskLib.isRunning()before starting a duplicate operation. - Use
taskLib.sleep()for throttling between batches if the operation generates excessive load. - For named tasks on XP 7.13+, the
runfunction receivestaskIdas its second argument:exports.run = function(params, taskId) { ... }.
Step 7: Validate and report results
- After the operation completes, log a summary: items processed, items created/updated/deleted, items failed, total duration.
- For migration operations, verify target content exists by querying the destination path.
- For publish operations, verify published state by querying the
masterbranch. - If errors occurred, collect error details (content path, error message) into a structured report.
Error Handling
- If
scripts/find-enonic-targets.mjsfinds no Enonic XP project, explain that the workspace does not contain an Enonic XP application. - If a content operation fails with
contentAlreadyExists, check whether the target exists and decide whether to update or skip. Readreferences/troubleshooting.mdfor duplicate handling patterns. - If a query returns zero results, verify the query syntax, branch context, and property paths. NoQL string-format mismatches (e.g. missing
instant()wrapper for date comparisons) are a common cause. - If a task fails or times out, check
taskLib.get(taskId)for state and progress details. Readreferences/troubleshooting.mdfor timeout and permission issues. - If an
AccessDeniedExceptionoccurs on publish or modify, ensure the context includesrole:system.adminprincipals vialib-context.