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

## 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}}`. `@index`, `@root`, `../` parent paths, and array indexing are not supported and are stripped at render time. 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.
