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

# Use Quiz API for Advanced Customization

> Use the Quiz API to control quiz behavior, manage data, and create dynamic, interactive funnel experiences with custom logic.

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:

```javascript theme={null}
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.
* **ll-quiz-google-address-select** – Fires when an address is selected from the Google Address autocomplete dropdown.
* **ll-quiz-value-change** – Fires when a block value is committed: typed fields when the visitor leaves the field or the step, every other input immediately. Also fires for values set through the Quiz API.

See the Quiz Events API page for the data each event carries.

## Getting Current Step Information

Use these functions to receive information about the step the user is currently viewing.

```javascript theme={null}
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.

```javascript theme={null}
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.

```javascript theme={null}
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:**

```javascript theme={null}
llQuizApi.getBlocks();
```

**Get a specific block by its ID:**

```javascript theme={null}
llQuizApi.getBlockByID("text-field-123");
```

**Get a block by its name property:**

```javascript theme={null}
llQuizApi.getBlockByName("firstName");
```

**Get all blocks from a specific step by step ID:**

```javascript theme={null}
llQuizApi.getBlocksByStepID("step-1");
```

**Get all blocks from a specific step by step name:**

```javascript theme={null}
llQuizApi.getBlocksByStepName("start");
```

**Important Notes:**

* getBlocks(), getBlocksByStepID() and getBlocksByStepName() only return blocks that hold a value (see Stateless Blocks below).
* getBlockByID() and getBlockByName() additionally resolve buttons (Continue, Previous, Submit, DefaultButton), so you can call setLabel() on them.
* When using getBlockByName(), if multiple blocks have the same name, the function returns the first match.
* getBlockByID() and getBlockByName() never return undefined. When no block matches, or the block is stateless, you get a "null block" whose methods log a console warning and do nothing: getValue() returns an empty string and validate() resolves to `{ success: false }`. Chained calls never throw, so optional chaining is not required.

## 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).

```javascript theme={null}
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.

```javascript theme={null}
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.

Setting a value through the API emits an **ll-quiz-value-change** event with `source: 'api'`, so listeners can tell script changes apart from visitor input.

## Resetting Blocks

You can reset any block to its initial state using the reset() function. This is similar to emptying a form field. Resetting also emits an **ll-quiz-value-change** event with `source: 'api'`.

```javascript theme={null}
const block = llQuizApi.getBlockByID("text-field-123");
block.reset();
```

## Understanding Stateless Blocks

Stateless blocks do not support getValue(), setValue(), reset(), or validate(). They are not returned by getBlocks(), getBlocksByStepID() or getBlocksByStepName().

**Stateless block types include:**

Navigation buttons (PREVIOUS, CONTINUE, SUBMIT), text blocks (HEADLINE, PARAGRAPH), LOADER, ACCORDION, LINKS, DEFAULT\_BUTTON, DEFAULT\_IMAGE, VIDEO, CUSTOM\_CODE, LIST, LIST\_LOADER, PROGRESS, COUNTDOWN, DIVIDER, and TRACKING blocks.

Buttons are the only stateless blocks you can retrieve, through getBlockByID() or getBlockByName(), because they support setLabel(). Calling a value method on a button throws an error:

```javascript theme={null}
const button = llQuizApi.getBlockByID('continue-123');
button.setLabel('Next'); // Works
button.getValue(); // Error: getValue() is not supported on stateless blocks (block type: continue)
```

Looking up any other stateless block returns a null block that warns instead of throwing:

```javascript theme={null}
const headline = llQuizApi.getBlockByID('headline-123');
headline.getValue(); // Logs "Cannot get value: block does not exist" and returns ""
```

## 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). Hidden blocks always validate successfully.

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.

```javascript theme={null}
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.

```javascript theme={null}
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.

```javascript theme={null}
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
* Buttons: Continue, Previous, Submit, DefaultButton

CheckboxField supports HTML in labels, while all other blocks escape HTML for security (plain text only). Calling setLabel() on a block without a label throws an error.

```javascript theme={null}
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 <a href="/terms">Terms and Conditions</a>');

  // Set label for multiple choice
  const choiceBlock = llQuizApi.getBlockByID('multiple-choice-123');
  choiceBlock.setLabel('Select your preferred option');

  // Set the text of a button
  const continueButton = llQuizApi.getBlockByID('continue-123');
  continueButton.setLabel('Next Step');
});
```

### setPlaceholder

You can programmatically manipulate the placeholder of blocks that render an input. This works on:

* Input fields: TextField, EmailField, PhoneNumberField, TextareaField, NumberField, DatePicker, BirthDateField, ZipCodeField, GoogleAddress, OtpField

Calling it on any other block does not throw: the value is stored on the block and nothing changes visually.

```javascript theme={null}
window.addEventListener('ll-quiz-init', (event) => {
  const llQuizApi = event.detail.llQuizApi;

  // Set placeholder for a text field
  const textBlock = llQuizApi.getBlockByID('text-field-123');
  textBlock.setPlaceholder('Enter your name');

  // Set placeholder for an email field
  const emailBlock = llQuizApi.getBlockByID('email-field-123');
  emailBlock.setPlaceholder('example@email.com');

  // Set placeholder for a textarea
  const textareaBlock = llQuizApi.getBlockByID('textarea-field-123');
  textareaBlock.setPlaceholder('Enter your message here...');
});
```

## Practical Examples

### Setting Default Values on Quiz Load

```javascript theme={null}
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

```javascript theme={null}
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('john@example.com');

  // 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

```javascript theme={null}
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 planValue = llQuizApi.getBlockByName('plan').getValue();
  if (planValue === 'premium') {
    await llQuizApi.navigation.goToStep('premium-step');
  } else if (planValue) {
    await llQuizApi.navigation.goToStep('standard-step');
  }
});
```

### Validating Steps

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
window.addEventListener('ll-quiz-init', async (event) => {
  const llQuizApi = event.detail.llQuizApi;

  // Email field with API validation
  const emailBlock = llQuizApi.getBlockByID('email-field-123');
  emailBlock.setValue('user@example.com');
  // This will trigger network validation if apiValidation is enabled
  console.log('Email validation:', await emailBlock.validate());

  // Phone number field with API validation
  const phoneBlock = llQuizApi.getBlockByID('phone-number-123');
  phoneBlock.setValue('+1234567890');
  // This will trigger network validation if apiValidation is enabled
  console.log('Phone validation:', await phoneBlock.validate());

  // OTP field
  const otpBlock = llQuizApi.getBlockByID('otp-123');
  otpBlock.setValue('123456');
  // This will trigger network validation
  console.log('OTP validation:', await otpBlock.validate());
});
```

### Conditional Visibility

Reacting to **ll-quiz-value-change** keeps the visibility in sync as the visitor answers, instead of evaluating once on load.

```javascript theme={null}
window.addEventListener('ll-quiz-value-change', (event) => {
  const { blockName, value, llQuizApi } = event.detail;
  if (blockName !== 'plan') return;

  const premiumBlock = llQuizApi.getBlockByName('premium-features');
  if (value === 'premium') {
    premiumBlock.visibility.show();
  } else {
    premiumBlock.visibility.hide();
  }
});
```

### Dynamic Label Updates

```javascript theme={null}
window.addEventListener('ll-quiz-value-change', (event) => {
  const { blockName, value, llQuizApi } = event.detail;
  if (blockName !== 'plan') return;

  const featuresBlock = llQuizApi.getBlockByName('features');
  if (value === 'premium') {
    featuresBlock.setLabel('Premium Features (Select all that apply)');
  } else {
    featuresBlock.setLabel('Standard Features');
  }
});
```

### Reacting to Visitor Answers

**ll-quiz-value-change** reports every committed answer with the value before and after the change and who made it. Ignore changes with a source other than `user` when you only want visitor input.

```javascript theme={null}
window.addEventListener('ll-quiz-value-change', (event) => {
  const { blockName, blockType, stepName, value, previousValue, source } = event.detail;

  // Ignore changes made by scripts or by the quiz itself
  if (source !== 'user') return;

  console.log(`${blockName} (${blockType}) on step ${stepName}: "${previousValue}" -> "${value}"`);
});
```
