Mappings

Contents

Bind HTTP functions and filters to specific content types or site-relative URLs

Introduction

Mappings are the first custom processing step in the site service. Mappings execute before the component rendering. You may define mappings for both HTTP functions and filters. The use cases are vast.

Usage

Mappings are defined in the site descriptor (cms/site.yaml). Examples of use cases:

  • Bind an HTTP function to a specific URL pattern (e.g. /mypath).

  • Bind an HTTP function to a specific set of content via content matches (e.g. only the portal:fragment content type).

  • Bind a filter to part or all of the site structure.

cms/site.yaml mapping examples
kind: "Site"
mappings:
- controller: "/cms/foobar/api.ts" (1) (2)
  pattern: '/api/v\d+/.*'          (3)
  order: 10
- controller: "/cms/pages/default/default.ts"
  match: "type:'portal:fragment'"  (4)
- filter: "/cms/foobar/filter.ts"  (5)
  pattern: '/dont-match-me/.*'
  invertPattern: true
  order: 10
- filter: "/cms/services/image.ts" (6)
  service: "image"
1 controller — path to an HTTP function implementation.
2 order — execution order across apps. Default 50. Lower number means higher priority, with 1 being the pole position. See Execution order.
3 pattern — regular expression matched against the site-relative URL. Default /.*. See Pattern mappings.
4 match — property condition evaluated against the contextual content. See Match mappings.
5 filter — path to an HTTP filter implementation.
6 service — system service handled by this mapping (e.g. the .../_/image/... endpoint). See Service mappings.
Each mapping must specify either filter: or controller: — never both — and at least one of pattern: or match:.

Execution order

An application may contain multiple mappings, and multiple applications can be added to a site. If two apps within a site share the same mapping path or match expression, the mapping with the lower order value wins.

If several mappings have the same order value, the sort order of the applications on the site determines which mapping is used.

Which mappings apply is decided once, before the first filter runs, from the content originally requested. A filter that re-routes rendering to another content therefore does not change the selection — the mappings already chosen keep running, in order, against the re-routed request.

Pattern mappings

Pattern-based mappings can be used to take over the request for a specific URL before content rendering. Common cases include exposing an API endpoint on a contextual URL like /api, or triggering a filter for every request within the site.

The pattern property specifies a regular expression matched against the request URL.

The part of the URL involved is the path relative to the site where the application is configured. For example, if a site is deployed via the virtual host example.com/mysite/, then pattern: '/api/.*' matches requests with URL ending in example.com/mysite/api/.*. Protocol, host, and port are not involved in the pattern matching.

If the pattern contains the question mark ? character, the URL to match also includes query parameters. The query parameters are normalized, so they are always in alphabetical order.

For example, the pattern pattern: '/api\?category=foo&key=\d'+ matches both:

  • /api?category=foo&key=123

  • and /api?key=123&category=foo

The question mark ? is escaped with a backslash in the pattern because it is a quantifier in regular expressions. Use single-quoted YAML strings for any pattern containing backslashes — YAML’s double-quoted form interprets backslash escapes and would corrupt regex tokens like \d.

Set invertPattern: true on a mapping to negate the result of evaluating the regular expression.

The pattern string must be a valid regular expression.

