> ## Documentation Index
> Fetch the complete documentation index at: https://docs-dev-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Learn how to use custom components to create fields that require specific logic or UI using JavaScript, HTML and CSS.

# Custom Field Components

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=40a3a95f263d86fd7e17a6329e9933d5" alt="Dashboard > Actions > Forms > Custom field" data-og-width="1408" width="1408" data-og-height="850" height="850" data-path="docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?w=280&fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=7cb67c1957d7175b4484c74b8d6f6ffc 280w, https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?w=560&fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=31071c0b41748e976c9dc71dba3b42a6 560w, https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?w=840&fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=8bc841ace281ab9ddc89fe5b7c47c337 840w, https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?w=1100&fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=c0fb10c7bb666a501e7205922950625b 1100w, https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?w=1650&fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=f4e004d188e089876fb82686d1cd3ce1 1650w, https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RDh-UBFSkTEu_d9f/docs/images/cdy7uua7fh8z/3bZUItvx7VQG1AUuTah1V0/6ba3b7e9909d189f418cdc85a5ed53b9/custom-field.png?w=2500&fit=max&auto=format&n=RDh-UBFSkTEu_d9f&q=85&s=f42060778e810a9a652547f9ae156f5e 2500w" />
</Frame>

Using the custom field, you can extend the look and feel of your forms and add powerful logic with JavaScript, HTML, and CSS.

The custom field has internal methods to facilitate passing data to the form, adding frontend and backend validations, and handling common events such as focus or blur.

You can use custom field to create:

* Fields with a custom data structure.

  * **Example**: Objects, array of strings
* Fields that use third-party widgets.

  * **Example**: Google Address autocomplete
* Fields with logic to hide or show other fields.
* Fields that require external APIs to get a value.

<Warning>
  To use custom fields you need to enable [Custom Domains](/docs/customize/custom-domains). Rendering a form with a custom field outside a custom domain, will display an error.
</Warning>

## Custom field settings

The custom field settings are:

### Params

Add key-value pairs to reference in the custom field source code. Key-value pairs can include form field [variables](/docs/customize/forms/variables).

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/wYcZudKzAy7DVE3d/docs/images/cdy7uua7fh8z/6VEtliKd20ZF4MJKhvB5Z0/e1b3b5df4860d013d1e3eb79d1174b35/params.png?fit=max&auto=format&n=wYcZudKzAy7DVE3d&q=85&s=f50087fd374b4d90c9c7908ed936cb9e" alt="" width="821" height="320" data-path="docs/images/cdy7uua7fh8z/6VEtliKd20ZF4MJKhvB5Z0/e1b3b5df4860d013d1e3eb79d1174b35/params.png" />
</Frame>

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Param values are only available after the form invokes the `init()` method.
</Callout>

**Example**:

In the example below, the custom field param settings are populated with the key-value pairs `symbol={{fields.symbol}}` and `separator=,`

```javascript lines expandable theme={null}
function CustomComponent(context) {
  const input = document.createElement('input');
  let mask = null;

  function mountComponent() {
    /** getParams() method returns the params you've configured in your input */
    const config = context.custom.getParams();
    const { symbol, separator } = config;

    mask = IMask(input,
    {
      mask: `${symbol}num`,
      blocks: {
        num: {
          mask: Number,
          thousandsSeparator: separator,
        }
      }
    });
  }

  return {
    /** Invoked once when the field is created */
    init() {
      mountComponent();
      return input;
    },
    ...
  };
}
```

### Source code

Add your Javascript code to the custom field.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/YlSGjDQ1BrChv4Jn/docs/images/cdy7uua7fh8z/TMuY21ILSxBiBWv792cTB/97e9855145d7b52fc33ed60249e86642/source-code.png?fit=max&auto=format&n=YlSGjDQ1BrChv4Jn&q=85&s=b0cfc862bfb7e9d60502a8787ae78e1b" alt="" width="958" height="405" data-path="docs/images/cdy7uua7fh8z/TMuY21ILSxBiBWv792cTB/97e9855145d7b52fc33ed60249e86642/source-code.png" />
</Frame>

**Example**:

```javascript lines theme={null}
function customInput() {
  const input = document.createElement('input');
  input.type = 'text';

  return {
    init() {
      return input;
    },

    block() {
      input.disabled = true;
    },

    unblock() {
      input.disabled = false;
    },

    getValue() {
      return input.value;
    }
  };
}
```

