JavaScript execution
Run JavaScript inside a bot, read and write Landbot Fields, and communicate with the page that embeds the widget.
JavaScript runs in three places:
- The Custom JS panel (Design → Advanced → Add JS in the builder) — bot-wide JavaScript that runs once when the widget loads. The panel accepts a full HTML fragment, not just script bodies: drop
<div>,<style>,<iframe>markup and a<script>tag together and Landbot will inject all of it into the bot's render context. - Code blocks in the flow — JavaScript that runs when the conversation reaches the block. Inside this code,
thisis the widget scope. - The parent page — JavaScript hosted alongside your widget, with a reference to
myLandbot(the value returned by the constructor).
The three sides talk through a small set of primitives: setCustomData (write fields), @{field} interpolation (read fields), and window (call functions on the other side or register them for later use).
Warning JavaScript execution is only available for web widgets (Fullpage, Popup, Container, Livechat, Native, ContainerPopup). It is not available for WhatsApp or Messenger channels.
Note Scope: bot iframe vs parent page. Every widget except
Landbot.Nativeruns in its own iframe —this.windowandthis.document(the bot's window/document) are different objects from the parent page'swindowanddocument. Functions registered onthis.windoware not visible to the parent page, and vice versa.
Landbot.Nativeis the exception: it renders directly into the parent DOM, sothis.window === windowandthis.document === document. Code written for Native and code written for the iframe widgets sometimes need different scope handling.
Running JS on widget load
Warning Test with Share URL, not Preview. The builder's Preview pane does not run the Custom JS panel or fire
onLoad. Open the bot's Share URL to test any Custom JS oronLoad-dependent behaviour.
From a Code block in the flow, use this.onLoad to register a callback that fires once the widget has rendered:
var landbotScope = this;
this.onLoad(function () {
console.log('Bot ready');
// landbotScope.* is the widget API from here
});
From the parent page, call onLoad on the widget instance — same semantics:
myLandbot.onLoad(function () {
console.log('Load complete!');
});
Sharing helpers across Code blocks
Code blocks can't reference each other's local variables — each runs in its own scope. To share a helper function between blocks, register it on the widget's window from onLoad (typically in the Custom JS panel), then call it from any Code block via this.window.helperName(...).
<!-- Design → Advanced → Add JS -->
<script>
var _landbot = this;
this.onLoad(function () {
_landbot.window.formatPrice = function (cents) {
return '$' + (cents / 100).toFixed(2);
};
_landbot.window.greet = function (name) {
return 'Hello, ' + name + '!';
};
});
</script>
// Later, in any Code block in the flow
this.setCustomData({
price_label: this.window.formatPrice("@{price_cents}"),
welcome: this.window.greet("@{name}"),
});
This is the canonical place for shared utilities (date formatting, validation, opening custom UI elements) — define them once in the Custom JS panel; call them from any Code block as the flow progresses.
Note
_landbot.windowhere is the bot iframe's window, not the host page's. Functions registered on it are visible to other Code blocks inside the same bot session, but not to the parent page. To call functions on the host page, use the parent-pagewindow— see Calling parent-page functions from the bot below.
Reading Landbot Fields
Inside a Code block, use @{fieldname} to interpolate the current value of a Landbot Field. Landbot substitutes the literal value before the JavaScript runs.
var welcome = "@{welcome}";
console.log(welcome);
For values that may contain line breaks, quotes, or boolean literals, use template literals to avoid escaping problems:
var message = `@{message}`;
Note
@{field}is replaced when the Code block executes, which only happens after the user has provided input to the bot. If you need a value available at the very first block, set it at construction withcustomData. See Setting variables.
Writing Landbot Fields
From a Code block, write fields with this.setCustomData:
this.setCustomData({
total: (Math.round("@{value}" * 100) / 100).toFixed(2),
});
From the parent page, write fields with myLandbot.setCustomData — same shape:
myLandbot.setCustomData({
email: 'visitor@example.com',
plan: 'pro',
});
For the full lifecycle and naming rules, see Setting variables.
Calling parent-page functions from the bot
window inside a Code block resolves to the page that hosts the widget. Any function you defined globally on the parent page is callable from the bot:
<!-- on the parent page -->
<script>
function changeBackgroundColor(color) {
document.body.style.background = color;
}
</script>
// in a Code block, after the user picks a colour
window.changeBackgroundColor("@{color}");
This is the pattern for any UI side-effect that lives outside the widget: open a modal, navigate, log analytics, fire a tracking event.
Calling widget methods from the parent page
The widget instance you got back from the constructor exposes the full Landbot.Widget API plus widget-type-specific methods. Use it to drive the widget from your page's UI:
var myLandbot = new Landbot.Popup({ configUrl: '...' });
document.getElementById('chat-with-us').addEventListener('click', function () {
myLandbot.open();
});
document.getElementById('start-flow').addEventListener('click', function () {
myLandbot.sendMessage({
type: 'button',
message: 'Get a quote',
payload: '#quote',
});
});
Common methods: open and close on Popup/Livechat, sendMessage on every widget, destroy to tear down.
Recipe: bot fills a webpage form
A common pattern: the bot interviews the user and pushes the collected answers into a form on the host page. Three pieces wire together.
1. The form on the parent page.
<form id="contact-form">
<input id="fullName" name="name" type="text" />
<input id="email" name="email" type="email" />
<input id="phone" name="phone" type="tel" />
<input id="company" name="company" type="text" />
<textarea id="message" name="message"></textarea>
<button type="submit">Submit</button>
</form>
<div id="myLandbot" style="width: 100%; height: 500px"></div>
2. A global function on the parent page that knows how to populate the form. The bot will call this.
window.fillContactForm = function (data) {
if (data.name) document.getElementById('fullName').value = data.name;
if (data.email) document.getElementById('email').value = data.email;
if (data.phone) document.getElementById('phone').value = data.phone;
if (data.company) document.getElementById('company').value = data.company;
if (data.message) document.getElementById('message').value = data.message;
};
var myLandbot = new Landbot.Container({
container: '#myLandbot',
configUrl: '...',
});
3. A Code block in the bot's flow that calls the function with the field values it collected.
window.fillContactForm({
name: "@{name}",
email: "@{email}",
phone: "@{phone}",
company: "@{company}",
message: "@{message}",
});
As soon as the Code block runs, the form fields on the host page are populated. The user reviews and submits the form themselves — the bot doesn't submit it.
Limitations
- Web only. WhatsApp and Messenger flows can't run JavaScript Code blocks.
- No
<script>tags inside a Code block. Use plain JavaScript statements — Landbot wraps them in their own function scope. - HTTPS is required for the parent page; mixed-content URLs are blocked.
@{field}resolution happens at execution time and depends on the user having reached the Code block. Values set from JavaScript become readable on the next user turn.
Worked examples
- Live demo: bidirectional examples — four runnable patterns: pre-filled query from a search box, CTA buttons opening the bot with different questions, per-product context via
customData, and the form-fill recipe above. Source-viewable in the browser.
The help center has cookbook-style examples that go beyond reference: