> ## Documentation Index
> Fetch the complete documentation index at: https://docs2.growthbook.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Visual Editor Troubleshooting

> Fixes for common issues with the GrowthBook Visual Editor — variations not showing, broken preview links, picking the right element with the tree selector, unresponsive pages on framework-heavy sites, why JavaScript changes seem to affect every variant, image replacement, and more.

export const CommercialFeature = ({feature, description}) => {
  const commercialFeatures = {
    "adv-presentations": {
      plan: "enterprise",
      displayName: "Adv Presentations"
    },
    "advanced-permissions": {
      plan: "pro",
      displayName: "Advanced Permissions"
    },
    "ai-byok": {
      plan: "enterprise",
      displayName: "Ai Byok"
    },
    "ai-suggestions": {
      plan: "enterprise",
      displayName: "AI Suggestions"
    },
    archetypes: {
      plan: "pro",
      displayName: "Archetypes"
    },
    "audit-logging": {
      plan: "enterprise",
      displayName: "Audit Logging"
    },
    "cloud-proxy": {
      plan: "pro",
      displayName: "Cloud Proxy"
    },
    "code-references": {
      plan: "pro",
      displayName: "Code References"
    },
    "contextual-bandits": {
      plan: "enterprise",
      displayName: "Contextual Bandits"
    },
    "custom-hooks": {
      plan: "enterprise",
      displayName: "Custom Hooks"
    },
    "custom-launch-checklist": {
      plan: "enterprise",
      displayName: "Custom Launch Checklist"
    },
    "custom-markdown": {
      plan: "enterprise",
      displayName: "Custom Markdown"
    },
    "custom-metadata": {
      plan: "enterprise",
      displayName: "Custom Metadata"
    },
    "custom-roles": {
      plan: "enterprise",
      displayName: "Custom Roles"
    },
    dashboards: {
      plan: "enterprise",
      displayName: "Dashboards"
    },
    "decision-framework": {
      plan: "pro",
      displayName: "Decision Framework"
    },
    "encrypt-features-endpoint": {
      plan: "pro",
      displayName: "Encrypt Features Endpoint"
    },
    "environment-inheritance": {
      plan: "enterprise",
      displayName: "Environment Inheritance"
    },
    "events-forwarder": {
      plan: "pro",
      displayName: "Events Forwarder"
    },
    "experiment-impact": {
      plan: "enterprise",
      displayName: "Experiment Impact"
    },
    "feature-configs": {
      plan: "enterprise",
      displayName: "Feature Configs"
    },
    "funnel-metrics": {
      plan: "pro",
      displayName: "Funnel Metrics"
    },
    "hash-secure-attributes": {
      plan: "pro",
      displayName: "Hash Secure Attributes"
    },
    "historical-power": {
      plan: "pro",
      displayName: "Historical Power"
    },
    holdouts: {
      plan: "enterprise",
      displayName: "Holdouts"
    },
    "incremental-refresh": {
      plan: "enterprise",
      displayName: "Incremental Refresh"
    },
    "json-validation": {
      plan: "enterprise",
      displayName: "JSON Validation"
    },
    "large-saved-groups": {
      plan: "enterprise",
      displayName: "Large Saved Groups"
    },
    learnings: {
      plan: "enterprise",
      displayName: "Learnings"
    },
    livechat: {
      plan: "pro",
      displayName: "Livechat"
    },
    "manage-official-resources": {
      plan: "enterprise",
      displayName: "Manage Official Resources"
    },
    "metric-correlations": {
      plan: "enterprise",
      displayName: "Metric Correlations"
    },
    "metric-effects": {
      plan: "enterprise",
      displayName: "Metric Effects"
    },
    "metric-groups": {
      plan: "enterprise",
      displayName: "Metric Groups"
    },
    "metric-populations": {
      plan: "pro",
      displayName: "Metric Populations"
    },
    "metric-slices": {
      plan: "enterprise",
      displayName: "Metric Slices"
    },
    "multi-armed-bandits": {
      plan: "pro",
      displayName: "Multi Armed Bandits"
    },
    "multi-metric-queries": {
      plan: "enterprise",
      displayName: "Multi Metric Queries"
    },
    "multi-org": {
      plan: "enterprise",
      displayName: "Multi Org"
    },
    "multiple-sdk-webhooks": {
      plan: "pro",
      displayName: "Multiple Sdk Webhooks"
    },
    "no-access-role": {
      plan: "enterprise",
      displayName: "No Access Role"
    },
    "override-metrics": {
      plan: "pro",
      displayName: "Override Metrics"
    },
    "pipeline-mode": {
      plan: "enterprise",
      displayName: "Pipeline Mode"
    },
    "post-stratification": {
      plan: "enterprise",
      displayName: "Post Stratification"
    },
    "precomputed-dimensions": {
      plan: "pro",
      displayName: "Precomputed Dimensions"
    },
    "prerequisite-targeting": {
      plan: "enterprise",
      displayName: "Prerequisite Targeting"
    },
    prerequisites: {
      plan: "pro",
      displayName: "Prerequisites"
    },
    "product-analytics-dashboards": {
      plan: "pro",
      displayName: "Product Analytics Dashboards"
    },
    "project-admin-role": {
      plan: "enterprise",
      displayName: "Project Admin Role"
    },
    "quantile-metrics": {
      plan: "pro",
      displayName: "Quantile Metrics"
    },
    "ramp-schedules": {
      plan: "pro",
      displayName: "Ramp Schedules"
    },
    redirects: {
      plan: "pro",
      displayName: "Redirects"
    },
    "regression-adjustment": {
      plan: "pro",
      displayName: "CUPED"
    },
    releases: {
      plan: "enterprise",
      displayName: "Releases"
    },
    "remote-evaluation": {
      plan: "pro",
      displayName: "Remote Evaluation"
    },
    "require-approvals": {
      plan: "enterprise",
      displayName: "Require Approvals"
    },
    "require-project-for-features-setting": {
      plan: "enterprise",
      displayName: "Require Project For Features Setting"
    },
    "require-project-for-sdk-connections-setting": {
      plan: "enterprise",
      displayName: "Require Project For Sdk Connections Setting"
    },
    "retention-metrics": {
      plan: "pro",
      displayName: "Retention Metrics"
    },
    "safe-rollout": {
      plan: "pro",
      displayName: "Safe Rollout"
    },
    saveSqlExplorerQueries: {
      plan: "pro",
      displayName: "Save SQL Explorer Queries"
    },
    "schedule-feature-flag": {
      plan: "pro",
      displayName: "Schedule Feature Flag"
    },
    "scheduled-revisions": {
      plan: "enterprise",
      displayName: "Scheduled Revisions"
    },
    scim: {
      plan: "enterprise",
      displayName: "SCIM"
    },
    "sequential-testing": {
      plan: "pro",
      displayName: "Sequential Testing"
    },
    "share-product-analytics-dashboards": {
      plan: "enterprise",
      displayName: "Share Product Analytics Dashboards"
    },
    simulate: {
      plan: "pro",
      displayName: "Simulate"
    },
    sso: {
      plan: "enterprise",
      displayName: "SSO"
    },
    "sticky-bucketing": {
      plan: "pro",
      displayName: "Sticky Bucketing"
    },
    teams: {
      plan: "enterprise",
      displayName: "Teams"
    },
    templates: {
      plan: "enterprise",
      displayName: "Templates"
    },
    "unlimited-managed-warehouse-usage": {
      plan: "pro",
      displayName: "Unlimited Managed Warehouse Usage"
    },
    "visual-editor": {
      plan: "pro",
      displayName: "Visual Editor"
    }
  };
  const {plan, displayName} = commercialFeatures[feature];
  const isEnterprise = plan === "enterprise";
  const defaultDescription = isEnterprise ? "is available on Enterprise plans." : "is available on Pro and Enterprise plans.";
  const planLabel = isEnterprise ? "Enterprise" : "Pro";
  const containerStyle = isEnterprise ? {
    backgroundColor: "color-mix(in srgb, var(--indigo-a3) 60%, transparent)"
  } : {
    backgroundColor: "color-mix(in srgb, var(--amber-a3) 60%, transparent)"
  };
  const badgeStyle = isEnterprise ? {
    boxShadow: "inset 0 0 0 1px var(--indigo-a8)",
    color: "var(--indigo-a11)"
  } : {
    boxShadow: "inset 0 0 0 1px var(--amber-a8)",
    color: "var(--amber-a11)"
  };
  return <div className="flex items-start gap-2 mb-4 p-3 text-sm leading-[1.4] rounded-lg" style={containerStyle} role="note">
      <span className="inline-flex items-center justify-center px-1.5 h-5 text-xs font-medium rounded-full shrink-0 leading-none" style={badgeStyle}>
        {planLabel}
      </span>
      <div className="flex-1 leading-[1.3]">
        <strong className="font-semibold">{displayName}</strong>{" "}
        {defaultDescription} {description}
      </div>
    </div>;
};

