Upgrade Notes

Contents

Upgrading from Guillotine 8 to 9

Guillotine 9 requires Enonic XP 8.1.0 or higher.

Guillotine 9 changes the API endpoint and several GraphQL fields. Start with the endpoint and URL changes below, then review the sections for features your clients use. Regenerate client types from the new schema after updating your queries.

Versions 8 and 9 use the same application key, so installing version 9 replaces version 8. If clients need to migrate at different times, see Migrating clients gradually.

API endpoint

All clients using the standard Guillotine 8 endpoint need to update their endpoint or its routing configuration.

These are the native XP endpoints; your production setup may expose a different public URL:

Version Native XP endpoint

Guillotine 8

POST /site/<project>/<branch>

Guillotine 9 Web API

POST /api/com.enonic.app.guillotine:graphql

Update ingress or virtual-host targets that point to the old endpoint. You can keep the same public URL, but must still update queries to select project and branch.

A common XP 8 setup exposes /api as a shared API base URL. Guillotine is then at <apiBaseUrl>/com.enonic.app.guillotine:graphql, with the image and attachment APIs under the same base. If you expose GraphQL individually or serve media through a CDN, also verify that your media links have working public routes. See Setup for routing examples and Configuration for separate media hosting.

For site mounting, add com.enonic.app.guillotine:graphql to the site’s apis descriptor list. The native endpoint is /site/<project>/<branch>/<site-path>/_/com.enonic.app.guillotine:graphql.

Project and branch

At the new Web endpoint, project and branch move from the request URL into the query. A request previously sent to /site/myproject/master becomes:

{
  guillotine(project: "myproject", branch: "master") {
    getChildren(key: "/", first: 10) {
      _id
      displayName
    }
  }
}

The JSON request body still contains query and optional variables; see Request format for complete examples.

Use master for published content or draft for work in progress. Content permissions continue to apply; draft access normally requires authentication.

At a site-mounted endpoint, the request still supplies project and branch, and query arguments cannot override them.

Page, image and attachment URLs

Clients selecting URL fields must update both their queries and their handling of the response. These fields now return objects:

Field Guillotine 8 Guillotine 9

Content.pageUrl

String

PageUrl

media_Image.imageUrl

String

ImageUrl

Media content’s mediaUrl

String

AttachmentUrl

Attachment.attachmentUrl

String

AttachmentUrl

Select path and queryString, then build the address in your client using the appropriate media base URL or frontend route.

Guillotine 8
{
  guillotine {
    get(key: "/my-site/photo") {
      pageUrl(type: absolute)
      ... on media_Image {
        imageUrl(scale: "width(800)", type: absolute)
        mediaUrl(type: absolute)
      }
      attachments {
        attachmentUrl(type: absolute)
      }
    }
  }
}
Guillotine 9
{
  guillotine(project: "myproject", branch: "master", scopeKey: "/my-site") {
    get(key: "/my-site/photo") {
      pageUrl { path queryString }
      ... on media_Image {
        imageUrl(scale: "width(800)") { path queryString }
        mediaUrl { path queryString }
      }
      attachments {
        attachmentUrl { path queryString }
      }
    }
  }
}

For media, concatenate your public API or CDN base with path and queryString. Links and images in processedHtml are placeholders: rewrite them from the structured rich-text data. For pages of a site, pass its scopeKey, and adapt pageUrl.path to your frontend’s host and routing, then append pageUrl.queryString. pageUrl.baseUrl holds the Base URL configured for the selected site, or for the project without scopeKey, or null when there is none. See Building URLs from parts for client examples.

The type argument and UrlType enum are removed. Remove type: server and type: absolute from queries and variables. Your client now supplies the deployment context when building links from these components.

The params argument of imageUrl, mediaUrl and attachmentUrl is removed: the media APIs read no query parameters of their own besides those the other arguments set. pageUrl keeps it. scale is optional: without it, imageUrl gives the image as stored.

Guillotine mounted on a site or a webapp no longer accepts image transformations by default: the scale, quality, background, format and filter of imageUrl, and the imageSrcWidth and imageWidths of processHtml, are field errors there. Queries to Guillotine’s API endpoint are not affected. To keep them on a site or webapp mount, set guillotine.imageTransformations = allow on its virtual host mapping; see Image transformations.

Processed rich text

