enonic-guillotine-query-builder
enonic guillotine query builder
SKILL.md
Full skill instructions
Enonic Guillotine Query Builder
Procedures
Step 1: Scan the workspace for existing Guillotine usage
- Execute
node scripts/find-guillotine-targets.mjs .to inventory files containing Guillotine markers (query strings, library imports, endpoint references). - If a Node runtime is unavailable, search the workspace manually for
guillotine,queryDsl,queryDslConnection, or/lib/guillotinein.ts,.js,.graphql, and.gqlfiles. - Note the Guillotine version in use: if
query(query: "...")string-based fields are found, the project uses the deprecated 5.x-style API; ifqueryDsl/queryDslConnectionare found, the project uses 6.x+ DSL. Check forexports.extensionsinguillotine/guillotine.jsto detect Guillotine 7 Extensions API usage. - If both styles coexist, flag the deprecated usage for migration.
Step 2: Load the Guillotine API reference
- Read
references/guillotine-reference.mdbefore composing any query. - Read
references/compatibility.mdwhen the workspace targets or migrates between Guillotine versions.
Step 3: Determine the query shape
- Identify the operation the user needs:
- Single content fetch: Use
get(key). - Direct children: Use
getChildren(key)orgetChildrenConnection(key)for pagination. - Filtered search: Use
queryDsl(query)for a flat list orqueryDslConnection(query)for pagination, aggregations, or highlighting. - Content type metadata: Use
getType(name)orgetTypes.
- Single content fetch: Use
- If pagination is needed, prefer connection variants (
queryDslConnection,getChildrenConnection) and guide the caller to passafter/first. - If aggregations or highlighting are needed, require
queryDslConnection— these features are not available onqueryDsl.
Step 4: Construct the content type fragment
- Derive the GraphQL type name from the content type descriptor by replacing dots (
.) and colons (:) with underscores (_), and removing hyphens (-) while capitalizing the following letter. The first letter of each segment after a colon is capitalized. Example:com.enonic.app.myapp:BlogPost→com_enonic_app_myapp_BlogPost. For built-in types:portal:template-folder→portal_TemplateFolder. - Use an inline fragment to access the type-specific
datafield:... on <GraphQLTypeName> { data { ... } }. - For content references (ContentSelector, ImageSelector, MediaSelector), follow the reference with a nested inline fragment on the target type.
- For RichText / HtmlArea fields, include
processedHtmland optionallylinks,images,macrossub-fields. Use theprocessHtmlinput argument for absolute URLs, srcset widths (imageWidths), or responsive sizes (imageSizes).
Step 5: Build query filters and sorting
- Use Query DSL input types. Each
QueryDSLInputmust contain exactly one expression field. - Combine multiple conditions using
booleanwithmust,should,mustNot, andfilterarrays. - For date or numeric ranges, use the
rangeexpression withgt/gte/lt/lteand the correctDSLExpressionValueInputtype (localDate,localDateTime,instant,long,double). - For sorting, use
SortDslInputwithfieldanddirection(ASC/DESC). - Read
references/examples.mdwhen the query pattern matches a documented example.
Step 6: Add aggregations and highlighting (if needed)
- Pass
aggregationsas an array ofAggregationInputobjects onqueryDslConnection. - Each aggregation requires a unique
nameand exactly one aggregation type field (terms,dateRange,stats, etc.). - For highlighting, pass
highlightwith apropertiesarray specifyingpropertyNamefor each field to highlight. - Read aggregation and highlight results from
aggregationsAsJsonandhighlightAsJsonon the connection result.
Step 7: Generate TypeScript types (if requested)
- Read
assets/guillotine-query.template.tsas the starting template. - Replace
__APP_KEY__,__CONTENT_TYPE__,__GRAPHQL_TYPE__, and__FIELDS__placeholders with the actual content type values. - Add typed fields to the
Datainterface matching the content type schema fields requested in the query. - For connection queries, use the
ContentConnection<T>generic with the specific content type.
Step 8: Set site context (if applicable)
- If the query targets a specific site, set
siteKeyon theguillotinefield or instruct the caller to set theX-Guillotine-SiteKeyHTTP header. - Use
${site}placeholder in path arguments for site-relative queries. - Use
_path(type: siteRelative)to return site-relative paths.
Step 9: Validate the query
- Verify all inline fragment type names use underscores, not the original descriptor format.
- Confirm
queryDsl/queryDslConnectionare used instead of the deprecatedquery/queryConnection. - Ensure
QueryDSLInputobjects contain exactly one expression field. - Verify
DSLExpressionValueInputobjects contain exactly one value type field. - Check that aggregation and highlight are only used on connection variants.
- For Guillotine 7+ projects, verify
pageUrl/mediaUrl/imageUrl/attachmentUrluseJsontype forparamsargument, notString. - Read
references/troubleshooting.mdif the query returns unexpected nulls, empty results, or type errors.
Error Handling
- If
getreturns null, verify the key is a valid content path or ID and that the correct branch (draft vs master) is targeted. - If inline fragment fields are null, confirm the GraphQL type name uses underscores and matches the content type descriptor exactly.
- If
queryDslreturns empty results, simplify tomatchAll: {}to confirm data exists, then re-add filters one at a time. - If aggregation or highlight results are null, verify the query uses
queryDslConnection, notqueryDsl. - If the deprecated
queryfield is used, readreferences/compatibility.mdto migrate toqueryDslwith DSL syntax. - If
scripts/find-guillotine-targets.mjscannot run, scan the workspace manually for Guillotine markers and continue.
