Troubleshooting
The SDK is built not to break your page: a lookup that matches nothing, or a
method a component doesn't have, does nothing and explains itself in the console
rather than throwing. So the browser console is the first place to look —
every message the SDK writes is prefixed Flex SDK:, Flex Experience SDK:, or
[flex-sdk], so searching the console for "flex" finds all of them.
Common problems
Nothing happens at all
Work through these in order:
- Are you on a published page? The SDK doesn't run in the Flex editor. Open the published URL or a preview link.
- Did the script load? Check the Network tab for a request for
flex-experience-sdk.js, and the console for a module-resolution error. - Is it a module script?
<script type="module">is required —importand top-levelawaitdon't work in a classic script. - Did
connect()resolve? Add aconsole.logafter theawait. If nothing prints, seeconnect()never resolves. - Did your lookup match?
console.log(experience.findByLocator('x').count). Zero means the locator isn't on the page — see My locator finds nothing.
connect() never resolves
connect() waits for the experience to be ready, and by default waits forever.
Usual causes:
- You're in the Flex editor. Nothing to connect to. Use a published URL.
- The root you passed doesn't contain the experience. Check your selector actually matched the right wrapper.
- The experience never mounted — a lazy-loaded embed that stayed below the fold, or a failed load.
Pass a timeout while you're debugging so you get an error instead of silence:
try {
const experience = await connect(undefined, { timeout: 10_000 })
} catch (error) {
console.error(error) // says what it was waiting for
}
My locator finds nothing
experience.findByLocator('hero').count // 0
- Check the spelling against the SDK Locators inspector in Flex. Locators are case-sensitive.
- Republish. A locator added in Flex only reaches the live page once the experience is published again.
- Is the element on this page? Lookups only see the page currently on screen. On a multi-page experience, look it up after navigating.
- One name at a time.
findByLocator('cta hero')never matches. Queryctaorhero— either finds an element carrying both. - Timing. A lookup reads the page at the moment you call it. If you're searching for something revealed later, run the lookup then, or subscribe on a container and use event bubbling.
It works on the first page, then stops
On an embedded experience (Flex Inline or Flex SSR), navigating swaps pages in
place and replaces the experience element — which silently invalidates the handle
you captured. Re-connect on the flex.page.change event and rebind your
listeners. Full pattern in
Delivery modes.
On a Ceros-hosted page, navigation is a full page load, so your script just runs again.
My video won't play
media.play() behaves like a viewer tapping play, and browsers block autoplay
without a user gesture. Starting playback from inside a component.click handler
is the reliable pattern. It's also a no-op before the video has finished
rendering — try it after connect() has resolved, not before.
My text change gets ignored or looks wrong
Use text.setText(). Writing to textContent or innerHTML on the element
fights with what Ceros renders and can corrupt the component.
Nothing works inside an iframe on my own page
Scripts in the parent page can't reach into a cross-origin iframe. If you're embedding via an iframe, the SDK script has to be part of the experience (its custom body HTML), not the surrounding page. To script from your own page, use Flex Inline or Flex SSR instead — see Delivery modes.
What the console messages mean
Warnings are deduplicated per page load, so a message inside a loop appears once rather than a thousand times.
matched 0 components that support it — the call did nothing
Your set was empty, or no member supports that method. Check the locator, and whether you're on the right page.
matched 0 components carrying the '<capability>' capability
The components you found don't have that capability — media.play() on text, for
instance. Check component.componentType and component.capabilities to see what
you actually matched.
reached N component(s) carrying '<capability>'; skipped M without it
A mixed set: the call applied where it could and skipped the rest. Usually harmless. To silence it, narrow the set first:
experience
.findByLocator('media-row')
.filter((c) => c.has('media'))
.media.play()
returns a value, so it ran on the FIRST of N matched components
You called a read — getText, getAttribute, isHidden, states.current — on a
set holding more than one component, and only one value can come back. Say which
one you meant:
set.first()?.text.getText() // the first, explicitly
set.only().text.getText() // assert there's exactly one
set.forEach((c) => console.log(c.text.getText())) // all of them
states.toggle("x") — <component> declares no such state. Available: …
The state name doesn't exist on that component. The message lists the ones that do; names must match what you called the state in Flex, and are case-sensitive.
pages.goTo("x") — no such page (N pages: …)
The slug or index doesn't exist. The message lists the available slugs;
experience.pages.list() gives you the same thing in code.
findByTag("…") — not a valid selector. Returning 0
The tag isn't a valid component tag. Tags look like cml-text, cml-video,
cml-image.
setText() — <component> exposes no setText() yet
You called text.setText() on something that isn't a text component. The call was
ignored rather than risk corrupting it.
ComponentSet.only() — expected exactly 1 component, found N
This one throws — only() is the deliberate assertion that exactly one thing
matched. Found 0 or 2+? Either the locator is wrong, or it's reused across
elements. Use .first() if any match will do, or scope the search to a section
first:
experience.findByLocator('pricing').only().findByLocator('cta').only()
setAttribute("cml-id") / setAttribute("editor-…")
These two throw. cml-id identifies the component and can't be reassigned;
editor-* attributes exist only while authoring and never reach a published page.
Both are readable — it's only writing them that's blocked. For your own values,
use setData.
createStore() persistence load/save failed; continuing in-memory
Browser storage was unavailable — private browsing, a full quota, or a sandboxed iframe. The store keeps working in memory for the rest of the visit; it just won't remember anything next time.
a store subscriber for "<key>" threw
Your subscriber callback raised an error. It's caught so it can't break other subscribers, but the error is yours to fix — the message includes it.
[flex-sdk] connect needs an experience root element, got null
You passed null to connect(), almost always from a querySelector that found
nothing. Check the selector. If the element genuinely doesn't exist yet, pass a
container that does and let connect wait for the experience inside it.
[flex-sdk] connect timed out after Nms
No ready experience appeared inside the root you passed before your timeout
elapsed. See connect() never resolves.
Still stuck?
Contact your Ceros representative with:
- The published URL where it reproduces.
- The exact console output.
- Your script, or the part that matters.
SDK_VERSIONfrom the page —import { SDK_VERSION } from '@ceros/flex-experience-sdk'— which pins down exactly which bundle you loaded.