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. Flex Inline and Flex SSR swap pages in place and replace the experience element, which silently invalidates a handle you captured earlier — and a standalone experience with the Page navigation setting on does the same. Re-connect on the flex.page.change event. Write it even when you expect a full page load, where it simply never fires: you cannot tell the two apart from inside your script. Full pattern in Delivery modes.
  • 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.