ProcessHtmlInput.type is also removed. Remove the type property from processHtml arguments and variables; imageWidths and imageSizes remain available. Links and images in processed HTML are placeholders. Rewrite each element carrying a data-link-ref or data-image-ref attribute from its entry in links or images. See Rich text. Link.content of a media link is the media content, as Link.media.content is.

Search queries

Clients using query or queryConnection must migrate to Query DSL. Clients already using queryDsl and queryDslConnection can keep those field names.

Removed Replacement

HeadlessCms.query

HeadlessCms.queryDsl

HeadlessCms.queryConnection

HeadlessCms.queryDslConnection

QueryContentConnection

QueryDSLContentConnection

Rewrite string queries as structured DSL expressions and string sorting as SortDslInput objects. For example:

Guillotine 8
{
  guillotine {
    query(query: "type = 'portal:site'", sort: "displayName ASC", first: 10) {
      _id
      displayName
    }
  }
}
Guillotine 9
{
  guillotine(project: "myproject", branch: "master") {
    queryDsl(
      query: { term: { field: "type", value: { string: "portal:site" } } }
      sort: [{ field: "displayName", direction: ASC }]
      first: 10
    ) {
      _id
      displayName
    }
  }
}

The old contentTypes and filters arguments are not available on the DSL fields. Express those constraints within the DSL query, combining conditions with boolean expressions as needed. The associated FilterInput, BooleanFilterInput, ExistsFilterInput, NotExistsFilterInput, HasValueFilterInput and IdsFilterInput types are removed; update variable declarations and any extension references to them.

For connection queries, update fragments that reference QueryContentConnection to QueryDSLContentConnection. The DSL connection supports cursor pagination and aggregations, but its query and sort arguments use the DSL input types. See Content queries for examples.

Playground at the GraphQL endpoint

Guillotine 9 removes the embedded playground that Guillotine 8 served on GET requests to the API root. Opening the API endpoint in a browser no longer displays a query editor. Send GraphQL queries using HTTP POST to the endpoint exposed by your installation.

The Query Playground in Content Studio remains available. To explore the API outside Content Studio, connect a third-party GraphQL client such as Altair.

The queryplayground.ui.mode configuration property is removed. Delete it from existing Guillotine configuration files; it no longer enables an editor at the API endpoint.

Site context and relative paths

Review this section if you use siteKey, getSite, site-relative paths, or queries scoped to a site.

Selecting a scope

The siteKey argument is renamed scopeKey, and the X-Guillotine-SiteKey request header is no longer supported. Select the scope in the GraphQL query:

{
  guillotine(project: "myproject", branch: "master", scopeKey: "/my-site") {
    getSite {
      _id
      displayName
    }
  }
}

In resolvers, read env.localContext.scopeKey in place of env.localContext.siteKey.

An omitted scopeKey selects the project for paths and URLs, as scopeKey: "/" does. getSite returns null without a site selection, including at a site-mounted endpoint; select a site explicitly when you need this field. A nonexistent scopeKey now raises an error while setting up the guillotine context.

Omit scopeKey, or pass scopeKey: "/", for project-root path and URL context: ${site} is then empty, so ${site}/blogs becomes /blogs. The project root is not a site content item, so getSite returns null for that selection, and env.localContext.scopeKey is only present when scopeKey is given. See Scope and site.

Replacing site-relative _path

The _path field no longer accepts type: siteRelative, and the ContentPathType enum is removed. _path returns the content’s full path within the project.

Select pageUrl { path } with an appropriate scopeKey for a site-relative link. This is not a direct string replacement: the old site-relative _path omitted the leading slash, while pageUrl.path includes it for descendants and URL-escapes the path. For the selected site itself, pageUrl.path is empty. Adjust URL concatenation accordingly.

A scopeKey naming a content that is not a site makes pageUrl.path relative to that content, as a virtual host mounted on it does, where the old site-relative _path was relative to its site. getSite and ${site} keep resolving to the nearest site, and the new ${scope} placeholder inserts the path of the content scopeKey names.

Query scope and URL scope

The previous automatic site-path restrictions on content queries and lookups are removed. Queries can reach content elsewhere in the project, subject to content permissions. If your client needs results from one subtree, add explicit path constraints to its DSL queries.

For page URLs, scopeKey selects both the URL base and the relative path. A request for a page URL outside the scope produces a GraphQL field error. For nested sites, choosing the parent or child site can produce different links. Test cross-site references and nested-site links against the URL context rules.

Page URLs without scopeKey