<CommercialFeature feature="visual-editor" />

When the [Visual Editor](/app/visual) doesn't behave as expected, this page lists the most common causes and fixes. Each item is independently linkable — share the anchor in support threads.

If your issue isn't here, the **Debug Panel** in the Visual Editor (see [Preview and QA](/app/visual/preview-and-qa#the-debug-panel)) is the single best diagnostic tool. Open it before filing a support ticket.

## Variation isn't showing on my site

By far the most common issue. The fix is almost always one of:

1. **Refresh the page** Sometimes the visual editor's changes don't apply until a hard reload (Cmd-Shift-R) to bust the cache, especially if you had the page open while making changes in GrowthBook.
2. **`Include Visual Experiments` toggle is off** on the SDK Connection. Visual experiments are stripped from the SDK payload unless this is enabled. Go to **SDK Configuration → SDK Connections**, edit the connection your site uses, and turn it on.
3. **The SDK isn't loaded yet** when the variation tries to render. Custom JS variations need to wait for the target element — use the `waitFor` helper from [Manual mode → Custom JavaScript](/app/visual/manual-mode#custom-javascript).
4. **URL targeting doesn't match.** The current URL doesn't satisfy the experiment's URL targeting rule. Check the rule and verify with the Debug Panel.
5. **The experiment is still a draft** and the SDK Connection doesn't have **Include Drafts** enabled. Draft experiments aren't delivered by default.