Examples:

  • Anything ending with .json within the /api/* path: pattern: '/api/.*\.json'

  • All items within the site, including the site itself: pattern: '/.*'

  • Regex with query string: pattern: '/endpoint\?bar=\d&foo=.*'+

  • Inverted via invertPattern: true together with pattern: '/section/.*'

App key substitution

A pattern may include the literal token ${app}, which is expanded at parse time to the owning application’s key. This lets you write site-mapping patterns that reference the app’s own key without hardcoding it.

cms/site.yaml in an app whose key is com.foo.bar
mappings:
  - pattern: "/_/static/${app}/.+"
    controller: "/site/mappings/static-controller.js"

Match mappings

Match-based mappings can be used to automatically render content types without using page templates or associating content with an implementation directly. A common case is automatic handling of the content type portal:fragment.

The match property specifies a condition related to the content corresponding with the requested URL path.

The condition takes the form of a property path followed by a :, and a value.

The property path can be one of the content properties (_id, _name, _path, type, displayName, hasChildren, language, valid) or any other custom property within the content.

Examples:

  • Match fragment content type: match: "type:'portal:fragment'"

  • Match content within the /features/ path: match: "_path:'/features/.*'"

  • Match custom property with string value: match: "data.employee.type:'developer'"

  • Match custom property with a number: match: "data.product.category:42"

  • Match custom property with a boolean: match: "x.com-enonic-myapp.menuItem.show:true"

  • Match any content: match: "type:'.+'"

The expected value can be either a regular expression matched against the property value, or simply a string, number, or boolean (true | false).

Missing/unavailable content does not match any condition. Omit match if you need to map an HTTP function or filter to a URL that does not correspond to any content.

Service mappings

Service-based mappings bind service requests directly to HTTP functions or filters.

Supported services:

component

Override the direct component rendering at /_/component/<component-path>.

image (deprecated)

Override the legacy image service at /_/image/<id>[:<fingerprint>]/<scale>/<name> (e.g. /_/image/123456/scale-100-100/photo.jpg). Replaced by the media:image Universal API.

attachment (deprecated)

Override the legacy attachment service at /_/attachment/<mode>/<id>[:<fingerprint>]/<name> (e.g. /_/attachment/inline/123456/logo.png). Replaced by the media:attachment Universal API.

Universal APIs mounted as points (/_/<app>:<api>) cannot be remapped through site mappings — they are routed before the site service processes mappings. If you need custom behavior on a service-point URL, implement a custom Universal API instead.

Re-routing to another content

XPXP8.1.08.1.0 A filter reached through a mapping can change which content is rendered while the visitor’s URL stays the same. Set contentPath on the request before handing it on, and the site service resolves the request against that content instead:

Render a variant content at the canonical URL
export function filter(req, next) {
    if (req.cookies.experiment === 'b') {
        // Visitors in the experiment get /mysite/campaign/variant-b,
        // still under the /mysite/campaign address they asked for
        req.contentPath = `${req.contentPath}/variant-b`;
    }

    return next(req);
}

This is a rendering re-route, not a redirect: no Location header, no second request, and path, url and the client’s address bar are untouched. Downstream, the request describes the new content. getContent() returns it, and its page or page template is resolved and rendered as though it had been requested directly. Only the target moves, so a re-route cannot leave the current project or branch: the new path is looked up in the same repository and branch, and the site is re-derived from it.

Rules:

  • Only documented fields are read from the object you pass to next(). Mutate req and hand it on. A fresh object works as long as it carries contentPath, but anything else you put on it is lost.

  • The re-routed content’s permissions apply. The lookup itself is privileged, so a filter can target content the visitor cannot read, but the response then fails with 403, as it would on a direct request. A path that resolves to nothing yields 404, naming the path the filter set.

  • Mappings are already chosen. Which mappings match is decided before any filter runs, from the originally requested content. A re-route does not re-run that matching, so it neither picks up mappings that would match the new content nor cancels the ones already selected. The remaining filters and any controller mapping in the chain receive the re-routed request.

  • Each filter may re-route once, starting from whatever the previous filter passed it. Inside the filter that re-routes, both before and after next(), getContent() still reports the original content. The change applies to the request handed onwards.

  • Generated URLs follow the re-route. pageUrl() without id or path, relative path values and component URLs resolve against the re-routed content, not the address the visitor asked for. A variant rendered at /mysite/campaign would link to itself as /mysite/campaign/variant-b. Pass the canonical target explicitly; the filter can hand it down with setCustomLocalAttribute() before it re-routes.

Keeping links on the canonical address
// the filter, before re-routing
import {setCustomLocalAttribute} from '/lib/xp/context';

setCustomLocalAttribute('com.example.myapp.canonicalPath', req.contentPath);
req.contentPath = `${req.contentPath}/variant-b`;

// the page, when linking to itself
import {get as getContext} from '/lib/xp/context';
import {pageUrl} from '/lib/xp/portal';

const canonical = getContext().attributes['custom.com.example.myapp.canonicalPath'] as string | undefined;
const selfUrl = pageUrl({path: canonical});

Re-routing only maps an incoming address to content. It does not change how URLs are generated, and Content Studio’s built-in preview still renders a content at its real path. A URL scheme of your own therefore also needs a URL function of your own in place of pageUrl(), a decision about whether the real path stays reachable, and, for editors to preview the scheme, a Live view extension that points the preview at your address.

Build the new path from values you control rather than interpolating a query parameter straight into it. A content path is a lookup key, and path segments have their own validation rules.

Contents

Contents