Developer Demo
External Content Sync
Push any external data source directly into Optimizely Graph without touching the CMS. Define a schema once via the Content Source sync API, send NdJSON over HTTP, and your data is instantly queryable alongside CMS-managed content — same GraphQL endpoint, same ISR caching.
Live Example — Referrals #
Each card is a Referral item synced from an external source via the _Item base type. Custom properties (name, comment) hold application data and are queried directly.
“Switched our content team from Contentful to Optimizely SaaS CMS. The Visual Builder makes it trivial for editors to create new pages without any engineer support.”
“The @recursive GraphQL directive saved us three days of nav implementation work. Fetches 5 levels of nested content in a single round-trip.”
“Optimizely Graph's ISR plus on-demand revalidation gives us the best of both worlds — fast static pages that update the moment editors publish.”
“We migrated our product catalog to the Graph Content Source API. Data syncs from our PIM in real-time and is immediately queryable via GraphQL.”
“Feature Experimentation and CMS in one platform is a genuine game changer. We A/B test content variations without ever leaving the same toolchain.”
“Preview mode was clean to implement. Pass a previewToken, swap to no-store caching, and editors see unpublished changes instantly in the live app.”
Querying External Content #
Once synced, external content is queried exactly like CMS-managed content — same GraphQL endpoint, same ISR caching. Query your custom properties directly; _itemMetadata fields are search-indexed internals and return null at query time.
query GetReferrals {
Referral(limit: 100, orderBy: { name: { value: ASC } }) {
items {
name
comment
}
}
}Sync Paths — Getting Data into Graph #
Four paths exist for pushing external data into Optimizely Graph. All four end up in the same place — data queryable via GraphQL alongside CMS content — but differ in who owns the pipeline, whether scheduling is managed, and what third-party tooling is involved.
Connecting to CMS — same step for every path
Once data is in Graph, wire it to CMS via Admin → Content Types → Create new… → Connect from Graph. Choose the source ID, schema, a CMS base type (Page, Component, Media, Image, or Video), and the fields to use as the content ID and display name. CMS creates a read-only connected content type editors can reference and browse in the Content Manager. Note: the label is Connect from Graph, not "Import from Graph" — a common source of confusion in the UI.
Direct to Graph
Content Source API — NdJSON over HTTP
Register a schema via PUT /api/content/v3/types and push NdJSON records via POST /api/content/v2/data. No third-party tools required. You build and run all sync logic.
⚠ Two undocumented-but-required fields: "preset": "next" on the schema and displayName___searchable in _itemMetadata. See Base Type Contracts ↓ for full examples. Additional sources may need to be enabled by Optimizely — contact support if the types endpoint returns a source-limit error.
OCP — Free Tier
Public apps + Custom Endpoints (real-time)
Included with SaaS CMS. Ships with public apps for common platforms — each app handles auth, schema registration, and field mapping automatically. If no public app exists, Custom Endpoints (real-time) adds a field-mapping UI over what you'd build for Path 1.
Public apps (free)
OCP — Paid Tier
Managed staging DB + scheduled syncs
Unlocks the OCP database as a hosted staging layer and scheduled outbound syncs to Graph via the Sync Manager. Data reaches OCP via S3 CSV, REST API, webhook, or an OCP app — from there OCP handles the sync to Graph on your chosen interval.
⚠ Paid tier required — confirm pricing with the CSM before scoping.
Reference OCP app: joshuaonwezen/ocp-product-catalog — TypeScript app with KV store, REST API, and bulk + real-time Graph sync.
CMP DAM → Graph
For existing CMP customers
For customers already on Content Marketing Platform. CMP DAM assets sync to Graph in real-time via OCP, making them queryable via GraphQL and selectable in CMS via the Browse DAM action on content reference and image properties.
Recommendation Matrix
Custom data, full control, no managed layer needed
Using Bynder, Brandfolder, Commercetools, Shopify, or WordPress
Public app handles auth, schema, and field mapping
Already on CMP, want DAM assets in Graph and selectable in CMS
Want managed scheduling + staging layer, paid tier acceptable
Confirm pricing with CSM before scoping
Free OCP tier + scheduled (not real-time) syncs from source
Free tier only supports real-time push — scheduled source syncs are paid-only
Base Type Contracts #
Graph ships three built-in base type contracts. Inherit from one when registering a content type — it adds the metadata property Graph needs to identify, index, and surface your items. All registrations require "preset": "next" and "useTypedFieldNames": true.
When pushing data, the displayName field inside any metadata object must be written as displayName___searchable. This is because displayName is marked searchable: true in the contract definition — with useTypedFieldNames enabled, Graph appends ___searchable to distinguish full-text indexed fields from plain stored fields in the payload. Fields in _assetMetadata and _imageMetadata are not searchable and keep their original names.
_Item
→ adds _itemMetadataThe base contract for all external items. Use this for structured data without a file attachment — testimonials, product catalog entries, CRM records, referrals.
Type Registration
PUT https://cg.optimizely.com/api/content/v3/types?id=rfl
Content-Type: application/json
Authorization: Basic <base64(APP_KEY:APP_SECRET)>
{
"contentTypes": {
"Referral": {
"contentType": ["_Item"],
"properties": {
"name": { "type": "String" },
"comment": { "type": "String" }
}
}
},
"preset": "next",
"useTypedFieldNames": true
}Data Payload (NdJSON)
POST https://cg.optimizely.com/api/content/v2/data?id=rfl
Content-Type: text/plain
Authorization: Basic <base64(APP_KEY:APP_SECRET)>
{"index": {"_id": 1, "language_routing": "en"}}
{
"_itemMetadata": {
"key": "ref-1",
"displayName___searchable": "Referral - Sarah Chen",
"lastModified": "2026-05-26T00:00:00.000Z",
"type": "Referral"
},
"name": "Sarah Chen",
"comment": "Switched our content team to Optimizely SaaS CMS...",
"ContentType": ["Referral"],
"Status": "Published",
"Language": { "DisplayName": "English", "Name": "en" },
"_rbac": { "read": ["Everyone"] }
}_AssetItem
→ adds _itemMetadata + _assetMetadataExtends _Item with _assetMetadata — adds fileSize, mimeType, and url. Use for PDFs, videos, audio, or any binary-backed asset from a DAM or CDN.
Type Registration
PUT https://cg.optimizely.com/api/content/v3/types?id=docs
Content-Type: application/json
Authorization: Basic <base64(APP_KEY:APP_SECRET)>
{
"contentTypes": {
"Document": {
"contentType": ["_AssetItem"],
"properties": {
"title": { "type": "String" },
"description": { "type": "String" }
}
}
},
"preset": "next",
"useTypedFieldNames": true
}Data Payload (NdJSON)
POST https://cg.optimizely.com/api/content/v2/data?id=docs
Content-Type: text/plain
Authorization: Basic <base64(APP_KEY:APP_SECRET)>
{"index": {"_id": 1, "language_routing": "en"}}
{
"_itemMetadata": {
"key": "doc-1",
"displayName___searchable": "Product Datasheet",
"lastModified": "2026-05-26T00:00:00.000Z",
"type": "Document"
},
"_assetMetadata": {
"fileSize": 245760,
"mimeType": "application/pdf",
"url": "https://example.com/docs/product-datasheet.pdf"
},
"title": "Product Datasheet",
"description": "Full technical specifications for the Enterprise plan.",
"ContentType": ["Document"],
"Status": "Published",
"Language": { "DisplayName": "English", "Name": "en" },
"_rbac": { "read": ["Everyone"] }
}_ImageItem
→ adds _itemMetadata + _assetMetadata + _imageMetadataExtends _AssetItem with _imageMetadata — adds width and height. Use for images from a DAM or media library where you need dimensions queryable at render time — for example to compute aspect ratios or avoid layout shift. All three metadata objects are required in the payload.
Type Registration
PUT https://cg.optimizely.com/api/content/v3/types?id=photos
Content-Type: application/json
Authorization: Basic <base64(APP_KEY:APP_SECRET)>
{
"contentTypes": {
"Photo": {
"contentType": ["_ImageItem"],
"properties": {
"altText": { "type": "String" },
"caption": { "type": "String" }
}
}
},
"preset": "next",
"useTypedFieldNames": true
}Data Payload (NdJSON)
POST https://cg.optimizely.com/api/content/v2/data?id=photos
Content-Type: text/plain
Authorization: Basic <base64(APP_KEY:APP_SECRET)>
{"index": {"_id": 1, "language_routing": "en"}}
{
"_itemMetadata": {
"key": "photo-1",
"displayName___searchable": "Product Hero Image",
"lastModified": "2026-05-26T00:00:00.000Z",
"type": "Photo"
},
"_assetMetadata": {
"fileSize": 1048576,
"mimeType": "image/jpeg",
"url": "https://example.com/images/product-hero.jpg"
},
"_imageMetadata": {
"width": 1920,
"height": 1080
},
"altText": "Optimizely platform dashboard screenshot",
"caption": "The Visual Builder interface showing a live page edit.",
"ContentType": ["Photo"],
"Status": "Published",
"Language": { "DisplayName": "English", "Name": "en" },
"_rbac": { "read": ["Everyone"] }
}