Skip to main content
FlowFn
IntegrationsTemplatesPricingDocsBlogSign inStart free
All documentation

Playground SDK reference

UpdatedJul 4, 2026Reading time6 min read

The SDK

FlowFn injects a window.flowfn object into every playground iframe before your JS runs. It's the only supported bridge between your code and the FlowFn host page.

flowfn.data — sheets

flowfn.data.sheets()
Returns an array of sheet slugs available in this playground.
flowfn.data.get(slug)
Synchronous read of the rows currently cached in the iframe — usually the first page for large sheets. Returns an array of row objects keyed by column.key plus an internal _id. Pair with rowCount + fetch below when you need every row.
flowfn.data.fetch(slug)
Asynchronous. Pages any rows not yet cached into the cache and resolves with the full row array (same shape as get). Concurrent calls for the same slug are de-duplicated. Use whenever rowCount(slug) exceeds get(slug).length.
flowfn.data.rowCount(slug)
Returns the server-side total row count as of the iframe's last load. May exceed the currently-cached get(slug).length for large sheets, and won't reflect rows added by other visitors or workflows until the iframe reloads. Returns 0 for unknown slugs.
flowfn.data.columns(slug)
Returns the column definitions for the given sheet: [{ id, key, name, type }].
flowfn.data.has(slug)
Boolean — does this sheet exist?
flowfn.data.insert(slug, cells)
Append a row. Gated by the sheet's Allow inserts opt-in. Returns the materialised row { _id, ...cells }. Rejects with sheet_write_disabled when off, sheet_full at the plan's row cap, invalid_cells when cell types fail validation. See Data sheets.
flowfn.data.update(slug, rowId, cells)
Patch an existing row's cells. Gated by Allow updates. No per-row ownership — any visitor can mutate any row when this is on. Cells you omit stay untouched.
flowfn.data.delete(slug, rowId)
Remove a row. Gated by Allow deletes. Returns { ok: true }. This is a soft delete — the row leaves all SDK reads immediately but is recoverable by the owner from the Data tab's Recently deleted panel for 90 days. There is no public restore.

flowfn.upload — visitor file uploads

flowfn.upload(fileOrInput, options?)
Upload a file from a public visitor. Accepts a File, a Blob, or an <input type="file"> element. Returns a Promise resolving to { id, url, filename, content_type, size } where url is a freshly-signed read URL (~1h TTL). Rejects with uploads_disabled / unsupported_type / file_too_large / daily_upload_cap / rate_limited. Off by default — flip the editor header's Uploads toggle to enable. See User file uploads.

flowfn.snapshot & flowfn.fire — workflow triggers

flowfn.snapshot()
Returns the current values of all input, text, and js_value @keys as an object: { name_input: 'Alice', message_text: '...' }.
flowfn.fire(triggerKey)
Fires a workflow trigger by @key. Auto-collects a snapshot. Click handlers on registered trigger @keys fire automatically — call this only when you need to fire from custom code.
flowfn.onResponse(callback)
Subscribe to trigger results. The callback receives { trigger_key, workflow_run_ids?, error? }. Returns an unsubscribe function.

flowfn.invoke & flowfn.onInvokeResult — bound actions

flowfn.invoke(keyName, inputs)
Runs a single platform-tool or AI action bound to a platform_tool_action / ai_tool_action @key. Returns a Promise that resolves with { status: 'completed', key, outputs } or rejects with a sanitised error code (rate_limited, invalid_inputs, handler_error, invocation_timeout, ...). inputs keys must match the binding's declared input names (which default to the action's field keys but can be renamed in the drawer to avoid collisions).
Auto-fire on click
Clicks on any [data-key] whose key is an action type auto-invoke. The SDK snapshots same-named @keys and sends only the declared inputs — extra fields are stripped. No need to call flowfn.invoke manually for click-driven actions.
flowfn.onInvokeResult(callback)
Subscribe to results from any action invocation (manual or auto-fired). The callback receives { ok: true, key, status, outputs } on success or { ok: false, key, error } on failure. Use res.key to branch when several action keys share one playground. Returns an unsubscribe function.

flowfn.fetchData — registered reads

flowfn.fetchData(refKey, inputs?)
Runs a platform-tool action you registered under the Fetch tab by its ref_key. Returns a Promise resolving to the action's outputs, or rejecting with an Error whose .code is the server-side error code. Unlike invoke, ref_key is a plain identifier (not a secret) — authentication is by allowlist membership + the playground's public access policy (published / password / embed gates). Use for reads (list/get); use invoke for writes. See Fetch entries.

flowfn.server.call — server-side code

flowfn.server.call(name, args?)
Runs a function you wrote in the Server tab and declared in the Functions panel. args are validated against the function's declared inputs, then the function runs on FlowFn with access to your sheets, registered tools/fetch entries, and agent/workflow dispatch. Resolves with the function's return value, or rejects with an Error whose .code is the server-side code (not_found, invalid_inputs, rate_limited, daily_cap_reached, TIMEOUT, RUNTIME_ERROR). Any function you declare and enable in the Functions panel is callable both from your editor preview and by visitors of your published page. See Server-side code.

flowfn.location — page URL hash and query

Your playground runs in a sandboxed iframe, which has its own internal address — so window.location.search inside it is always empty and won't show the query string of the page your visitor is on. FlowFn forwards the published page's URL hash and query string to your code instead:

flowfn.location.query
The parsed query parameters as an object. For a visitor on your-site.flowfn.com/?ref=email&id=7, this is { ref: 'email', id: '7' }.
flowfn.location.search
The raw query string (e.g. ?ref=email&id=7).
flowfn.location.hash
The URL fragment (e.g. #support). For convenience this is also mirrored onto window.location.hash before your code runs, so existing window.location.hash code and in-page #anchor links work as you'd expect.

Deep links work automatically. If the page is opened with a fragment that matches an element id — e.g. your-site.flowfn.com/#pricing with a <section id="pricing"> on the page — FlowFn scrolls that element into view on load. No code needed.

flowfn.currentPage — which page you're on

flowfn.currentPage is the current page's slug as a string — '' (empty string) on the home/index page, and always the canonical lowercase kebab-case slug (e.g. about-us) on every other page. Use it in shared site JS for page-conditional logic, like highlighting the active nav link:

// shared JS — mark the current page's nav link
document.querySelectorAll('nav a[href]').forEach((a) => {
  const slug = a.getAttribute('href').replace('/', '');
  if (slug === flowfn.currentPage) a.classList.add('active');
});

flowfn.state — shared client state

A lightweight key/value store for state that must survive navigation between the pages of a multi-page site (a cart, a checkout in progress, a wizard step). The sandbox can't use localStorage, so FlowFn keeps it for you, namespaced per playground and shared across all of that site's pages. Values must be JSON-serialisable; every method returns a Promise. For shared, queryable, or server-side data use Data sheets instead.

flowfn.state.set(key, value, options?)
Stores a value. By default it lives in the browser session (cleared when the tab closes); pass { persist: true } to keep it across visits.
flowfn.state.get(key)
Resolves with the stored value, or null if unset.
flowfn.state.remove(key), flowfn.state.keys(), flowfn.state.clear()
Delete one key, list all keys, or wipe this site's state.
await flowfn.state.set('cart', [{ id: 'sku_1', qty: 2 }]);
const cart = await flowfn.state.get('cart'); // [{ id: 'sku_1', qty: 2 }]

flowfn.rewire — for dynamic DOM

If your JS adds elements after the page loads, call flowfn.rewire() to re-apply data-key attributes to anything matching a registered selector. Idempotent — safe to call repeatedly.

Sandbox notes

The SDK runs synchronously before your JS, so top-level calls work:

// runs at script load — flowfn is already defined
const rows = flowfn.data.get('users');
document.body.innerHTML = JSON.stringify(rows);

The sandbox blocks browser storage APIs (localStorage, cookies, IndexedDB). For lightweight client state that survives page navigation use flowfn.state (above); for shared or persistent data use Data sheets or a workflow trigger. Because the iframe has its own internal address, read the page's URL parameters via flowfn.location rather than window.location.search.

Spotted an issue or have feedback?

support@flowfn.com
Back to docs hub →