Documentation

Install · 5 minutes

One 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

01Install

Pick your stack in the sidebar. All three boot the same SDK — only the entry point differs.

index.html
<!-- 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.

terminal
npm install @flawlee/react
src/main.tsx
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.

app/providers.tsx
'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>
  );
}
app/layout.tsx
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:

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, h1h3, and li tags, then swaps any text the decision API has a variant for. Matching uses two strategies:

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.

Hero.tsx
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.

CTA.tsx
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. autoInit defaults to true.
  • 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

SignupForm.tsx
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:

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:

CookieBanner.tsx
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.