Skip to main content

Quickstart

In about five minutes you'll name a component in Flex, add a script to your experience, and watch it change the page.

You'll need a Flex experience you can edit and publish, and a browser from 2023 or later (the SDK ships as a standard ES module and relies on import maps). There is nothing to install.

1. Give a component a locator

Open your experience in Flex, select the element you want your script to control, and open the SDK Locators section of the inspector. Add a locator — for this walkthrough, headline.

A locator is a name you choose. It's how your code finds the element, so pick something stable and descriptive. A few things worth knowing:

  • Reuse a name to group elements. Give three cards the same card locator and one call reaches all three.
  • One element can carry several names, space-separated, exactly like CSS classes. An element with cta hero is found by cta or hero.
  • Locators survive restyling, resizing, and moving the element. They break only if someone renames them.

2. Add your script

Put a module script in your experience's custom body HTML (Flex → experience settings). Anything valid in a <script type="module"> works.

<script type="module">
import { connect } from '@ceros/flex-experience-sdk'

const experience = await connect()

experience.findByLocator('headline').text.setText('Hello from the SDK')
</script>

connect() returns a promise that resolves once the experience has loaded and is ready to script. Because it's a module script, top-level await is available — no DOMContentLoaded wrapper needed.

3. Publish and check

Publish the experience and open the published URL. The headline should read "Hello from the SDK".

If it doesn't, open your browser's console. Rather than throwing, the SDK writes a message saying what it couldn't find — search for "flex" to spot it, then see Troubleshooting.

This won't run in the Flex editor

The SDK attaches to a published experience. In the editor there is nothing for it to connect to, so connect() never resolves. Test on a published URL or a preview link.

4. Go further

Everything else is built from the same three moves. A couple of examples to build on:

const experience = await connect()

// React to a click, anywhere in the experience
experience.findByLocator('cta').on('component.click', (event) => {
console.log('clicked', event.target.cmlLocator)
})

// Pause every video on the experience
experience.findByTag('cml-video').media.pause()

// Reveal something the design hides by default
experience.findByLocator('promo').visibility.show()

// Move to another page
experience.pages.goTo('pricing')

Next: Locators for finding components, Capabilities for what you can drive, or a complete worked example.


Loading the SDK

How you import the SDK depends on how your experience reaches the page. Both forms give you the same API.

On a Ceros-hosted page

A published experience served from its own Ceros URL — including one you've put in an <iframe> — resolves the bare specifier for you:

<script type="module">
import { connect } from '@ceros/flex-experience-sdk'
const experience = await connect()
</script>

Every published page carries an import map that points that name at the SDK bundle. There's no loader script and no global to wait for, and nothing is downloaded until a script actually imports it.

On your own page (Flex Inline or Flex SSR)

When the experience is embedded in a page you own, that page has no Ceros import map, so import the bundle from its URL instead:

<script type="module">
import { connect } from 'https://assets.ceros.site/js/flex-experience-sdk.js'

const experience = await connect(/* a root — see below */)
</script>
  • The URL is stable and always serves the current SDK: https://assets.ceros.site/js/flex-experience-sdk.js.
  • A URL import is self-contained, so load order relative to Ceros' own embed scripts doesn't matter. The assets host sends the CORS headers a cross-origin module import needs.
  • To fetch it early: <link rel="modulepreload" href="https://assets.ceros.site/js/flex-experience-sdk.js">.
Embedded experiences differ in more than the import

On Flex Inline and Flex SSR, Ceros does not inject your experience's custom head/body HTML into your page, and page navigation happens in place rather than as a full page load. Both change how you write SDK code — see Delivery modes.

connect() reference

function connect(
experienceRoot?: Document | ShadowRoot | Element | null,
options?: { timeout?: number },
): Promise<Component>

experienceRoot — where to look for the experience. Defaults to the experience on the page, which is what you want for a page holding one experience. Pass a Document, ShadowRoot, or Element to search inside a specific container — see Multiple experiences.

options.timeout — milliseconds to wait before rejecting. Omit it (or pass 0) to wait indefinitely. Set it when the experience might be lazy-loaded and you'd rather get a clear error than a promise that never settles:

try {
const experience = await connect(container, { timeout: 10_000 })
} catch (error) {
console.error('Experience never became ready', error)
}

The promise rejects immediately if you pass null — usually a querySelector that found nothing. If the experience hasn't mounted yet, pass a container that does exist and let connect wait for the experience to appear inside it.

When it's safe to call

connect() is deliberately hard to call too early:

  • It works from inline scripts, async modules, and tag-manager-injected scripts. Script order doesn't matter.
  • Given a container (document, or a wrapper element), the experience need not exist yet — connect watches that container and resolves when the experience appears and becomes ready. Lazy-mounted and late-hydrated embeds are fine.
  • Given the experience element itself, that element must already be in the DOM. Pass a container when it might not be.

Multiple experiences on one page

There's no experience ID to pass. Instead, hand connect a container that holds the one you want. Every embed carries a data-flex-manifest-url attribute, so that URL makes a reliable selector — and the same call works for both Flex Inline and Flex SSR:

const experience = await connect(
document.querySelector(
'[data-flex-manifest-url="https://…/pricing/manifest.v1.json"]',
),
)

Call connect once per experience, and keep each handle with the code that uses it. Lookups on one handle never reach into another experience.

Skipping the wait

If you already know the experience is ready — in a test, typically — you can wrap its element directly and skip the readiness check:

import { wrap } from '@ceros/flex-experience-sdk'

const experience = wrap(document.querySelector('ceros-experience-viewer'))

On a real page, prefer connect(). Lookups made before the experience is ready return empty sets.

Things to know

  • Re-connect after a page change on an embedded experience. Flex Inline and Flex SSR swap pages in place and replace the experience element, which silently invalidates a handle you captured earlier. Re-connect on the flex.page.change event — see Delivery modes. On a Ceros-hosted page, navigation is a full page load, so your script simply runs again.
  • Look components up when you need them. A lookup queries the page at the moment you call it. Components that appear later aren't added to a set you captured earlier.
  • There is no global. No window.flexSdk — each script imports the module and gets its own handle.

Checking the version

import { SDK_VERSION } from '@ceros/flex-experience-sdk'

console.log(SDK_VERSION) // e.g. '0.1.0'

Useful when reporting an issue: it tells us exactly which bundle your page loaded.