Skip to content

AI Code Generation — Flutter head for XM Cloud

Created:
Updated:

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:

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:

At a high level, the contract between XM Cloud and the heads looks like this:

XM Cloud / Experience Edge

GraphQL endpoint

Flutter widgets for mobile / kiosk head

Next.js components for web head


Starting from the Figma component matrix

By the time I consider a Flutter head, I usually already have:

Instead of inventing a second library from scratch, I extend that same matrix to call out explicitly:

For shared components I keep two parallel contracts:

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:

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:

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:

Authors preview the web version of a page inside XM Cloud. The Flutter app then:

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:

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:

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:

On the Flutter side I have used two patterns:

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:

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


Previous Post
AI-Powered Stack — Series overview and roadmap
Next Post
Custom Claude Code Commands