Custom code
The Visual Editor handles layout, text, colours, and images visually. For behaviour the visual tools don't cover (complex interactions, feature flags, API calls, or animations beyond the built-in entrance animations), use the Custom code escape hatch to write JavaScript and CSS per variation.
Where to find it
Open the Inspector (right panel), click Advanced to expand that section, then click Custom code (JS & CSS) to expand its own disclosure inside. Both stay collapsed by default so they never clutter your normal editing flow.
What it does
In the Custom code panel, you edit the JavaScript and CSS source that runs for the whole variation wherever it's applied. This is separate from the per-element visual edits: your custom code coexists with them.
The variation applies your CSS, then runs your custom JavaScript, and only after that applies the visual edits (the per-element text, style, and layout changes you made in the editor). So your code can't yet see elements the visual editor is about to insert or change when it first runs. If you need to act on those, wait for them: avsb.utils.waitForElement and avsb.utils.onMutation both work from inside your custom code, and are cancelled automatically for you. See Variation Code for the full runtime order and options.
How it works
- Edit source. Write JavaScript and/or CSS in the editor. The editor honours your experiment's configured script language (JavaScript or TypeScript) and lint mode (Off / Standard / Strict). The panel shows the active mode, e.g.
TypeScript · Strict. - Compile and save. Click Compile & save. A vs B compiles your source on the server using the same pipeline and strict-lint gate as the dashboard code editor. You don't need a local build step.
- View errors inline. If the code has compilation or type errors, they appear inline (file, line, column, message). Fix and save again. Nothing is saved while there are errors.
Compilation and validation
When you click Compile & save, A vs B:
- Parses your JavaScript or TypeScript source
- Type-checks it (if TypeScript or if strict mode is enabled)
- Applies linting rules matching your experiment's lint mode
- Compiles it to runnable JavaScript
In Strict mode, type errors block saving: the same gate as the rest of the platform. In Standard mode, type warnings are shown but don't block. In Off mode, only syntax errors block.
The editor sends your source to A vs B, which compiles it server-side. You see errors inline instantly. There's no need to set up a build tool locally or run a dev server.
Collaboration
If a teammate is actively editing the variation, saving your custom code is blocked and the editor shows who holds the lock. This matches the rest of the editor's collaboration model: one person edits a variation at a time.
Editing JavaScript the copilot wrote
When the AvsB Copilot builds something with behaviour (a popup, a tab switcher, a countdown, a carousel), the JavaScript it wrote is a normal entry in the changes list, labelled by what it does. Nothing about it is locked away:
- Open it to see the code. Click the change and the code opens in its own editor, right in the workbench, with line numbers, syntax highlighting, and search. That's a step up from the plain text box in the Custom code panel above, because this is code you're reviewing, not typing from scratch.
- Change whatever you like. Fix a timing, reword a message, or rewrite the whole thing. Your edited version runs in the editor preview straight away, so you see the effect before anything ships.
- The safety checks follow the code, not the author. An edited script goes through the same review as the original when you save: if your edit adds a call to an outside service or would rewrite existing page content, you're shown that plainly before it ships; if your edit removes one, the earlier flag clears on its own. See Save asks before AI-written code reaches out or rewrites your page.
- Undo works as usual. Undoing the copilot's reply removes the code with it; removing just the change's card drops only that script.
The copilot writes its behaviour to find its own markup, so you can freely edit, move, or restyle the block it built and the behaviour keeps working. If you remove the block entirely, remove its behaviour change too: the code editor makes it easy to see which script belonged to what.
Your own custom code in the panel above is separate and keeps its full-trust escape-hatch behaviour, exactly as before: the review above applies to code the copilot wrote (and any edits you make to it), not to scripts you author here yourself.
Scope and runtime behaviour
Custom code runs in the visitor's browser, within the same execution context as the visual edits. You can:
- Access the DOM and make further mutations
- Call the snippet SDK (e.g.
window.avsb) - Add event listeners to elements
- Load external resources (images, stylesheets, fonts)
The code runs once per page load (or SPA navigation) for each visitor bucketed into the variation. It's safe to include imperative effects: the visitor only gets one variation at a time.
A minimal example: swap a headline and log a custom event, defensively checking the element exists first (the page it's running on might not match what you expect).
/** @type {(options: AvsbVariationOptions) => void} */function initVariation(options) { const banner = document.querySelector('.promo-banner'); if (banner instanceof HTMLElement) { banner.textContent = 'Free shipping this week'; } options.track.event('promo_banner_shown');}Like Variation Code, your JavaScript must be wrapped in a function named initVariation that takes one options argument. A vs B calls it for you; a missing or renamed wrapper means your code silently does nothing. options gives you track, utils, onRemove, waitUntil, and more, exactly as documented there.
Custom code is an escape hatch for behaviour the visual tools don't support. If you find yourself reaching for it often, consider adding the missing capability as a feature request: the visual editor is built incrementally based on real usage patterns.
What happens if your code throws
A JavaScript error in your custom code never stops the rest of the page. A vs B catches it, still applies the variation's visual edits, and reports the error. The report is tagged with the exact experiment and variation it came from, and lands in your experiment's error log.
This holds whether the error is thrown directly in your function body or later, inside a .then(), a setTimeout, or another callback. Both kinds are caught and correctly attributed, because each variation's code carries its own internal tag. That is not true everywhere: an asynchronous error thrown from Project JavaScript is not tagged the same way, so it never reaches your error log at all. Custom code, scoped to one variation, does not have that gap.
Language and lint mode
The editor uses the script language and lint mode configured on your experiment:
- Language: JavaScript or TypeScript. If TypeScript, your code is checked against the DOM and snippet SDK types when you save, and any mismatch shows up as one of the inline errors described above.
- Lint: Off, Standard, or Strict. In Strict mode, type errors block saving; in Standard, they're warnings; in Off, only syntax errors block.
Both settings are shown in the Custom code panel header (for example TypeScript · Strict) so you know what rules apply before you save. The panel itself is a plain text box: there's no live autocomplete or inline type hints as you type, only the check that runs when you click Compile & save.
Related
- Getting Started: the visual editing workflow and how to select elements.
- Variation Code: how custom code runs at runtime and interacts with other experiment steps.