## Preview link returns the control

You're hitting `?experiment-id=1` but seeing variation 0. In order of likelihood:

1. **The experiment is a draft** and **Include Drafts is off** on the SDK Connection. Toggle it on, then hard-reload (Cmd-Shift-R) to bust the SDK cache.
2. **Wrong client key.** Your site is loading a different SDK Connection than the one the experiment is associated with. Check the SDK Connection in GrowthBook against the client key in your site's source.
3. **CDN or browser cache** is serving the pre-querystring version of the page. Hard-reload, or test in a private window.
4. **URL targeting excludes this URL.** Even with the querystring, the SDK won't run the experiment on URLs that don't match the targeting pattern.

## Element selector picks the wrong thing

When you hover an element, the blue highlight selects an ancestor or sibling instead of what you wanted:

<Frame>
  <img src="https://mintcdn.com/growthbook-ea15456d/eqfxHndrQcOIlRaT/static/images/visual-editor/ve-tree-selector.png?fit=max&auto=format&n=eqfxHndrQcOIlRaT&q=85&s=46e8e65e695e2eb1683392ad598dc5b4" alt="The Visual Editor tree selector, showing the selected element's parents, the current node, its siblings, and children" width="340" data-path="static/images/visual-editor/ve-tree-selector.png" />
</Frame>

