Skip to main content
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.

Getting Current Step Information

Use these functions to receive information about the step the user is currently viewing.
llQuizApi.getCurrentStepID(); // Returns "step-1"
llQuizApi.getCurrentStepName(); // Returns "start"
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:
llQuizApi.getBlocks();
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');
    }
  }
});