When I first started experimenting with connecting LangGraph workflows and Sitecore projects, my goal was something simple yet practical, something along these lines:
- Automate content editors work,
- …still keep humans in charge.
Over the last few months I’ve experimented with LangGraph-style workflows that connect:
- XM Cloud (via Experience Edge and headless APIs),
- Sitecore Content Hub (for articles and DAM assets),
- Sitecore CDP/Personalize (for behavioral signals and experiments),
- and model providers for text and image generation.
This write up summarizes what actually worked, what didn’t, and the patterns I’d reuse if I were starting again.
The focus is content operations:
- assisted content creation and editing,
- localization and translation,
- moderation and policy checks,
- image generation and editing anchored in Content Hub/DAM,
- and continuous optimization loops informed by CDP/Personalize.
All of it is orchestrated by LangGraph running as a cloud‑hosted service, with Sitecore products on one side and editors on the other.
LangGraph as the content workflow orchestrator
At a high level, the architecture I’ve had the best results with looks like this:
Key choices:
- LangGraph runs as a long‑lived service (either LangGraph Cloud or my own FastAPI + LangGraph deployment), not as ad‑hoc scripts.
- Sitecore systems talk to it via webhooks or API calls (for example, “run the translation flow for this item”).
- The flows are narrow and explicit: “translate this item”, “propose edits for this page”, “create image variants”, “summarize experiment performance and propose changes”.
- Editors always get a chance to review and approve before anything touches live content.
The rest of the post dives into three representative flows, then into implementation details.
Flow 1: Assisted content creation & editing for XM Cloud
The first workflow I usually build is an editorial copilot for XM Cloud pages and articles.
Scenario
An editor is working on a new article or landing page in XM Cloud. They want help with:
- improving clarity and structure,
- generating variants (e.g., long/short, executive summary),
- aligning tone with brand guidelines,
- and optionally creating initial drafts from a brief.
But they still want to stay in control of the content and publishing.
High-level workflow
In practice this looks like:
-
Trigger
- I expose a simple command in the head (e.g., a button in a custom editor UI) that calls a small backend service.
- That service sends a
POSTto the LangGraph endpoint with the XM item ID and the fields to work on.
-
Fetch & classify
- A LangGraph node calls Experience Edge Preview to fetch the item content and metadata.
- Another node classifies the content (topic, product line, funnel stage) using a model plus a knowledge base of existing content.
-
Draft suggestions
- One or more nodes generate:
- tightened version of the existing copy,
- alternative headlines and intros,
- optional SEO‑leaning variant with different emphasis.
- Prompts are grounded by brand voice docs stored in Content Hub (or a simple internal knowledge base).
- One or more nodes generate:
-
Moderation
- A moderation node checks for policy violations, PII, regulatory issues, etc.
- If anything is flagged, the flow halts and posts to a moderation channel instead of writing back.
-
Review & apply
- LangGraph posts a summary + diff to Slack/Teams (or sends a callback the head can poll).
- The editor chooses which suggestions to accept, optionally edits them, and then confirms.
- A final node writes the accepted text into XM Cloud as a new draft version, never directly to the published one.
Example LangGraph integration
Below is a simplified version of how I modeled that flow. It omits error handling and auth for clarity.
from typing import TypedDict, List, Optional
from langgraph.graph import StateGraph, END
import httpx
XM_EDGE_ENDPOINT = "https://edge.sitecorecloud.io/api/graphql/v1"
class ContentState(TypedDict):
item_id: str
language: str
fields: dict
classification: dict
suggestions: dict
moderation_flags: List[str]
editor_decision: Optional[dict]
async def fetch_xm_content(state: ContentState) -> ContentState:
query = """
query Item($id: String!, $language: String!) {
item(path: $id, language: $language) {
id
name
fields {
name
value
}
}
}
"""
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(
XM_EDGE_ENDPOINT,
json={"query": query, "variables": {"id": state["item_id"], "language": state["language"]}},
headers={"sc_apikey": "<EDGE_PREVIEW_API_KEY>"},
)
data = resp.json()["data"]["item"]
state["fields"] = {f["name"]: f["value"] for f in data["fields"]}
return state
async def generate_suggestions(state: ContentState) -> ContentState:
# Pseudo-call to an LLM; in practice use your provider of choice.
body = state["fields"].get("Body", "")
# ... call LLM with body + brand guidelines ...
state["suggestions"] = {
"tightened": body, # replace with actual LLM output
"headline_options": [], # etc.
}
return state
async def moderate(state: ContentState) -> ContentState:
# Call moderation provider or custom policy checker.
state["moderation_flags"] = []
return state
async def apply_changes(state: ContentState) -> ContentState:
decision = state["editor_decision"]
if not decision:
return state
# Call XM Cloud management API / headless API to write a new draft version.
# I keep this in a separate module with strong typing and tests.
return state
graph = StateGraph(ContentState)
graph.add_node("fetch_xm_content", fetch_xm_content)
graph.add_node("generate_suggestions", generate_suggestions)
graph.add_node("moderate", moderate)
graph.add_node("apply_changes", apply_changes)
graph.set_entry_point("fetch_xm_content")
graph.add_edge("fetch_xm_content", "generate_suggestions")
graph.add_edge("generate_suggestions", "moderate")
graph.add_edge("moderate", "apply_changes")
graph.add_edge("apply_changes", END)
editorial_copilot_flow = graph.compile()
In a real implementation I add:
- conditional edges (e.g., if
moderation_flagsis not empty, go to a “halt and notify” node), - an interrupt step to wait for editor input, using LangGraph’s built‑in human‑in‑the‑loop pattern,
- proper retries and circuit breakers around Sitecore APIs.
Flow 2: Translation & localization across XM Cloud and Content Hub
The second workflow I’ve found very impactful is localization, especially when content lives partly in XM Cloud and partly in Content Hub.
Scenario
- Source content (e.g., an English product page) is authored in XM Cloud.
- Key descriptive fragments and imagery live in Content Hub.
- The business needs localized versions in 5–10 languages, but human translators are reserved for the highest‑value content.
The goal is not “machine‑translate everything.” It’s:
- propose translations grounded in existing terminology and glossaries,
- allow linguists to review and adjust where it matters,
- automatically handle the long tail of lower‑risk content.
Workflow overview
Integration points that worked
-
XM Cloud → LangGraph
- Webhook or scheduled poll based on item status (“Ready for localization”).
- Experience Edge Preview to fetch the English source version.
-
Content Hub → LangGraph
- REST or GraphQL to fetch related content fragments and metadata (e.g., product descriptions, disclaimers).
- Asset metadata (alt text, captions) when images are part of the localized story.
-
LangGraph → Content Hub / XM Cloud
- For AI‑assisted content, LangGraph writes:
- localized text into Content Hub entities, and/or
- localized versions of XM Cloud items (via Headless Services or management APIs).
- For human‑only workflows, LangGraph opens a translation job in Content Hub or an external TMS, and waits for completion.
- For AI‑assisted content, LangGraph writes:
A simple LangGraph localization flow
from langgraph.graph import StateGraph, END
from typing import TypedDict, List
class LocalizeState(TypedDict):
item_id: str
source_language: str
target_languages: List[str]
source_fragments: dict
translations: dict
async def gather_fragments(state: LocalizeState) -> LocalizeState:
# Fetch main body from XM Cloud (Edge Preview)...
# Fetch supporting text from Content Hub (e.g., product description)...
state["source_fragments"] = {
"title": "...",
"body": "...",
"product_short_desc": "...",
}
return state
async def translate_fragments(state: LocalizeState) -> LocalizeState:
translations = {}
for lang in state["target_languages"]:
# Call your translation model with glossary and examples.
translations[lang] = {
"title": "...",
"body": "...",
"product_short_desc": "...",
}
state["translations"] = translations
return state
async def persist_translations(state: LocalizeState) -> LocalizeState:
# For each target language, write:
# - a new language version in XM Cloud (headless write)
# - or localized fragments in Content Hub
return state
graph = StateGraph(LocalizeState)
graph.add_node("gather_fragments", gather_fragments)
graph.add_node("translate_fragments", translate_fragments)
graph.add_node("persist_translations", persist_translations)
graph.set_entry_point("gather_fragments")
graph.add_edge("gather_fragments", "translate_fragments")
graph.add_edge("translate_fragments", "persist_translations")
graph.add_edge("persist_translations", END)
localization_flow = graph.compile()
In reality I augment this with:
- a cost/priority decision node that decides whether a locale should be human‑only, AI‑assisted, or skipped,
- a QA node that checks for obvious issues (length constraints, placeholders left untranslated, etc.),
- and hooks back into Content Hub’s own translation workflows when they’re already in place.
Flow 3: Image generation and editing with Content Hub DAM
Once text workflows are stable, image workflows are a natural extension. Content Hub makes this easier because it already owns asset metadata and storage.
Scenario
- Editors need multiple hero/banner image variants per campaign, per locale.
- They want to experiment quickly but keep all assets properly catalogued in Content Hub.
- Legal and brand teams need an audit trail and control over what gets used.
Workflow overview
- An editor creates or updates a Content Hub campaign or content item and flags it as “needs image variants.”
- LangGraph picks up the item, reads:
- the copy,
- target audience,
- locale,
- and any existing brand imagery.
- A node generates prompt candidates for an image model based on that context and brand rules.
- Another node calls the image generation API (DALL·E, Midjourney, Stable Diffusion, etc.).
- Generated images are ingested back into Content Hub DAM as assets with:
- tags,
- source prompts,
- usage restrictions.
- Editors get a notification with thumbnails and approve which ones to use in XM Cloud.
- A final node updates the relevant image fields in XM Cloud (or in Content Hub variants used by XM Cloud).
Integration diagram
From a technical standpoint this flow looks very similar to the editorial one; the difference is:
- the tool nodes call a binary API (images) instead of text,
- and I’m more conservative about where images are allowed to be used without human review.
Experimenting with CDP/Personalize
Example: underperforming landing pages
-
A scheduled LangGraph flow calls CDP/Personalize APIs to:
- retrieve experiment results and engagement metrics for key pages,
- flag pages with poor performance or high bounce rates.
-
For each flagged page, the flow:
- fetches the underlying XM Cloud item,
- summarizes what the page is trying to do and how it’s currently structured,
- compares it to higher‑performing pages in the same category.
-
An LLM node then proposes:
- alternative headlines,
- restructured content blocks,
- or alternative calls to action.
-
The flow creates tasks (e.g., in Content Hub, JIRA, or Azure DevOps) with:
- a summary of the issue,
- suggested changes,
- links to metrics from CDP/Personalize,
- and deep links to the relevant XM Cloud item.
-
Editors review, accept or adjust, and then rerun the normal publish flow.
The key is that CDP/Personalize is not writing content—it’s providing feedback signals that LangGraph uses to prioritize work and pre‑fill suggestions.
Hosting LangGraph and exposing flows as APIs
In all of these experiments I’ve had better results treating LangGraph as a versioned backend service rather than local scripts.
Example FastAPI wrapper
Here’s a highly simplified example of how I expose a flow as an HTTP endpoint that Sitecore systems can call:
from fastapi import FastAPI
from pydantic import BaseModel
from langgraph.graph import StateGraph
app = FastAPI()
class RunEditorialRequest(BaseModel):
item_id: str
language: str
# Assume editorial_copilot_flow is the compiled graph from earlier.
@app.post("/workflows/editorial-copilot")
async def run_editorial_copilot(req: RunEditorialRequest):
state = {"item_id": req.item_id, "language": req.language}
result = None
async for event in editorial_copilot_flow.astream(state):
# You can stream intermediate events to logs if desired.
result = event
return result
On the Sitecore side, this is just another HTTP call from:
- a custom UI in the head,
- a background service,
- or an integration function sitting next to your XM Cloud app.
I keep:
- auth config (API keys, tokens) in environment variables or Key Vault,
- per‑flow quotas and timeouts to avoid surprises,
- and structured logging around every node invocation.
Lessons learned, so far
After a few iterations, a few patterns have stood out.
-
Small, composable flows beat “do everything” agents.
My most reliable results came from flows that do one thing well—“suggest edits”, “prepare translations”, “generate image variants”—rather than giant graphs that try to handle the entire content lifecycle. -
Sitecore stays the source of truth.
I never let LangGraph write directly to production content or templates. It writes drafts, opens tasks, or updates non‑live fields. Normal XM Cloud / Content Hub workflows still govern what goes live. -
Human‑in‑the‑loop is non‑negotiable for public content.
Every flow has an obvious “pause and ask a human” point, usually implemented via LangGraph interrupts plus Slack/Teams or a custom UI. -
Version workflows like code.
I keep LangGraph graphs, prompts, and integration code in the same repo as the XM Cloud/Content Hub integration. Changes go through pull requests and get reviewed like any other backend change. -
Start where the pain is highest, not where the tech is coolest.
The biggest wins came from:- helping editors clean up and structure existing content,
- speeding up low‑risk localization,
- and giving content teams a clearer view of which pages are underperforming, powered by CDP/Personalize data.
Useful links
- Sitecore XM Cloud docs — https://doc.sitecore.com/xmc/en/home.html
- Sitecore Content Hub docs — https://doc.sitecore.com/ch/en/users/content-hub/content-hub.html
- Sitecore CDP & Personalize docs — https://doc.sitecore.com/cdp/en/home.html
- Experience Edge best practices — https://doc.sitecore.com/xmc/en/developers/xm-cloud/experience-edge-best-practices.html
- LangGraph overview — https://langchain-ai.github.io/langgraph/