Skip to content

New: AI agent integration via Model Context Protocol (MCP).Learn more

Installation guide

The getting started page is the ten-minute tour. This page is the complete installation: every layer an existing Evolve project has to touch before an editor can sign in and push a page. Authoring spans four places in your stack, and each one gets a section below:

  1. The CMS service — gains the authoring GraphQL surface
  2. The authoring service — a new service hosting chat, library, and push routes
  3. The gateway — learns to forward the editor’s CMS token
  4. The storefront — mounts the editor chrome

You will need:

  • A Contentful OAuth application (Developers → OAuth applications). Its redirect URL points at your authoring service’s callback route.
  • An AI provider key (Anthropic, OpenAI, or Google) for chat.
  • Local HTTPS with Portless (guide). This is not optional: the editor session cookie is shared between the storefront, the authoring service, and the gateway on a common parent domain (*.evolve.localhost in dev), which requires real hostnames and HTTPS.

Generate these once and configure them consistently — most installation problems trace back to a mismatch here:

Secret Used by Purpose
SESSION_ENCRYPT_KEY (32 bytes) authoring service, storefront, gateway Encrypts the editor session cookie
SESSION_SIGN_KEY (32 bytes) authoring service, storefront, gateway Signs the session data cookie
JWT_ISSUER / JWT_AUDIENCE authoring service, storefront, gateway Claims validated on the session token
AUTHORING_INTERNAL_SECRET authoring service, CMS service Protects the internal OAuth exchange endpoints
CONTENTFUL_OAUTH_CLIENT_ID / _SECRET CMS service The OAuth app credentials

The three SESSION_*/JWT consumers all read the same cookie — if any of them uses different keys, the session silently fails verification.

The authoring GraphQL surface (library search, page push, publish flows) ships as ContentfulAuthoringModule in @evolve-framework/contentful/authoring. Compose it next to the delivery module in your CMS service:

backend/services/cms-contentful/src/module.ts
import { ContentfulModule } from "@evolve-framework/contentful";
import { ContentfulAuthoringModule } from "@evolve-framework/contentful/authoring";
import { CompositeModule } from "@evolve-framework/core";
import { config } from "./config.ts";
export const createModule = (): CompositeModule => {
return new CompositeModule([
new ContentfulModule({
config: {
spaceId: config.CONTENTFUL_SPACE_ID,
environment: config.CONTENTFUL_ENVIRONMENT,
accessToken: config.CONTENTFUL_ACCESS_TOKEN,
previewToken: config.CONTENTFUL_PREVIEW_TOKEN,
managementToken: config.CONTENTFUL_MANAGEMENT_API_ACCESS_TOKEN,
},
}),
new ContentfulAuthoringModule({
config: {
spaceId: config.CONTENTFUL_SPACE_ID,
environment: config.CONTENTFUL_ENVIRONMENT,
oauthClientId: config.CONTENTFUL_OAUTH_CLIENT_ID,
oauthClientSecret: config.CONTENTFUL_OAUTH_CLIENT_SECRET,
internalSecret: config.AUTHORING_INTERNAL_SECRET,
},
}),
]);
};

Add the three new fields to the service’s config class as optional env fields — the service still boots without them, and the OAuth routes return 503 until they are configured.