### JSON Schema

By default, the custom field accepts any value format. However, you can use [JSON Schema](https://json-schema.org/) to validate values server-side.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/tcenw4jcNpftRqWN/docs/images/cdy7uua7fh8z/5iQfwWq8Scx11s1fcSONvK/d7af907f575095d66558ee2631c73113/json-schema.png?fit=max&auto=format&n=tcenw4jcNpftRqWN&q=85&s=2ff1c6c6f96da0fce99fcc43e09eea2f" alt="" width="672" height="351" data-path="docs/images/cdy7uua7fh8z/5iQfwWq8Scx11s1fcSONvK/d7af907f575095d66558ee2631c73113/json-schema.png" />
</Frame>

**Example**:

```json lines theme={null}
{
  "type": "array",
  "items": {
    "type": "string"
  },
  "minItems": 2
}
```

To accommodate complex validation requirements, you can use a [flow](/docs/customize/forms/flows).

### CSS

Add your CSS styles to the custom field.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/YlSGjDQ1BrChv4Jn/docs/images/cdy7uua7fh8z/lC4w1THpVDD32Ke6IsXrl/e1371beeed09d5f12b6656d397c3b692/css.png?fit=max&auto=format&n=YlSGjDQ1BrChv4Jn&q=85&s=ca12451bac20a7793b8908e0ee1ff1ed" alt="" width="812" height="343" data-path="docs/images/cdy7uua7fh8z/lC4w1THpVDD32Ke6IsXrl/e1371beeed09d5f12b6656d397c3b692/css.png" />
</Frame>

## Custom field handlers

You can use these handlers to add custom behavior to the field:

### init()

Invoked once when the field is created and passes the params values that you configure in the Params settings.

Returns an HTML element, string, or no value.

**Example**:

```javascript lines theme={null}
const input = document.createElement('input');
input.type = 'text';

init() {
  return input;
}
```

### update()

Invoked when the user visits the same form step again.

This option is useful when you need to re-render UI logic or refresh params values that may have changed.

### onFocus()

Invoked when focus enters the custom field HTML element.

### onBlur()

Invoked when focus abandons the custom field HTML element.

### getValue()

Invoked when the form needs to get the custom field value one or more times. This is typically executed when the user submits the form step. If you need to perform client-side validations, you can throw an error to display a custom error message to the user.

**Example:**

```javascript lines theme={null}
function customTextInput() {
  const input = document.createElement('input');
  input.type = 'text';

  return {
    init() {
      return input;
    },

    getValue() {
      if (input.value !== 'Auth0') {
        throw new Error('The value must be Auth0')
      }

      return input.value;
    }
  };
}
```

### block()

Invoked when the custom field must be blocked. This is typically executed when the user submits the form step, and data is being processed to our backend.

### unblock()

Invoked when the custom field must be unblocked. This is typically executed after the user submits the form step or the data has stopped being processed to our back-end due to a validation error.

### getScripts()

Returns a list of URLs the form guarantees will have finished loading before the `init()` method is invoked.

**Example**:

```js lines theme={null}
getScripts() {
  return ['https://example.com/script_a.js', 'https://example.com/script_b.js'];
}
```

## Context object

When passing a context object, you can use these methods to handle logic on your form and components.

### Custom methods

#### context.custom.getValue()

Receives the value of the current custom field.

#### context.custom.setValue()

Sets a value to the current custom field.

**Example**:

```javascript lines theme={null}
function customInput(context) {
  const input = document.createElement('input');
  input.type = 'text';

  input.addEventListener('change', () => {
    context.custom.setValue(input.value);
  });

  return {
    init() {
      return input;
    },
  };
}
```

#### context.custom.createUid()

Returns a unique identifier for the current custom field.

**Example**:

```javascript lines theme={null}
function customInput(context) {
  const input = document.createElement('input');
  input.type = 'text';
  input.id = context.custom.createUid();

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    }
  };
}
```

#### context.custom.getParams()

Receives the parameters from the current custom field settings.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Param values are only available after the form invokes the `init()` method.
</Callout>

**Example**:

```javascript lines theme={null}
function customInput(context) {
  // Accessing parameters on the root of the function does NOT work
  // const { defaultValue } = context.custom.getParams();
  // console.log(defaultValue); // undefined

  function buildInput() {
    const { defaultValue } = context.custom.getParams();
    const input = document.createElement('input');
    input.type = 'text';
    input.value = defaultValue;

    return input;
  }

  return {
    init() {
      return buildInput();
    },

    getValue() {
      return input.value;
    }
  };
}
```

### Form methods

When you need to interact with the form to gather values from other fields or navigate to other form steps, you can use the following form methods:

#### context.form.getId()

Returns a unique identifier for the current form.

#### context.form.getRoot()

Returns the root HTML element for the current form.

#### context.form.goForward()

Goes to the next form step.

#### context.form.goPrevious()

Goes to the previous form step.

#### context.form.isValid()

Returns a boolean if the form passes all the client-side validations.

#### context.form.validate()

Evaluates existing field values using client-side validation before continuing. If a field does not pass the validation, an error message will appear.

#### context.form.getAllHiddenFields()

Returns an object with all hidden field values.

#### context.form.setHiddenField(id, value)

Sets a hidden field value.

| Parameter | Description                              |
| --------- | ---------------------------------------- |
| `id`      | *String*. The ID of the hidden field.    |
| `value`   | *String*. The value of the hidden field. |

#### context.form.getValues()

Returns an object with all field and hidden field values.

#### context.form.getField(id)

Returns an instance for the specified field.

* `getNode() | true` Returns the root HTML element of the field.
* `getValue()` Returns the field value.
* `setRequired(boolean)` Sets or unsets the field as required.

| Parameter | Description                      |
| --------- | -------------------------------- |
| `id`      | \*String \*. The ID field value. |

**Example**:

```javascript lines theme={null}
const fullName = context.form.getField('full_name');
const fullNameValue = fullName.getValue();
console.log(fullNameValue); // John Doe
```

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  `setRequired()` only sets or unsets the field as required on the client-side. For example, if you unset a field as required, but it was marked as required in your field settings, the form will return an error if the field has no value.
</Callout>

## Custom field examples

The sections below provide example custom fields you can add to your forms:

### Range input custom field

A custom field that returns a value from a predetermined range.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/DJz3781nwG6wS-wJ/docs/images/cdy7uua7fh8z/4afLZWM9MAXXti4F3Cuc3D/c6c6d33c650b7b9fde3de011feb82192/range-input-custom-field.png?fit=max&auto=format&n=DJz3781nwG6wS-wJ&q=85&s=d565e351c20fe3cd08d295a4493916dc" alt="" width="380" height="242" data-path="docs/images/cdy7uua7fh8z/4afLZWM9MAXXti4F3Cuc3D/c6c6d33c650b7b9fde3de011feb82192/range-input-custom-field.png" />
</Frame>

**Source code**:

```javascript lines theme={null}
function rangeInput() {
  const input = document.createElement('input');
  input.type = 'range';
  input.min= '0';
  input.max= '100';
  input.value = '0';

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    }
  };
}
```

### Color input custom field

A custom field that returns a color hex value.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/DJz3781nwG6wS-wJ/docs/images/cdy7uua7fh8z/5HAWLZJmCRlyl3dIrgzgWv/c602ccc1662d40d03898dcaa94d213dc/color-input-custom-field.png?fit=max&auto=format&n=DJz3781nwG6wS-wJ&q=85&s=1602fbf8a1eaa1c063991f15d0d7738d" alt="" width="376" height="262" data-path="docs/images/cdy7uua7fh8z/5HAWLZJmCRlyl3dIrgzgWv/c602ccc1662d40d03898dcaa94d213dc/color-input-custom-field.png" />
</Frame>

**Source code**:

```javascript lines theme={null}
function colorInput() {
  const input = document.createElement('input');
  input.type = 'color';
  input.value = '#20c5a0';

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    },
  };
}
```

### Autocomplete input custom field using values from API

A custom field that returns an autocomplete value using a third-party API.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/YlSGjDQ1BrChv4Jn/docs/images/cdy7uua7fh8z/l0lvyJgPLrHrLT8rpR6IF/800868d966ef5b64effbb6a3b0cabf94/autocomplete-api.png?fit=max&auto=format&n=YlSGjDQ1BrChv4Jn&q=85&s=9cdc117b8807ed67e3cb687c5f0c7516" alt="" width="380" height="294" data-path="docs/images/cdy7uua7fh8z/l0lvyJgPLrHrLT8rpR6IF/800868d966ef5b64effbb6a3b0cabf94/autocomplete-api.png" />
</Frame>

**Source code**:

```javascript lines expandable theme={null}
function textInputWithAutocomplete(context) {
  const input = document.createElement('input');
  input.type = 'text';

  function populateInputValue(json) {
    const { city } = json;

    input.value = city;
  }

  function fetchIpInfo() {
    const url = 'https://ipinfo.io/json';
    fetch(url)
      .then((res) => res.json())
      .then((json) => populateInputValue(json));
  }

  return {
    init() {
      fetchIpInfo();
      return input;
    },

    getValue() {
      return input.value;
    },
  };
}
```

### Dynamic dropdown custom field using values from API

A custom field that returns a value from a dynamic dropdown list using a third-party API.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/wYcZudKzAy7DVE3d/docs/images/cdy7uua7fh8z/6gwOMuZaYi3dh0bkrxnKy9/33172d00e3933c74954a8ac3befaff7f/dropdowncustomfield.png?fit=max&auto=format&n=wYcZudKzAy7DVE3d&q=85&s=78158d47bcc28037dfe59f5a0aafea75" alt="" width="379" height="254" data-path="docs/images/cdy7uua7fh8z/6gwOMuZaYi3dh0bkrxnKy9/33172d00e3933c74954a8ac3befaff7f/dropdowncustomfield.png" />
</Frame>

**Source code**:

```javascript lines expandable theme={null}
function dynamicDropdown() {
  const select = document.createElement('select');
  select.classList.add('af-stringField-input');

  function buildOption(data) {
    const { name: { first } } = data;
    const option = document.createElement('option');
    option.value = first;
    option.innerText = first;

    return option;
  }

  function populateNames(json) {
    const { results } = json;

    results.forEach((o) => {
      const option = buildOption(o);

      select.appendChild(option);
    });
  }

  function fetchNames() {
    const url = 'https://randomuser.me/api/?results=10&inc=name';
    fetch(url)
      .then((res) => res.json())
      .then((json) => populateNames(json));
  }

  return {
    init() {
      fetchNames();
      return select;
    },

    getValue() {
      return select.value;
    },
  };
}
```

### Dynamic input custom field with a (+) button to add more fields

A custom field that allows users to add more fields.

<Frame>
  <img src="https://mintcdn.com/docs-dev-docs-event-stream-action-templates/RjB12i6aOVmBONJv/docs/images/cdy7uua7fh8z/7xM41qqb1zYjLAPG16wxGc/a33a3dd624222643e3f967ff35acbbac/Screenshot_2024-09-12_at_09.32.35.png?fit=max&auto=format&n=RjB12i6aOVmBONJv&q=85&s=41cbbae05efab020a2f002958605f2b2" alt="" width="381" height="330" data-path="docs/images/cdy7uua7fh8z/7xM41qqb1zYjLAPG16wxGc/a33a3dd624222643e3f967ff35acbbac/Screenshot_2024-09-12_at_09.32.35.png" />
</Frame>

**Source code**:

```javascript lines expandable theme={null}
function DynamicInputs(context) {
  const DEFAULT_INITIAL_INPUTS = 2;
  const DEFAULT_PLACEHOLDER = 'jane.doe@example.com';
  const DEFAULT_ADD_BUTTON_TEXT = 'Add new item';
  const DEFAULT_INPUT_TYPE = 'email';
  const STATE_VALUE = {};
  const FIELD_ID = context.custom.createUid();
  let UUID_COUNTER = 0;
  let INPUTS_COUNTER = 0;

  const container = document.createElement('div');

  const inputsContainer = document.createElement('div');
  container.appendChild(inputsContainer);

  function buildAddNewItem() {
    const config = context.custom.getParams();
    const { add_button_text } = config;

    const ADD_BUTTON_TEXT = add_button_text || DEFAULT_ADD_BUTTON_TEXT;

    const addInputButton = document.createElement('button');
    addInputButton.type = 'button';
    addInputButton.classList.add('af-dynamic-input-add-button');
    addInputButton.id = `${FIELD_ID}_add-input-button`;
    addInputButton.onclick = buildInputContainer.bind(this);

    const addInputButtonIcon = document.createElement('span');
    addInputButtonIcon.classList.add('af-button', 'af-dynamic-input-add-button-icon');
    addInputButtonIcon.innerText = '+';

    const addInputButtonText = document.createElement('span');
    addInputButtonText.classList.add('af-dynamic-input-add-button-text');
    addInputButtonText.innerText = ADD_BUTTON_TEXT;

    addInputButton.appendChild(addInputButtonIcon);
    addInputButton.appendChild(addInputButtonText);
    container.appendChild(addInputButton);
  }

  function removeInput(container, input) {
    delete STATE_VALUE[input.name];
    container.remove();
  }

  function buildRemoveInputButton(container, input) {
    const button = document.createElement('button');
    button.type = 'button';
    button.classList.add('af-button', 'af-dynamic-input-remove-button');
    button.innerText = '-';
    button.onclick = removeInput.bind(this, container, input);

    INPUTS_COUNTER--;

    return button;
  }

  function buildInput() {
    const config = context.custom.getParams();
    const { placeholder, input_type } = config;

    const PLACEHOLDER = placeholder || DEFAULT_PLACEHOLDER;
    const INPUT_TYPE = input_type || DEFAULT_INPUT_TYPE;

    const input = document.createElement('input');
    input.type = INPUT_TYPE;
    input.placeholder = PLACEHOLDER;
    input.classList.add('af-stringField-input');
    input.name = `${FIELD_ID}_${UUID_COUNTER}`;
    input.id = input.name;
    input.addEventListener('change', () => {
      STATE_VALUE[input.name] = input.value;
    });

    UUID_COUNTER++;

    return input;
  }

  function buildInputContainer() {
    const container = document.createElement('div');
    container.classList.add('af-dynamic-input-container');

    const input = buildInput();
    container.appendChild(input);

    const removeButton = buildRemoveInputButton(container, input);
    container.appendChild(removeButton);

    inputsContainer.appendChild(container);

    INPUTS_COUNTER++;
  }

  function initComponent() {
    const config = context.custom.getParams();
    const { initial_inputs } = config;

    const INITIAL_INPUTS = initial_inputs || DEFAULT_INITIAL_INPUTS;
    INPUTS_COUNTER = INITIAL_INPUTS

    for (let i = 0; i < INITIAL_INPUTS; i++) {
      buildInputContainer();
    }
  }

  function blockFields(value) {
    const inputKeys = Object.keys(STATE_VALUE);

    inputKeys.forEach((o) => {
      const selector = document.getElementById(o);
      selector.disabled = value;
    });
  }

  return {
    init() {
      buildAddNewItem();
      initComponent();
      return container;
    },

    block() {
      blockFields(true);
    },

    unblock() {
      blockFields(false);
    },

    getValue() {
      return Object.values(STATE_VALUE);
    },
  };
}
```

**CSS code:**

```css lines expandable theme={null}
.af-button.af-dynamic-input-remove-button {
  width: 48px;
  color: var(--button-font-color);
  background: var(--button-background-color);
}

.af-dynamic-input-container {
  display: flex;
  margin-bottom: var(--spacing-1);
}

.af-dynamic-input-container input {
  margin-right: var(--spacing-1);
}

button.af-dynamic-input-add-button {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  margin-top: var(--spacing-1);
  display: inline-flex;
  cursor: pointer;
  align-items: center;
}

.af-button.af-dynamic-input-add-button-icon {
  background: var(--primary-color);
  width: 24px;
  padding: 0;
  height: 24px;
  border-radius: .3em;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  margin-right: var(--spacing-1);
}

.af-dynamic-input-add-button-text {
  color: var(--label-font-color);
  font-size: var(--label-font-size);
}

.af-dynamic-input-add-button:focus {
  outline: none;
}

.af-dynamic-input-add-button:hover .af-button {
  transition: filter var(--transition-normal), box-shadow var(--transition-normal);
  filter: brightness(1.1);
  box-shadow: 0 0 0 var(--outline-width) var(--outline-color), 0px 4px 8px -4px var(--shadow-color), 0px 16px 24px var(--shadow-color);
}

.af-dynamic-input-add-button:focus .af-button {
  box-shadow: 0 0 0 var(--outline-width) var(--outline-color), 0px 4px 8px -4px var(--shadow-color), 0px 16px 24px var(--shadow-color);
}
```
