> ## Documentation Index
> Fetch the complete documentation index at: https://support.deepfield-ai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a custom question interface

> Create, validate, translate, and test a custom interface while preserving the question’s stored answer type.

A custom code renderer changes how one question is presented and answered. The question keeps its built-in type, so its stored value, server validation, charts, and typed exports continue to follow that type.

Use custom code when the participant needs an interaction that cannot be represented by the built-in settings, such as a constrained price ladder, a content-dependent allocation, a click-to-reveal task, or a custom arrangement of values from earlier answers.

Keep routing, conditions, section assignment, screening, and quotas in their native controls. A renderer governs one question screen; it does not control the rest of the participant journey. Custom-code questions cannot be used as quota variables.

## Ask the assistant to build the renderer

You do not need to write the component yourself. In the **Assistant** panel, name the question and describe:

1. What the participant sees and does.
2. Which earlier answers the interaction needs.
3. What value the question must store.
4. The condition that makes the answer complete.
5. Any mobile, translation, or accessibility requirement.

For example:

> On “Choose your cold-coffee budget for next month,” create a custom interface that starts from the earlier monthly-spend answer. Increase or decrease it in €5 steps, show the current amount at all times, and store one numeric answer. The participant can continue after selecting an amount between €0 and €150. Keep it usable on mobile.

After the assistant updates the form, open the renderer and complete the preview checks below before saving the questionnaire.

## Open the renderer editor

Open the question's **⋯** menu and select **Custom-built question**. If the question already uses one, select **Edit** in its custom-question strip. **Test** in that strip opens a participant test of the question; it does not open the editor preview.

<Frame caption="Open Custom-built question from the question menu; the underlying question type remains unchanged.">
  <img src="https://mintcdn.com/deepfieldai/dK8nllvOe0f0oG_-/images/building-your-study/custom-code-open.png?fit=max&auto=format&n=dK8nllvOe0f0oG_-&q=85&s=3d7058a861c11d0c607305433f38390a" alt="Question menu with Custom-built question selected and the question type still visible" width="296" height="554" data-path="images/building-your-study/custom-code-open.png" />
</Frame>

The dialog opens on **Preview**. Interact with the component until it produces an answer. The status beneath the preview reports whether that answer matches the question type.

<Frame caption="Preview runs the same sandboxed component used in the participant survey and shows whether its answer has the expected shape.">
  <img src="https://mintcdn.com/deepfieldai/dK8nllvOe0f0oG_-/images/building-your-study/custom-code-preview.png?fit=max&auto=format&n=dK8nllvOe0f0oG_-&q=85&s=a0d3afa97caffbdd3a6ab607c35f4cfc" alt="Custom-built question preview with the participant interaction and Edit code control" width="1188" height="774" data-path="images/building-your-study/custom-code-preview.png" />
</Frame>

Preview uses sample inputs and does not run the study's routing. Use **Test this question** to check the saved renderer in the participant journey. Save renderer changes first if that action is disabled. After changing an earlier answer, test that a dependent custom question receives the new inputs and asks for a fresh answer.

## Edit the component

Select **Edit code** to open the TSX editor. Select **Back to preview** to return to the interaction. It must export one React component. Imports are limited to `react` and `@deepfield/question-kit`.

The question kit provides the question definition, declared earlier answers, translatable parameters, locale, current answer, and answer controls:

```tsx theme={null}
import { OptionRow, useQuestion } from "@deepfield/question-kit";

export default function Question() {
  const { question, answer, setAnswer, setValid } = useQuestion();
  const selected = Array.isArray(answer) ? answer : [];

  return question.options.filter((option) => !option.is_group).map((option) => (
    <OptionRow
      key={option.id}
      selected={selected.includes(option.id)}
      input="radio"
      name={question.question_id}
      onClick={() => {
        setAnswer([option.id]);
        setValid(true);
      }}
    >
      {option.text}
    </OptionRow>
  ));
}
```

Use the **Coding guide** in the dialog for the components and answer contract available to the selected question. The component runs without access to the network, the DeepField page, browser cookies, or browser storage. It can use React, HTML, SVG, canvas, Tailwind utilities, theme colors, and components exported by the question kit.

<Frame caption="The Code view includes the source editor, compile feedback, and a coding guide for the current question contract.">
  <img src="https://mintcdn.com/deepfieldai/dK8nllvOe0f0oG_-/images/building-your-study/custom-code-editor.png?fit=max&auto=format&n=dK8nllvOe0f0oG_-&q=85&s=479066a2cc505ccad509194cda599da3" alt="Custom question Code view with source editor, compile status, and Coding guide control" width="1188" height="774" data-path="images/building-your-study/custom-code-editor.png" />
</Frame>

## Match the answer contract

`setAnswer` must use the value shape of the underlying type:

| Question type               | Stored value                                                                       |
| --------------------------- | ---------------------------------------------------------------------------------- |
| Numeric                     | A number                                                                           |
| Open-ended                  | A string                                                                           |
| Multiple Choice or Ranking  | An array of option IDs                                                             |
| Allocation or Multi-Numeric | An object mapping option IDs to numbers                                            |
| Categorical Matrix          | An object mapping row IDs to one column ID, or arrays when multi-select is enabled |
| Numeric Matrix              | An object mapping row IDs and column IDs to numbers                                |
| MaxDiff                     | An object with `best` and `worst` option IDs                                       |