* **Use the tree selector.** Click the **tree icon** in the selected-element bar to open a small view of the surrounding DOM, then click the exact ancestor or child you want. This is the most reliable way to land on the right node. See [Picking an element](/app/visual/manual-mode#picking-an-element).
* **Cycle through stacked elements.** For elements sitting on top of each other (z-index), the picker grabs the topmost. Hold a modifier key (Alt on macOS, Shift on Windows) while clicking to select the ones underneath.
* **Keep interactive elements open.** Dropdowns and hover menus close as soon as you move the mouse to the side panel. Use the **Interactive Mode** toggle to keep them open while you select.
* **Override the selector directly.** In the **Advanced** section you can type or refine the CSS selector by hand — useful when no amount of clicking lands on the element, or when you want a more durable selector. See [Detailed Selector](/app/visual/manual-mode#detailed-selector).

## Changes flicker on page load (FOOC)

A "flash of original content" happens when the SDK applies the variation a moment after the page renders the control. Mitigations:

* **Load the SDK as early as possible.** Place the `<script>` tag in `<head>`, not at the bottom of `<body>`.
* **Use the [Script Tag SDK](/lib/script-tag)** — it's optimized to apply variations before paint when possible.
* **Avoid heavy `before-render` custom JS.** The SDK has to wait for it.
* **For server-rendered sites, consider an [Edge SDK](/lib/edge/cloudflare)** to apply visual changes at the CDN before HTML reaches the browser. This eliminates FOOC entirely.

Heavy client-side-rendered apps (complex React/Vue/Svelte that rehydrate the page) are especially prone to flicker. For those, [Feature Flags](/app/features) are usually a better fit than visual experiments.

## The page freezes or becomes unresponsive while editing

Some elements aren't plain HTML — they're rendered and **owned by the site's JavaScript framework** (React, Next.js, Vue, Svelte, etc.). When you edit one, the Visual Editor applies your change, the framework re-renders the element and overwrites it, the editor re-applies, the framework reverts again — and the two can spin the browser's main thread until the page becomes unresponsive.

The editor detects this loop and steps in before the page freezes. What happens next depends on the change:

* **Style changes are fixed automatically.** Color, size, spacing, borders, visibility, and other styling are **re-delivered as global CSS** — a CSS rule re-matches the element on every render instead of being overwritten, so it beats the framework without a fight. These just work (you'll find them in the variation's global CSS); nothing to do.
* **Content and structural changes can't be.** Text, HTML, and attribute changes have no CSS equivalent, so the editor **stops previewing** the change (to stay responsive) but **keeps it saved** — the loop can be specific to the editor (dev/staging builds re-render far more than production, and you're interacting with the page while editing), so the change may still work when the SDK applies it once on your live site. When you see the notice:
  * **Verify with a [preview link](/app/visual/preview-and-qa)** on your live site. The editor can't tell an editor-only loop from a real one, so confirm whether the change actually holds for users before relying on it.
  * **Target a nearby static element** — one the framework doesn't re-render. Use the [tree selector](#element-selector-picks-the-wrong-thing) to look for a stable parent or child. (An adjacent element isn't guaranteed to be static — much of a modern app's DOM is framework-controlled, so this doesn't always help.)
  * **Use a [feature flag](/app/features)** instead. When the site's own code renders the content, a code-level change is the right tool — the visual editor is fighting the framework for ownership of that element.
  * **As an escape hatch, Custom JS** with a `waitFor`/`MutationObserver` helper can re-apply the change, though the framework may still overwrite it. See [Custom JavaScript](/app/visual/manual-mode#custom-javascript).

This also affects **live experiments** — the SDK applies changes the same way — so a change that fights the framework can flicker for real users too. For heavily client-rendered pages, prefer styling (delivered as CSS) or [Feature Flags](/app/features) over text/DOM changes.

## Image upload didn't replace the picture

Modern responsive images use `srcset` and `<picture>` markup. If a plain `src` replacement doesn't show up:

* The Visual Editor should be removing `srcset` automatically — check the Changes panel for an attribute-removal change next to the image change.
* If it didn't, the target element might be nested in markup the picker didn't recognize as a `<picture>`. Try selecting the parent `<picture>` element directly.
* See [Images → `<picture>` and `srcset` handling](/app/visual/images#picture-and-srcset-handling).

## AI mode is greyed out / not available

AI mode requires:

* **An AI provider key configured** by an org admin in **Settings → AI**. See [AI Integrations](/integrations/ai).
* **A plan that includes the `ai-suggestions` commercial feature.** Free plans don't include AI.
* **An active connection** in the side panel — sign in if you've been signed out.

If all three are in place and AI mode is still unavailable, hard-reload the side panel (right-click → Reload).

## Extension can't connect to my self-hosted GrowthBook

* **Confirm the API host URL.** Use your API host, not your front-end host. They're usually different (e.g., `api.example.com` vs. `app.example.com`).
* **Confirm HTTPS.** Chrome blocks extension requests to HTTP hosts and to HTTPS hosts with invalid certs. Use a real TLS cert.
* **Test the API from `curl` first** with `Authorization: Bearer <PAT>`. If `curl` fails, the host or token is the problem. If `curl` works but the extension doesn't, your browser's network environment (proxy, VPN, split-DNS) is blocking extension requests.
* **CORS** — your API host's CORS configuration must allow the extension's origin. The extension uses `chrome-extension://<extension-id>` as its origin.

## "Wrong domain" banner won't go away

The Visual Editor scopes itself to the URL you set when creating the experiment. If you've moved to a different domain in the active tab, changes are paused intentionally.

* **Switch the active tab back** to the experiment's target URL.
* **Or update the experiment's target URL** in GrowthBook to match where you actually want to edit.

## Changes apply to the wrong variation

* **Check the variation switcher** above the property groups. It's easy to make changes to v2 thinking you're on v1.
* **The control is read-only** — if you can't make changes, you're probably on the control tab. Switch to one of the variants.

## JavaScript changes seem to apply to every variant

This is an **editor-preview artifact** — it doesn't happen for real users.

When you add or **Run** Custom JS, the script executes on the live page so you can preview it. CSS and DOM/text changes are cleanly undone when you switch variants, but **arbitrary JavaScript can't be automatically reversed** — once a script has run it may already have created elements, attached listeners, or changed page state. Switching variants in the editor doesn't "un-run" it, so the page shows the **combined effect** of every script you've run since the last page load.

**Real users only ever see one variant** — each visitor is bucketed into a single variation and loads only that variation's JS. They never experience the stacking you see while flipping between variants in the editor.

To preview one variant cleanly:

* **Reload the page**, then open the variant you want. A fresh load runs only that variant's JS. (The editor prompts you to reload after you change a JS block, since JS can't be hot-swapped in place.)
* Use **Run** to re-execute the current variant's JS after editing it — Custom JS runs once per page load, not reactively.
* To be sure no leftover side effects remain, hard-reload (Cmd/Ctrl-Shift-R).

If your JS targets elements that appear late, wrap it in a `waitFor` helper — see [Custom JavaScript](/app/visual/manual-mode#custom-javascript).

## Save fails with "permission denied"

Your Personal Access Token's user account doesn't have permission to edit this experiment. Check:

* **Project permissions.** Your user role might be limited to a subset of projects, and this experiment lives in a different one.
* **Experiment ownership.** Some orgs restrict editing to experiment owners or members of the assigned team.
* **PAT scope.** Re-create the PAT if it was generated under an older account state.

## Still stuck?

* **Share the Debug Panel screenshot** when filing a support ticket — see [Preview and QA → The debug panel](/app/visual/preview-and-qa#the-debug-panel).
* **Reach out** in [the GrowthBook community Slack](https://slack.growthbook.io) or via [support](mailto:support@growthbook.io).

## Next steps

* **[Preview and QA](/app/visual/preview-and-qa)** — preview links, debug panel, live-experiment warning.
* **[Visual Editor overview](/app/visual)** — back to the main page.