Without scopeKey, page URLs belong to the project, not to the site that contains the content: pageUrl.path is the full content path, such as /my-site/posts/first-post, and pageUrl.baseUrl is the Base URL configured for the project. This applies to pageUrl on content and on rich-text links alike. Site frontends pass the scopeKey of their site to get site-relative paths and the site’s Base URL.

Schema metadata and publication timestamps

These changes affect clients inspecting content type definitions or generating types from the schema.

Content type and form definitions

ContentType.displayName is removed. Select title instead when querying getType, getTypes or a content item’s contentType field. This does not affect Content.displayName.

The following form metadata fields and types are removed:

Type Removed fields

FormInput

customText, validationRegexp, maximize, defaultValue

FormItemSet

customText

DefaultValue

The entire type, including value and type

Remove these selections and any fragments on DefaultValue. If your tooling depends on this metadata, inspect the current definitions in Query Playground and adapt it to the available fields. ContentType.form and formAsJson remain available.

Publication timestamps

PublishInfo.from, to and first change from String to DateTime. They are still represented as timestamp strings in JSON, but generated clients and extensions may require a DateTime scalar mapping. Regenerate client types and review custom resolvers that provide these values.

Custom extensions

Review these changes if your app provides guillotine.ts or guillotine.js extensions. The extensions(graphQL) entry point remains the same. Update any references to the removed fields, arguments and types described above, then verify schema generation and queries using your custom fields.

Resolver application context

Guillotine no longer temporarily changes the portal request’s application to the app that owns a resolver. Portal URL helpers that infer an application from the request can therefore generate different URLs. Where a helper accepts an application parameter, pass the intended application explicitly.

The resolver’s script context and app.name still identify the extension app. Resolvers beneath guillotine execute in the selected project repository and branch. Treat env.localContext.scopeKey as optional. See Lifecycle and execution.

Rich-text macros

Macros in rich text come from the applications configured on the site that scopeKey selects or lies in, or on the project without one, and from the built-in macros disable and embed. Among them, a macro is matched by its name, also when written in another case. A macro of an application configured elsewhere stays in the text as written. Configure the application providing the macros on the site or project.

The data-macro-name attribute and the name of a macro hold the name of its descriptor, and its parameters are named as the inputs of the descriptor’s form, also when the text writes them in another case.

Rich-text image styles

Image styles in rich text come from the applications configured on the site that scopeKey selects or lies in, or on the project without one, rather than from all installed applications. An image whose style belongs to an application configured elsewhere is processed without that style. Configure the application providing the styles on the site or project.

ImageStyle.application (new) gives the application whose style descriptor holds the style, next to its name. Match both where several applications define styles of the same name.

TypeScript definitions

The generic parameters for resolver types have changed:

Type Guillotine 8 Guillotine 9

DataFetchingEnvironment

<Args, LocalContext, Source>

<Args, Source>

Resolver

<Args, LocalContext, Source, Return>

<Args, Source, Return>

Update generic arguments when migrating typed resolvers. Custom local-context types are no longer supplied through these generic parameters; describe the properties your resolver uses separately.

Several named exports are also removed from the root of @enonic-types/guillotine, including Field, Fields, CreationCallbacks, Enums, Type, Types, InputTypeField, InputTypeFields, InputType, InputTypes, Interface, Interfaces, Union, Unions, CreateDataFetcherResult, CreateDataFetcherResultParams and DataFetcherResult.

Use the exported Extensions type to derive types for individual properties, for example:

import type {Extensions} from '@enonic-types/guillotine';

type Types = NonNullable<Extensions['types']>;
type CreationCallbacks = NonNullable<Extensions['creationCallbacks']>;

Recompile extensions against the updated definitions and follow the TypeScript examples for the current signatures.

Migrating clients gradually

For a gradual migration, you can fork the Guillotine 8 branch and build it with a distinct application key. Change appName in gradle.properties, for example to com.example.guillotine8, and update application-key references and endpoint mappings accordingly. This allows the fork to serve existing clients while you migrate others to Guillotine 9. Public URLs can be preserved by mapping them to the fork’s endpoint.

Verify the fork against your target XP version and test your extensions with both APIs before using this arrangement. Both versions discover extension files in installed apps, so a different application key does not isolate extensions. Their schema definitions, creation callbacks and resolvers must work with both versions; check schema generation and representative client queries against each endpoint.


Contents

Contents