Most XM Cloud projects I touch start with a Next.js head, but it’s not the only option for the “Head”. What if we need a mobile app or kiosk experience that reuses the same CMS content and similar components?
My default answer for mobile apps (unless we have to go native for platform only features) is Flutter: one codebase, great performance, and a nice fit for multi-channel UI. In this post I describe how I have wired up a Flutter-based head on top of XM Cloud and Experience Edge when the main web head is still Next.js — and how I phrase prompts so coding agents follow Flutter and XM Cloud best practices instead of just dumping UI code:
- consuming the same XM Cloud content and component contracts,
- going against GraphQL endpoints from Flutter,
- and leaning on AI agents to generate both Next.js components and Flutter widgets off the same Figma component matrix.
This is written in a “this is how I’ve done this kind of projects” style rather than as a strict tutorial, but I thought this may be helpful for someone who might need a quick start.
If you want to see the rest of the pipeline that this Flutter head plugs into, the other posts in the series are:
- AI Code Generation — From prompt to XM Cloud component matrix
- AI Code Generation — Figma to Next.js components with Figma MCP and Claude Code
- AI Code Generation — Local-first component library with Storybook
- AI Code Generation — Promoting Storybook components into XM Cloud (BYOC)
At a high level, the contract between XM Cloud and the heads looks like this:
Starting from the Figma component matrix
By the time I consider a Flutter head, I usually already have:
- a Figma component matrix for the site,
- Storybook stories for the web head,
- and TypeScript view models aligned with XM Cloud templates.
Instead of inventing a second library from scratch, I extend that same matrix to call out explicitly:
- which components are shared between Next.js and Flutter,
- which are web-only,
- and which are mobile-only but still backed by XM Cloud content.
For shared components I keep two parallel contracts:
- a TypeScript interface for the web head,
- a Dart class for the Flutter head.
I often let an AI coding agent do the mechanical work of mapping TS → Dart. A prompt along these lines has worked well:
You are a senior TypeScript and Dart engineer working on a Sitecore XM Cloud project with:
- a Next.js web head, and
- a Flutter mobile head.
Inputs:
- TypeScript interfaces that describe XM Cloud-backed components used by the Next.js head.
- The XM Cloud template definitions for those components (field names and types).
Goals:
- Produce equivalent, idiomatic Dart model classes for Flutter.
- Keep models aligned with XM Cloud templates so they can be deserialized directly from Experience Edge GraphQL JSON.
Requirements:
- Preserve property names where it makes sense (camelCase for both TypeScript and Dart).
- Use null-safe Dart with required/optional fields that reflect the XM Cloud template (required for non-null fields, optional for nullable fields).
- Add simple factory constructors (e.g. `fromJson`) that map from the GraphQL JSON shape into the Dart model.
- Do not invent fields that do not exist in XM Cloud templates or the TS interfaces.
Output:
- Dart model classes under a hypothetical `lib/models` folder.
- A short Markdown table mapping: TS interface → Dart class → XM template fields.
With this in place, the Figma matrix becomes the shared source for both heads: same components, same fields, different UI technologies.
Content SDK vs GraphQL from a Flutter point of view
On the XM Cloud side, I usually rely on:
- Experience Edge GraphQL for content and layout,
- and sometimes a small BFF (backend-for-frontend) where security or caching needs are stronger.
The Sitecore Content SDK is officially supported for JavaScript/TypeScript and .NET. I have not seen a first-party Dart/Flutter version. In Flutter projects this has pushed me toward two patterns:
- call the Experience Edge GraphQL endpoint directly from Flutter using
httporgraphql_flutter, or - point Flutter at a BFF that itself uses the Content SDK or GraphQL.
In more regulated environments, the BFF route has worked better because the real XM Cloud keys and personalization rules stay server-side and the mobile app only sees a simplified JSON contract.
Using XM Cloud pages as a content backend for Flutter
One subtle but important constraint: XM Cloud does not build Flutter screens out of widgets for you. There is no native concept of a “Flutter rendering” that appears in XM Cloud Pages.
In practice, this workflow has worked for me:
- define normal Sitecore templates and renderings for the Next.js head,
- compose pages in XM Cloud Pages or in the content editor,
- treat those pages as a content backend for both Next.js and Flutter.
Authors preview the web version of a page inside XM Cloud. The Flutter app then:
- reads the page definition and data via GraphQL,
- maps each rendering to a Flutter widget based on
componentName, - and builds the mobile screen at runtime.
It is more manual and involved than having “Flutter pages in XM Cloud”, but it stays realistic and keeps XM Cloud focused on content rather than mobile layout.
Getting example JSON from the XM Cloud GraphQL endpoint
To make AI-generated widgets useful, I have found it crucial to show the agent real XM Cloud JSON, not just field lists.
On XM Cloud, the Experience Edge GraphQL endpoint (for example https://edge.sitecorecloud.io/api/graphql/v1) can return the layout and components for a given route path. A simplified query I often start from looks like this:
query PageByRoute($site: String!, $language: String!, $routePath: String!) {
layout(site: $site, language: $language, routePath: $routePath) {
item {
id
name
url {
path
}
}
renderings {
placeholders {
name
elements {
uid
componentName
dataSource {
... on HeroBanner {
title {
value
}
body {
value
}
image {
jsonValue
}
}
}
}
}
}
}
}
In my workflow I usually:
- run this query in the XM Cloud GraphQL playground or via a small script,
- capture the
data.layoutJSON for a few representative pages, - and save those payloads next to the component specs and Figma screenshots.
This gives the coding agent a realistic view of how pages, placeholders, and renderings are represented and how content for each component shows up in JSON.
Letting agents build Flutter widgets (and Next.js components)
Once I have:
- the Figma component matrix row or frame,
- a JSON payload from the GraphQL layout query,
- and the Dart/TypeScript models for that component,
then coding agents (OpenAI’s coding models, Claude Code, Google’s CLI, etc.) become genuinely useful.
A prompt I use for Flutter widget generation looks roughly like this:
You are a senior Flutter and React engineer on a Sitecore XM Cloud project.
Project context:
- Web head: Next.js using TSX components backed by XM Cloud.
- Mobile head: Flutter app that consumes XM Cloud content via Experience Edge GraphQL.
- State management in Flutter: Riverpod; navigation: GoRouter.
Inputs:
- Figma description for one component (text + screenshots, or a Figma MCP reference),
- the current Next.js (TSX) implementation for this component, if it exists,
- the XM Cloud GraphQL JSON snippet that contains this component’s data,
- the Dart model that represents the content for this component.
Goal:
- Implement a **reusable Flutter widget** that renders this component for the mobile app, following idiomatic Flutter patterns.
Requirements (Flutter best practices):
- The widget must be a small, focused, composable widget (stateless where possible) that accepts the Dart model as a required parameter.
- Map fields from the XM JSON → Dart model → widget UI in a single, clear place (no hidden globals).
- Use Flutter layout idioms (`Column`, `Row`, `Flexible`, `ListView`, etc.) rather than trying to pixel-match the web version.
- Apply theming and typography via `Theme.of(context)` where appropriate.
- Keep layout responsive: handle small and large screens gracefully (no hard-coded magic numbers that break on tablets/phones with different sizes).
- Do not perform HTTP/GraphQL calls inside the widget; treat it as a pure renderer that gets data from higher layers.
Requirements (code structure and styling):
- Place the widget under `lib/widgets/<component_name>/<component_name>_widget.dart`.
- If the design calls for non-trivial styling, create or update a co-located `lib/widgets/<component_name>/<component_name>.module.scss` (or similar) and describe how it should be wired through Flutter’s theming / styling approach used in this app.
- Keep dependencies minimal; do not introduce new packages unless strictly necessary.
Output:
- A single Dart file with the widget implementation.
- A short example of using this widget in a demo screen or story-style page.
- A brief note explaining any assumptions, TODOs, or areas where the Figma design and the GraphQL data shape do not line up cleanly.
When I use a Figma MCP or similar integration, I also attach the relevant Figma frames directly so the agent can see spacing, typography, and states rather than guessing.
On some projects I have pointed the same prompt shape at Next.js and Flutter simultaneously: one run produces or refactors the TSX component, another produces or refactors the Dart widget, both mapped to the same XM Cloud fields.
Giving agents context via AGENTS.md
An AGENTS.md file in the Flutter repo has been surprisingly effective at keeping generated code consistent. A trimmed-down example:
# AGENTS.md — Flutter head for XM Cloud
## Project scope
- Flutter app that consumes Sitecore XM Cloud content via Experience Edge GraphQL.
- Web head is Next.js; content contracts are shared when possible.
## Tech stack
- Flutter with null safety enabled.
- State: Riverpod; routing: GoRouter.
- HTTP: `graphql_flutter` or `http` + a small repository layer.
- Layout and design tokens: Tailwind-aligned spacing/typography where it makes sense, documented in this repo.
- Styling: component-scoped styles via SCSS (or Dart theming extensions), no new global styles without discussion.
## Structure
- Models live under `lib/models`.
- Widgets live under `lib/widgets/<component_name>`.
- Demo / story-style screens live under `lib/demo`.
- Any platform-specific glue (e.g., integrations with native APIs) lives under `lib/platform`.
## Content contracts
- Do not invent new CMS fields.
- Keep property names aligned with the TypeScript models.
- Treat XM Cloud as the source of truth for content shape.
## What you should generate
- Pure Dart/Flutter code (no platform channels unless asked).
- Small, composable widgets rather than monolithic screens.
- Example usage code for any new widgets.
- Clean separation between data fetching (repositories/providers) and UI widgets.
I usually link this from the root README and from the prompts I send to agents, so they pick it up as part of the context.
Where XM Cloud keys live in Flutter projects
Secrets in a client app are never truly secret, so I treat XM Cloud keys used directly from Flutter as public-ish and try to keep them:
- out of Git,
- scoped and rate-limited on the XM side,
- or, ideally, replaced by a BFF that owns the real key.
On the Flutter side I have used two patterns:
- For local development, a
.envfile consumed viaflutter_dotenv, kept out of version control. - For CI and production builds,
--dart-defineflags andString.fromEnvironment:
const edgeEndpoint = String.fromEnvironment('XM_EDGE_ENDPOINT');
const edgeApiKey = String.fromEnvironment('XM_EDGE_API_KEY');
and then:
flutter build ios \
--dart-define=XM_EDGE_ENDPOINT=https://edge.sitecorecloud.io/api/graphql/v1 \
--dart-define=XM_EDGE_API_KEY=your_public_mobile_key
When a backend is available, I prefer to let Flutter talk to that backend, and let the backend talk to XM Cloud with a more privileged key that never leaves the server.
Recap: one pipeline, multiple heads
On the projects where this pattern worked well, the flow looked roughly like this:
- Figma component matrix drives both Next.js components and Flutter widgets.
- XM Cloud templates and renderings define pages once; authors work in Pages.
- Experience Edge GraphQL layout queries provide real JSON payloads.
- Agents generate or refactor Dart widgets and TSX components from the same contracts and payloads.
- Flutter treats XM Cloud as a content backend, not as a layout engine.
It is more manual and involved than a “Flutter-first CMS” experience, but it is also quite doable. XM Cloud stays excellent at content and composition, Flutter stays excellent at multi-platform UI, and AI agents help bridge the gap between Figma, GraphQL JSON, and running code.
Additional resources
- Flutter documentation: https://docs.flutter.dev
- Dart language documentation: https://dart.dev/guides
- Sitecore XM Cloud docs: https://doc.sitecore.com/xmc/en/developers/xm-cloud/index-en.html
- Experience Edge GraphQL docs: https://doc.sitecore.com/xmc/en/developers/experience-edge-for-xm-cloud/index-en.html
graphql_flutterpackage: https://pub.dev/packages/graphql_flutter- Riverpod state management: https://riverpod.dev
- GoRouter navigation: https://pub.dev/packages/go_router