What this module brings:

  • The authoring* queries and mutations on the CMS subgraph. Every resolver requires the editor’s CMS token (forwarded by the gateway as X-Authoring-Cms-Token) and rejects with REQUIRES_AUTHORING_ACCESS otherwise — public storefront traffic is unaffected.
  • The /internal/oauth/contentful/* REST routes that exchange and refresh editor OAuth tokens on behalf of the authoring service, guarded by AUTHORING_INTERNAL_SECRET.

Whether authoring runs embedded in the CMS service (shown above) or as its own subgraph is a deploy-time choice — drop the module from the composite and mount it in a separate service to split it out.

Finally, recompose the supergraph so the gateway knows the new fields:

Terminal window
task -d backend/services/graphql-gateway supergraph

Create a new service (the reference uses port 4007) following the standard service anatomy. Its module mounts the editor routes, the AI tools, and optionally the MCP endpoint and workflows — the getting started page shows the module code in full.

Configuration highlights beyond the shared secrets:

Variable Example (dev) Purpose
GATEWAY_URL http://localhost:4000/graphql Where authoring tools execute GraphQL
ALLOWED_ORIGINS https://storefront.evolve.localhost CORS allowlist for the chrome
AUTHORING_SERVER_URL https://authoring.evolve.localhost Public URL of this service
STOREFRONT_BASE_URL https://storefront.evolve.localhost Where login redirects back to
AUTHORING_COOKIE_DOMAIN evolve.localhost Parent domain the session cookie is set on
CMS_CONTENTFUL_INTERNAL_URL http://localhost:4004 The CMS service’s internal OAuth routes
CHAT_PROVIDER / CHAT_MODEL + API key anthropic / model id The chat model

Two integration points people forget:

  • Body limit: chat requests carry inline image uploads; raise the HTTP bodyLimit (the reference uses 25 MB) in createDomainService.
  • Local development: register the service in the monolith (backend/services/monolith/src/index.ts, import + moduleNames) and add a Portless alias (portless alias authoring.evolve 4007 via the service’s setup:portless script).

The gateway resolves the editor’s session into a CMS token and forwards it to subgraphs as X-Authoring-Cms-Token. This is the authoring_token router plugin, configured in the router config:

backend/services/graphql-gateway/router.config.yaml (excerpt)
plugins:
authoring_token:
enabled: true
config:
issuer: "${JWT_ISSUER}"
audience: "${JWT_AUDIENCE}"
encrypt_keys:
- id: "1"
secret: "${AUTHORING_SESSION_ENCRYPT_KEY}"
sign_keys:
- id: "1"
secret: "${AUTHORING_SESSION_SIGN_KEY}"

The AUTHORING_SESSION_* values must equal the authoring service’s SESSION_* keys. Two delivery paths flow through this plugin:

  • Header path: the authoring service proxies chat/library/push calls and sets X-Authoring-Cms-Token itself (it decrypted the cookie server-side). The plugin passes it through.
  • Cookie path: the chrome calls the gateway directly from the storefront origin with the __authoring_session cookie; the plugin decrypts it and extracts the CMS token.

An invalid or expired session never fails the request — the plugin simply forwards no token, and the CMS resolvers answer REQUIRES_AUTHORING_ACCESS. The chrome reacts by refreshing the session and retrying.

Also on the gateway:

  • CORS: add the storefront origin to the router’s CORS policy — the chrome makes credentialed cross-origin calls.
  • Production only: the router enforces persisted documents (require_id: true), so the chrome’s GraphQL operations must be registered as Hive App Deployment document IDs alongside your storefront’s.
  • The router only reads its config at boot — restart it after changes.

Install @evolve-framework/authoring-core and @evolve-framework/authoring-chrome, then:

  1. Mount the chrome in the layout that wraps your main pages, via a wrapper around AuthoringServerMount — see getting started. The mount reads the session cookie server-side and renders nothing for anonymous shoppers.
  2. Host bindings: implement the bindings provider that bridges the chrome to your router, block renderer, and locale logic. The platform reference is frontend/site/src/authoring/host-bindings.tsx.
  3. Proxy handling: the storefront’s src/proxy.ts must let /authoring paths through before i18n routing and expose the requested path to the chrome (an x-pathname header in the reference).
  4. Schema registry codegen: add the @evolve-framework/authoring-core/codegen plugin to your codegen config (getting started).
  5. Dev origins: with Portless, add the aliases to allowedDevOrigins in next.config.ts — Next.js otherwise blocks cross-origin dev resources, which silently prevents all client hydration (the chrome then never appears):
frontend/site/next.config.ts (excerpt)
const nextConfig: NextConfig = {
allowedDevOrigins: ["storefront.evolve.localhost", "*.evolve.localhost"],
// ...
};

Environment: the storefront needs AUTHORING_SERVER_URL, the gateway URL, and the same SESSION_* / JWT values as the authoring service (it verifies the session cookie during server rendering).

Work through these in order — each step isolates a layer:

  1. Boot: pnpm dev — the monolith log should show the authoring service starting alongside the others.

  2. Guard: query the gateway without a session:

    Terminal window
    curl -s localhost:4000/graphql -H 'content-type: application/json' \
    -d '{"query":"{ authoringListPages { id } }"}'

    Expect a REQUIRES_AUTHORING_ACCESS error — that proves the CMS module is mounted and enforcing.

  3. Login: visit the authoring service’s login route, complete the Contentful OAuth round-trip, and confirm the __authoring_session cookie is set on the parent domain (Domain=evolve.localhost).

  4. Chrome: open the storefront — profile badge, prompt bar, and drawer should appear.

  5. End to end: open the drawer (library search exercises the cookie→gateway→CMS path), then generate and Push a page.

Symptom Likely cause
Cookie set, but no chrome Storefront can’t verify the session: SESSION_*/JWT mismatch with the authoring service, or blocked dev origins (check allowedDevOrigins)
Chrome visible, gateway calls fail with CORS errors Storefront origin missing from the router CORS policy, or the router wasn’t restarted after the config change
REQUIRES_AUTHORING_ACCESS while logged in Gateway plugin keys differ from the authoring service’s SESSION_* keys, so the cookie decrypts to nothing
OAuth exchange returns 503 OAuth credentials or AUTHORING_INTERNAL_SECRET not configured on the CMS service
Chat works but pushes fail The editor’s Contentful user lacks management permissions on the space