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

# Custom Interface

> Display-only tool that renders prior step outputs through a custom Handlebars HTML template in the task feed.

This document covers `custom_interface`, a display-only tool that renders prior step outputs through a custom Handlebars HTML template in the task feed. The template is not pre-authored: on the first run the tool sees the actual data, generates a template with an LLM from the user's UI description, and freezes it into the tool's configuration; subsequent runs reuse the frozen template with no generation call.

## When to use it (vs `app_tool` vs `custom_connector`)

* **`custom_interface`** — display-only. Renders data from prior steps as a read-mostly HTML view (cards, tables, side-by-side comparisons) with citation highlighting and inline value editing. It performs no external calls and drives no workflow logic.
* **`app_tool`** — interactive. A configured-tool kind backed by a generated application whose runtime calls go through `/api/app-runtime` (gated by the binding's allowlist). Use when the user needs a working mini-app, not a rendered view.
* **`custom_connector`** — integration. Connects an agent to an external HTTP API (auth, endpoints, schemas). Its optional UI template renders a connector result; it exists to fetch/push data, not to display prior step outputs.

## Authentication and enablement

No integration or credentials — the tool only reads data already present in the task. It is a configured tool: each instance is created from the agent's tool settings with a display name and a **UI description** (e.g. "side-by-side PO vs Invoice comparison"), stored as bindings (`ui_description`, `title`, `display_name`). Off by default — enable it in the agent's Tools panel.

## Inputs

Required:

* `variables` — the prior step outputs to display. Each item is `{name, ref}` where `ref` is a `${VAR_N}` reference and `name` is the logical root the template uses (e.g. `invoice`). Pass the whole variable, not a sub-path.

Injected from bindings (not shown as a per-run input):

* `ui_template` — the frozen HTML template (empty on first run).
* `ui_description`, `title`, `display_name` — configuration metadata.

## Output

Structured content:

```json theme={null}
{
  "tool_name": "custom_interface",
  "display_name": "Invoice Review",
  "data": { "invoice": { "invoice_number": { "value": "INV-1", "word_id_groups": [4, 5] } } },
  "ui_template": "<div>{{invoice.invoice_number.value}}</div>",
  "render_mode": "custom_ui",
  "title": "Invoice Review",
  "completed_at": "2026-07-29T10:00:00Z",
  "configured_tool_id": "<configured_tools row id>"
}
```

* `render_mode` is `custom_ui` when a template is available, `json` when generation failed (the frontend then falls back to a structured data view).
* Citable values (`{value, word_id_groups}`) render as clickable citation spans linking back to the source document; double-clicking a field allows inline correction (only the corrected `data` is persisted — the template and render mode are server-owned and cannot be changed through an edit).
* Dropdown bindings (`{{select …}}`, below) render as editable `<select>` controls; picking an option registers a pending edit exactly like a double-click text correction (same Save/Discard bar, same `data`-only persistence). The saved value is **not** validated server-side against the options list — options can be data-driven and differ between the render and the save. A dropdown bound to a path whose parent objects are absent from this run's data still saves: the missing branch is created on write (a numeric next segment creates an array, so `line_items.0.uom` rebuilds the right shape). Only a path blocked by an existing non-object value is refused, and that reports an inline error on the Save bar rather than clearing the pending edit.

## Template lifecycle

1. **First run**: no frozen template → the tool builds a compact shape skeleton of the actual data and calls the LLM (Pro model, falling back to Default) to generate a Handlebars template, then freezes it into `configured_tools.bindings.ui_template`. The freeze is fill-empty-only (never overwrites an existing template, e.g. a user's saved edit) and is mirrored onto the tool's draft and active-version counterpart rows so the version diff stays clean.
2. **Subsequent runs**: the frozen template is injected via bindings. Because extraction field names can vary run to run, a fast field-mapping LLM call aliases this run's data keys to the template's paths (add-only, fail-open).
3. **Editing**: the config editor can regenerate, hand-edit, or refine the template with feedback (`POST /api/configured-tools/:id/custom-interface/refine`) and save it (`PUT /api/configured-tools/:id/custom-interface/template` — an explicit save always overwrites).

Supported template syntax: `{{var}}` (dot paths), `{{#each}}`, `{{#if}}`, `{{#unless}}`, `{{#with}}`, all with `{{else}}`; comparison guards `{{#if (eq|ne|gt|gte|lt|lte field …)}}`; and dropdown bindings `{{select field options="A|B|C"}}` (fixed pipe-separated list) or `{{select field options=path.to.array}}` (array in the current context — strings, or objects carrying `value` and optionally `label`). A dropdown whose options don't resolve for a run degrades to the plain text render; a current value missing from the list is kept as an extra option. `@index`, `@root`, `../` parent paths, and array indexing are not supported and are stripped at render time. Hand-written `<select>`/`<option>`/`<input>` HTML has no data binding — `{{select …}}` is the only wired form control. Rendered HTML is sanitized (DOMPurify) before injection — scripts, event handlers, and `javascript:` URLs never execute.

## Limits

* Template generation happens at most once per configured tool (first run wins; failures fall back to the JSON view and retry on the next run).
* The generation prompt describes data shape within a bounded line budget; extremely deep/wide payloads have deep fields omitted from the skeleton (logged as a warning).
* Rendering is client-side; very large `data` payloads are subject to the platform's tool-output caps.

## Side effects

* First successful generation writes `ui_template` into the tool's `configured_tools.bindings` (and its draft/active-version counterparts). No external systems are touched.

## Expected errors

* `variables is required — pass at least one {name, ref}` — no input provided.
* Template generation failure (LLM error/empty output) — logged warning; the step still completes with `render_mode: "json"`.
* Freeze/persist failures are best-effort — logged warning, never fail the step; the template is regenerated on the next run.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.