Architecting Headless Content Delivery with Sulu CMS
Learn how to architect headless content delivery in Sulu CMS, focusing on custom content types, GraphQL boundaries, and preventing schema drift between the admin and frontend.
21 Sept 2025, 22:44 UTC

The Problem: Schema Drift in Decoupled CMS Architectures
When using a headless CMS, a common failure point is the synchronization between the content model defined in the administration panel and the data expectations of the frontend application. If a content editor changes a field type or removes a property in the CMS, the frontend often breaks because the GraphQL query returns null or an unexpected structure.
The goal is to establish a content delivery pipeline in Sulu that minimizes this drift while maintaining a strict boundary between internal content management and external consumption.
The Smallest Suitable Design
Sulu operates on a decoupled architecture where the administration interface (built on Symfony) manages the content, and a GraphQL API serves that content to the frontend. The most efficient design for a standard content delivery requirement is to use Custom Content Types.
Instead of building a complex custom API layer, you define the content structure directly in the Sulu Admin UI. This automatically generates the corresponding GraphQL schema, reducing the amount of boilerplate code required to expose data.
Trust and Data Boundaries
To prevent unauthorized access and ensure system stability, you must define clear boundaries between the internal admin environment and the public-facing API:
- Administrative Boundary: The Sulu Admin panel should be restricted to internal VPNs or protected by strong authentication (e.g., OAuth2 or LDAP).
- API Boundary: The GraphQL endpoint is the only gateway for the frontend. Access should be governed by CORS (Cross-Origin Resource Sharing) policies to ensure only authorized domains can request data.
- Data Sanitization: Because Sulu allows rich text and flexible content blocks, the frontend is responsible for sanitizing HTML output to prevent Cross-Site Scripting (XSS) attacks.
Implementation Example: Defining a Content Type
To implement a simple "Product Highlight" section, follow these steps within the Sulu Admin:
- Navigate to Settings > Content Types.
- Create a new Content Type named
product_highlight. - Add fields:
title(Text),price(Number), andimage(Media). - Save the configuration.
To verify the delivery, run a query in the GraphQL playground (typically located at /api/graphql). You will need an API token with read permissions for the specific content area.
query {
page(identifier: "products/featured") {
content {
... on ProductHighlight {
title
price
image {
url
}
}
}
}
}
Operational Checks and Verification
To ensure the system is behaving as expected, perform these diagnostic checks:
| Check | Method | Expected Result |
|---|---|---|
| Schema Sync | Query the GraphQL playground after a field change. | The new field appears immediately in the schema. |
| Access Control | Request the API from an unauthorized domain. | Browser blocks request via CORS or server returns 403 Forbidden. |
| Payload Size | Inspect the network tab for deeply nested queries. | Response time remains under 200ms for standard page loads. |
Failure Modes
- Schema Mismatch: If a field is renamed in the admin, the frontend query will fail to find the property. Mitigation: Use a versioning strategy for your API or implement optional chaining in your frontend code to handle
nullvalues gracefully. - N+1 Query Problem: Deeply nested GraphQL queries can trigger excessive database hits. Mitigation: Implement a caching layer (like Varnish or Redis) between the Sulu API and the frontend.
- Configuration Drift: Changes made in the UI are not automatically tracked in Git. Mitigation: Regularly export the Sulu configuration to YAML files and commit them to version control.
Conditions for Redesign
The current design—relying on the integrated Symfony-based GraphQL API—is sufficient for most enterprise sites. However, you should move toward a more complex microservices architecture if:
- The API request volume exceeds the vertical scaling limits of the Symfony application.
- You require real-time content updates via WebSockets that the standard GraphQL polling cannot support.
- Content needs to be aggregated from multiple disparate data sources (e.g., an external ERP and Sulu) into a single unified graph, necessitating a dedicated Apollo Federation layer.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.