Use IDs from `question.options` and `question.matrix_rows`. `setValid(false, message)` keeps **Next** unavailable and can show the participant what remains to be completed. The server validates the submitted value and the question's configured validation rules.

The current `answer` already contains restored progress. Use it as the submitted state so a participant who reloads can see and change the saved answer.

For a currency question, call `setCurrency("EUR")` (or the selected currency code) as well as `setAnswer(...)`. A numeric value alone does not pass the preview’s currency check.

## Add earlier answers and translatable text

Open **Advanced — inputs & text**.

* **Inputs** declares earlier questions. The renderer can access only those selected inputs.
* **Text shown to participants** stores named values that enter the standard translation workflow. Keys must be unique and non-empty.
* **Preview text in** switches among available runtime locales.
* **Sample inputs** lets you test Answered, Unanswered, and Skipped states for each declared input.

Participant-facing strings in the source are included in translation when they can be extracted. Use named text values for short labels or text that must be managed explicitly. The source code itself is not translated.

<Frame caption="Declare the earlier spend question and give each participant text a unique key.">
  <img src="https://mintcdn.com/deepfieldai/qcWsRCXzDL76h9Bd/images/building-your-study/configured-custom-inputs.png?fit=max&auto=format&n=qcWsRCXzDL76h9Bd&q=85&s=b312b8259a0531073fd6292f9f7f7936" alt="Custom renderer with one earlier question selected and four translatable text entries" width="1142" height="533" data-path="images/building-your-study/configured-custom-inputs.png" />
</Frame>

## Test and save the renderer

Before selecting **Save renderer**:

1. Complete the Preview with typical, boundary, and incomplete values.
2. Test each state of every earlier-answer input.
3. Switch the preview locale when the study is multilingual.
4. Confirm the answer status matches the underlying type.
5. Save the renderer, then save the questionnaire.
6. Open a test link and repeat the task on desktop and mobile widths.
7. Reload during the task and confirm the saved answer is restored correctly.

Compile errors and invalid text keys prevent the renderer from being saved. An invalid preview answer is a signal to correct the component before testing the study; the server will still reject a submitted value that does not match the question contract.

<Frame caption="The preview accepts the 35-euro numeric answer and its currency.">
  <img src="https://mintcdn.com/deepfieldai/dK8nllvOe0f0oG_-/images/building-your-study/configured-custom-preview.png?fit=max&auto=format&n=dK8nllvOe0f0oG_-&q=85&s=7f145848da23632e34a1a11efd999680" alt="Custom budget preview at 35 euros with the green Preview answer looks good status" width="1188" height="774" data-path="images/building-your-study/configured-custom-preview.png" />
</Frame>

<Frame caption="The participant starts from the earlier 30-euro answer and can increase it to 35 euros.">
  <img src="https://mintcdn.com/deepfieldai/qcWsRCXzDL76h9Bd/images/participants/participant-custom.png?fit=max&auto=format&n=qcWsRCXzDL76h9Bd&q=85&s=55bcc27631c81f87e3fe70991b19c369" alt="Participant budget control at 35 euros with Next enabled" width="1440" height="900" data-path="images/participants/participant-custom.png" />
</Frame>

## Understand analysis and export behavior

Charts read the typed answer defined by the question type. Analysis shows a notice when a question uses custom code because the chart may not represent every detail of the interaction.

The response Excel workbook and raw CSV export add a `[raw]` column for each custom-code question. It contains the exact stored value, including additional keys that do not appear in the type-specific columns.

<Frame caption="Open the saved answer in Responses: this custom interface stored the number 35.">
  <img src="https://mintcdn.com/deepfieldai/qcWsRCXzDL76h9Bd/images/responses/custom-answer-result.png?fit=max&auto=format&n=qcWsRCXzDL76h9Bd&q=85&s=08f508dc97136e659f703160ec05e715" alt="Saved numeric response 35 with an Edit answer control" width="552" height="312" data-path="images/responses/custom-answer-result.png" />
</Frame>

## Return to the built-in interface

Open the renderer and select **Remove — use the built-in renderer**. Save the questionnaire. The question keeps its wording, type, options, and standard settings, but participants return to the built-in answer interface.

## Troubleshoot a renderer

<AccordionGroup>
  <Accordion title="The code does not compile">
    Select **Edit code** and resolve the reported TypeScript or syntax error. Check component names and props in **Coding guide**, and remove any import other than `react` or `@deepfield/question-kit`.
  </Accordion>

  <Accordion title="The preview answer does not match the question">
    Compare the emitted value with the answer-contract table. Use option IDs and row IDs rather than display labels. Keep the original question type unless the intended analysis also requires a different data shape.
  </Accordion>

  <Accordion title="An earlier answer is missing">
    Add its question under **Advanced — inputs & text** and confirm it appears earlier in the participant journey. Handle Answered, Unanswered, and Skipped states in the component.
  </Accordion>

  <Accordion title="The participant cannot continue">
    Check the conditions passed to `setValid`, then test the same value in Preview. Confirm that the answer also passes any native **Answer validation** rule on the question.
  </Accordion>

  <Accordion title="The editor loads as plain text">
    The full code editor could not load. The fallback still permits text editing, but use Preview and a test link before publishing. Reload the builder if syntax assistance is required.
  </Accordion>
</AccordionGroup>

## Related

* [Choose and configure question types](/building-your-study/question-types)
* [Reuse earlier answers](/building-your-study/answer-piping)
* [Translate your survey](/translations/multilingual-studies)
* [Export response data](/responses/exporting-data)
