The LanderLab Quiz API provides a powerful way to extend and customize your quizzes at runtime using JavaScript. This API gives you controlled access to quiz data, blocks, and events, making it easy to create dynamic and interactive quiz experiences.
Understanding the Quiz API
When a quiz loads, many processes happen behind the scenes—blocks are initialized and rendered, events are registered and dispatched, and integrations run. The LanderLab Quiz API hooks into these processes and provides functions to read, write, or execute data in a way the system expects.
The Quiz API is part of the LanderLab Event system and is accessible through the detail property of event objects.
Accessing the Quiz API
You can access the Quiz API in any LanderLab Quiz event using the following pattern:
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
// Use llQuizApi here
});
Available Quiz Events
The LanderLab Quiz API can be accessed in the following events:
- ll-quiz-init – Fires when the quiz loads initially and is bound to the DOM.
- ll-quiz-submit – Fires when the quiz gets submitted.
- ll-quiz-step-view – Fires when a step is visited.
- ll-quiz-step-leave – Fires when navigating away from a step.
- ll-quiz-exit – Fires when the tab/window is closed (based on pagehide event, not entirely reliable).
- ll-quiz-button-click – Fires when a button block is clicked (DefaultButton, Continue, Previous, Submit).
- ll-quiz-input-click – Fires when a Multiple Choice option or Image Choice option is clicked.
Use these functions to receive information about the step the user is currently viewing.
llQuizApi.getCurrentStepID(); // Returns "step-1"
llQuizApi.getCurrentStepName(); // Returns "start"
Navigation
You can navigate around your quiz programmatically by requesting navigation. Forward navigation (goNext, goToStep) validates the current step before proceeding and prevents navigation if the step is invalid.
llQuizApi.navigation.goNext(); // Navigates to the next step (async, validates first)
llQuizApi.navigation.goBack(); // Navigates to the previous step
llQuizApi.navigation.goToStep("step-1"); // Navigates to the given step ID (async, validates first)
llQuizApi.navigation.goToUrl("https://example.com"); // Navigates to the given URL
llQuizApi.navigation.goToUrl("https://example.com", true); // Navigates to URL in a new tab
Forward navigation (goNext, goToStep) validates the current step before proceeding. If validation fails, navigation is prevented and a warning is logged to the console. These methods are async and return a Promise.
Step Validation
You can validate a step by calling these functions. Step validation is asynchronous, so keep that in mind. You get a boolean result which indicates the validity of the step.
llQuizApi.validateStepByID("step-1").then(console.log); // { success: boolean }
llQuizApi.validateStepByName("start").then(console.log); // { success: boolean }
If multiple steps have the same name, validateStepByName validates the first one found.
Retrieving Blocks
The Quiz API provides multiple functions to retrieve blocks from your quiz. You can get all blocks, a specific block by ID or name, or all blocks from a particular step.
Get all blocks in your quiz:
Get a specific block by its ID:
llQuizApi.getBlockByID("text-field-123");
Get a block by its name property:
llQuizApi.getBlockByName("firstName");
Get all blocks from a specific step by step ID:
llQuizApi.getBlocksByStepID("step-1");
Get all blocks from a specific step by step name:
llQuizApi.getBlocksByStepName("start");
When using getBlockByName(), if multiple blocks have the same name, the function returns the first match.
Working with Block Properties
Every block provides three useful properties for easier block handling:
- blockID – The unique identifier of the block that you can use for methods like getBlockByID.
- blockType – The type of the block (text-field, checkbox-field, etc.).
- variableName – The optional variable name you assigned to your block (from the block.name property).
const block = llQuizApi.getBlockByID("text-field-123");
block.blockID; // The unique identifier
block.blockType; // The type of the block
block.variableName; // The variable name you assigned
Getting and Setting Block Values
Almost all blocks containing user data support getValue() and setValue() functions that allow you to manipulate the current state of a block easily.
const block = llQuizApi.getBlockByID("text-field-123");
const value = block.getValue(); // Returns "hello"
block.setValue(`${value} my-name`);
block.getValue(); // Returns "hello my-name"
Some blocks use different formats other than simple strings to work with values. Check the documentation for specific block types to understand their value formats.
Resetting Blocks
You can reset any block to its initial state using the reset() function. This is similar to emptying a form field.
const block = llQuizApi.getBlockByID("text-field-123");
block.reset();
Understanding Stateless Blocks
Stateless blocks do not support getValue(), setValue(), or reset() functions. Calling these methods on stateless blocks will throw an error.
Stateless block types include:
Navigation buttons (PREVIOUS, CONTINUE, SUBMIT), text blocks (HEADLINE, PARAGRAPH), LOADER, ACCORDION, LINKS, DEFAULT_BUTTON, DEFAULT_IMAGE, CUSTOM_CODE, LIST, LIST_LOADER, PROGRESS, COUNTDOWN, DIVIDER, TRACKING, and CONTAINER blocks.
const headline = llQuizApi.getBlockByID('headline-123');
// This will throw an error:
headline.getValue(); // Error: getValue() is not supported on stateless blocks
Block Validation
You can validate a block by calling the validate() method. Block validation is asynchronous, so keep that in mind. Depending on the block type, this may trigger network validation (e.g., phone number, email, OTP).
It’s important to understand that depending on the block, a validation could trigger a module usage like network validation in the phone block for example. This is also the reason the method is asynchronous.
window.addEventListener('ll-quiz-init', async (event) => {
const llQuizApi = event.detail.llQuizApi;
// Get a block
const block = llQuizApi.getBlockByID('text-field-123');
// Validate using async/await
const result = await block.validate();
console.log(result); // { success: true } or { success: false }
// Using .then()
block.validate().then((result) => {
if (result.success) {
console.log('Block is valid!');
} else {
console.log('Block has validation errors.');
}
});
});
Visibility Management
Programmatically control the visibility of a block. Hidden blocks are excluded from validation and are not included in the submission payload.
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
const block = llQuizApi.getBlockByID('text-field-123');
// Show the block
block.visibility.show();
// Hide the block
block.visibility.hide();
// Get visibility state
const state = block.visibility.get(); // true | false | undefined
// Reset visibility state back to default
block.visibility.clear();
});
Important Notes:
- Hidden blocks (visibility.get() === false) are:
- Not validated (skipped in validation loops)
- Not included in submission payload
- Hidden in DOM (display: none)
- Shown blocks (visibility.get() === true or undefined) are:
- Validated normally
- Included in payload
- Visible in DOM
- Visibility state:
- undefined = default state (block is visible, no programmatic override)
- true = explicitly shown (overrides any default)
- false = explicitly hidden (block is not visible, not validated, not in payload)
Additional Response Data
You can use the additionalResponseData object to add, remove, or manipulate additional response data that will be sent when the user submits your quiz. A common use case is to add further hidden fields to the response, like UTM parameters and such.
The data will be included in the lead submission payload with blockType ‘hidden-field’.
All values must be strings. Convert numbers, booleans, or other types to strings before setting them.
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
// Set string values
llQuizApi.additionalResponseData.set('my-value', 'hello');
llQuizApi.additionalResponseData.set('my-number', '42');
llQuizApi.additionalResponseData.set('my-boolean', 'true');
// Update existing value
llQuizApi.additionalResponseData.set('my-value', 'hello-2');
// Get a value
const value = llQuizApi.additionalResponseData.get('my-value'); // "hello-2"
// Remove a value
llQuizApi.additionalResponseData.remove('my-value');
// Get all additional response data
const allData = llQuizApi.additionalResponseData.getAll();
// { "my-number": "42", "my-boolean": "true" }
});
Custom Methods
For some blocks, we provide custom methods that might come in handy.
setLabel
You can programmatically manipulate the label of blocks that support labels. This works on:
- Input fields: TextField, EmailField, PhoneNumberField, TextareaField, NumberField, DatePicker, BirthDateField, ZipCodeField, GoogleAddress, OtpField, SelectField, RangeSlider
- Choice blocks: MultipleChoice, ImageChoice
- Checkbox: CheckboxField
CheckboxField supports HTML in labels, while all other blocks escape HTML for security (plain text only).
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
// Set label for a text field (HTML will be escaped)
const textBlock = llQuizApi.getBlockByID('text-field-123');
textBlock?.setLabel('Enter your name');
// Set label for checkbox (HTML is allowed)
const checkboxBlock = llQuizApi.getBlockByID('checkbox-123');
checkboxBlock?.setLabel('
I agree to the terms
');
\ // Set label for multiple choice\ const choiceBlock = llQuizApi.getBlockByID('multiple-choice-123');\ choiceBlock?.setLabel('Select your preferred option');\ });
Practical Examples
Setting Default Values on Quiz Load
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
// Get a block by ID and set a value
const nameBlock = llQuizApi.getBlockByID('text-field-123');
nameBlock?.setValue('John Doe');
// Get the value back
console.log(nameBlock?.getValue()); // 'John Doe'
});
Working with Multiple Blocks
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
// Get blocks by name
const firstNameBlock = llQuizApi.getBlockByName('firstName');
firstNameBlock?.setValue('John');
const emailBlock = llQuizApi.getBlockByName('email');
emailBlock?.setValue('[email protected]');
// Get all blocks and set default values for text fields
const allBlocks = llQuizApi.getBlocks();
allBlocks.forEach(block => {
if (block.blockType === 'text-field') {
block.setValue('Default value');
}
});
});
Programmatic Navigation Based on Conditions
window.addEventListener('ll-quiz-init', async (event) => {
const llQuizApi = event.detail.llQuizApi;
// Example: Auto-advance after 5 seconds (validates before navigating)
setTimeout(async () => {
await llQuizApi.navigation.goNext();
}, 5000);
// Example: Navigate to specific step based on block value
const planBlock = llQuizApi.getBlockByName('plan');
if (planBlock) {
const planValue = planBlock.getValue();
if (planValue === 'premium') {
await llQuizApi.navigation.goToStep('premium-step');
} else {
await llQuizApi.navigation.goToStep('standard-step');
}
}
});
Validating Steps
window.addEventListener('ll-quiz-init', async (event) => {
const llQuizApi = event.detail.llQuizApi;
// Validate step by ID using async/await
const resultById = await llQuizApi.validateStepByID('step-1');
console.log('Step validation result:', resultById); // { success: true } or { success: false }
// Validate step by name using async/await
const resultByName = await llQuizApi.validateStepByName('start');
console.log('Step validation result:', resultByName); // { success: true } or { success: false }
// Validate step using .then()
llQuizApi.validateStepByID('step-2').then((result) => {
if (result.success) {
console.log('Step is valid!');
} else {
console.log('Step has validation errors.');
}
});
// Example: Only navigate if step is valid
const currentStepId = llQuizApi.getCurrentStepID();
const validationResult = await llQuizApi.validateStepByID(currentStepId);
if (validationResult.success) {
await llQuizApi.navigation.goNext();
} else {
console.log('Cannot navigate: step is invalid');
}
});
Adding UTM Parameters
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
// Extract UTM parameters from URL
const urlParams = new URLSearchParams(window.location.search);
const utmSource = urlParams.get('utm_source');
const utmMedium = urlParams.get('utm_medium');
const utmCampaign = urlParams.get('utm_campaign');
// Add to additional response data
if (utmSource) {
llQuizApi.additionalResponseData.set('utm_source', utmSource);
}
if (utmMedium) {
llQuizApi.additionalResponseData.set('utm_medium', utmMedium);
}
if (utmCampaign) {
llQuizApi.additionalResponseData.set('utm_campaign', utmCampaign);
}
});
Blocks with Network Validation
window.addEventListener('ll-quiz-init', async (event) => {
const llQuizApi = event.detail.llQuizApi;
// Email field with API validation
const emailBlock = llQuizApi.getBlockByID('email-field-123');
if (emailBlock) {
emailBlock.setValue('[email protected]');
// This will trigger network validation if apiValidation is enabled
const result = await emailBlock.validate();
console.log('Email validation:', result);
}
// Phone number field with API validation
const phoneBlock = llQuizApi.getBlockByID('phone-number-123');
if (phoneBlock) {
phoneBlock.setValue('+1234567890');
// This will trigger network validation if apiValidation is enabled
const result = await phoneBlock.validate();
console.log('Phone validation:', result);
}
// OTP field
const otpBlock = llQuizApi.getBlockByID('otp-123');
if (otpBlock) {
otpBlock.setValue('123456');
// This will trigger network validation
const result = await otpBlock.validate();
console.log('OTP validation:', result);
}
});
Conditional Visibility
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
const planBlock = llQuizApi.getBlockByName('plan');
const premiumBlock = llQuizApi.getBlockByName('premium-features');
if (planBlock) {
const planValue = planBlock.getValue();
if (planValue === 'premium') {
premiumBlock?.visibility.show();
} else {
premiumBlock?.visibility.hide();
}
}
});
Dynamic Label Updates
window.addEventListener('ll-quiz-init', (event) => {
const llQuizApi = event.detail.llQuizApi;
const planBlock = llQuizApi.getBlockByName('plan');
const featuresBlock = llQuizApi.getBlockByName('features');
if (planBlock) {
const planValue = planBlock.getValue();
if (planValue === 'premium') {
featuresBlock?.setLabel('Premium Features (Select all that apply)');
} else {
featuresBlock?.setLabel('Standard Features');
}
}
});
Last modified on April 2, 2026