Documentation
Install · 5 minutesOne tag in <head> for HTML or one provider for React. The script reads its website-id, initializes itself, and swaps marked text before first paint.
00Before you begin
- A Flawlee account and your website-id from the dashboard (UUID, e.g.
b5a10d0a-90d7-4484-82a3-2035a0fba3d2). - Edit access to the page template or the root component of your app.
- HTTPS on your domain. The SDK runs over HTTPS only.
01Install
Pick your stack in the sidebar. All three boot the same SDK — only the entry point differs.
<!-- Before </head> --> <script async src="https://flawlee.com/flawlee.js" website-id="b5a10d0a-90d7-4484-82a3-2035a0fba3d2"></script>
The script reads website-id from the tag itself. Works with Tilda, WordPress, Shopify, plain HTML — no adapters needed.
npm install @flawlee/react
import { FlawleeProvider } from '@flawlee/react'; import App from './App'; ReactDOM.createRoot(document.getElementById('root')!).render( <FlawleeProvider websiteId="b5a10d0a-90d7-4484-82a3-2035a0fba3d2"> <App /> </FlawleeProvider> );
The provider creates one FlawleeSDK instance, shares it via React context, and calls init() automatically. Wrap your app root — don't nest the provider inside frequently re-rendered components.
'use client'; import { FlawleeProvider } from '@flawlee/react'; import { ReactNode } from 'react'; export function Providers({ children }: { children: ReactNode }) { return ( <FlawleeProvider websiteId={process.env.NEXT_PUBLIC_FLAWLEE_ID!}> {children} </FlawleeProvider> ); }
import { Providers } from './providers'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> ); }
The provider must live in a client component ('use client') — the SDK touches document and window and can't run on the server. For the Pages Router, wrap _app.tsx.
02Verify
Open the page, then DevTools → Console:
> window.Flawlee FlawleeSDK { analytics: FlawleeAnalytics, optimizer: FlawleeOptimizer } > window.Flawlee.analytics.getWebsiteId() "b5a10d0a-90d7-4484-82a3-2035a0fba3d2"
If getWebsiteId() returns your ID, install is done. Application → Cookies should now show flawlee_uid, flawlee_sid, and flawlee_test_id.
03What gets swapped
The optimizer scans p, span, div, a, h1–h3, and li tags, then swaps any text the decision API has a variant for. Matching uses two strategies:
- By XPath — when the dashboard records a path for a text, the server ships it and the SDK swaps that exact node.
- By content hash — fallback when the path drifts: SHA-1 of the normalized text is compared against incoming variants.
The swap happens before first paint, so visitors don't see the original copy flash. If the decision call doesn't return within 5 seconds, the page renders the original.
04Restricting changes
By default the SDK swaps any text that has a variant configured. When you need hard guarantees — a legal footer or a price — pick one of the patterns below.
React: explicit opt-in with <OptimizedText>
Only what's wrapped in the component is swappable; everything else is guaranteed untouched.
import { OptimizedText } from '@flawlee/react'; export function Hero() { return ( <section> {/* AI is allowed to rewrite */} <OptimizedText as="h1" path="main > section > h1" fallback="Cleaning for home and office" /> {/* AI never touches: price + legal */} <p className="price">from $29</p> <p className="legal">Not an offer</p> </section> ); }
React: same effect via the hook
Use the hook when the text needs to land in an attribute (aria-label, title, placeholder) or feed component logic.
import { useOptimizedText, useFlawleeGoal } from '@flawlee/react'; export function CTA() { const label = useOptimizedText({ path: 'main > section > button', fallback: 'Book a cleaning', }); const trackGoal = useFlawleeGoal(); return ( <button aria-label={label} onClick={() => trackGoal('cta-clicked')}> {label} </button> ); }
HTML: control from the dashboard
Plain HTML has no per-element opt-out — the SDK scans every listed tag. Drive scope from the dashboard: a swap only fires for texts that have a live experiment. No experiment, no change.
Tip. For copy you must lock down (prices, legal, registration data), use the React entry point and wrap allowed elements with <OptimizedText>. Everything else stays as plain JSX. That's a compile-time guarantee — a dashboard mistake can't override it.
05React & Next.js
The @flawlee/react package exports:
<FlawleeProvider websiteId>— root provider.autoInitdefaults totrue.useFlawlee()— direct SDK access:{ sdk, isReady, setConsent }.<OptimizedText path fallback as className />— component for swappable text.useOptimizedText({ path, fallback })— same as a hook.useFlawleeAnalytics()—track(eventType, data?).useFlawleeGoal()—trackGoal(label).useFlawleeConsent()—{ grantConsent, hasConsent }for CMP integration.useFlawleeOptimize()— ref that re-scans dynamically rendered subtrees.
Tracking a goal
import { useFlawleeGoal } from '@flawlee/react'; export function SignupForm() { const trackGoal = useFlawleeGoal(); async function onSubmit(values) { await api.signup(values); await trackGoal('user-signed-up'); } return <form onSubmit={onSubmit}>{/* ... */}</form>; }
06window.Flawlee
After load, the script publishes a single FlawleeSDK instance on window.Flawlee:
Flawlee.analytics— pageview, active time, scroll, forms, UTM.getWebsiteId()returns the current ID;track(type, data)sends a custom event;trackGoal(label)records a conversion.Flawlee.optimizer— fetches and applies text variants from the decision server.Flawlee.init()— safe to call again; the SDK guards against double-init.
07User consent
The SDK respects the flawlee_consent cookie. If its value is denied, both analytics and personalization stop: no events, no swaps.
From React, use the hook — it sets the cookie and re-runs variant loading:
import { useFlawleeConsent } from '@flawlee/react'; function CookieBanner() { const { grantConsent, hasConsent } = useFlawleeConsent(); if (hasConsent) return null; return ( <button onClick={() => grantConsent({ analytics: true, personalization: true })}> Accept </button> ); }
Set flawlee_consent=granted via your CMP. To revoke, set flawlee_consent=denied.
08Common issues
Console: [Flawlee SDK] Not initialized: websiteId is unknown
The <script> with website-id is missing or empty. Confirm the tag survived your build pipeline and that YOUR_ID is replaced with the real UUID.
Console: [Flawlee SDK] Already initialized
The SDK loaded twice — usually a script tag in the template plus a FlawleeProvider on top, or a header tag plus a plugin. Keep one source.
Console: [Flawlee Analytics] Disabled: consent denied
flawlee_consent=denied is set — this is intentional. To enable, call grantConsent() or clear the cookie.
Text isn't being swapped
Check, in order: (1) window.Flawlee exists; (2) at least one experiment is live for this domain; (3) the text is inside a scanned tag or wrapped in <OptimizedText>; (4) no extension is blocking requests to flawlee.com.
useFlawlee must be used within a FlawleeProvider
The hook ran outside the <FlawleeProvider> tree. In Next.js App Router, make sure the consuming component is a client component and the provider wraps {children} in layout.tsx.
CSP blocks the script
Allow flawlee.com in script-src and connect-src. The SDK uses no inline code — 'unsafe-inline' is not needed.