Auto-Fill Form
The user types one seed value, like a company name, a domain, or a work email address, and every other field in the form fills in from a single Exa enrichment call. Each derived field can be visible to the user or captured silently for downstream use.
Demo
Populate many fields with a single user input. Choose to present them to the user or keep them hidden in your database.
How it works
A form is one seed field plus a list of derived fields. Each derived field carries the Exa outputSchema description, its expected type, and a visible flag that controls whether to render it.
On submit, the server builds an outputSchema from the derived fields and fires one exa.search call with type: "deep". The client patches each field as the response returns. Hidden fields end up in the form state for downstream use (routing, scoring, outbound automation) without ever rendering to the user.
Source code
"use client";
import { useState } from "react";
import {
DERIVED_FIELDS,
type DerivedField,
type FieldState,
} from "@/lib/input-form-fields";
export function InputFormExample() {
const [seed, setSeed] = useState("");
const [fields, setFields] = useState<Record<string, FieldState>>(() =>
Object.fromEntries(DERIVED_FIELDS.map((f) => [f.id, { status: "idle" } as FieldState]))
);
const [submitting, setSubmitting] = useState(false);
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (!seed.trim() || submitting) return;
setSubmitting(true);
setFields(
Object.fromEntries(
DERIVED_FIELDS.map((f) => [f.id, { status: "enriching" } as FieldState])
)
);
try {
const res = await fetch("/api/enrich-form", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
seed,
derived: DERIVED_FIELDS.map((f) => ({
id: f.id,
description: f.description,
outputType: f.outputType,
})),
}),
});
const json = await res.json();
if (!res.ok || json.error) {
const msg = json.error || `Request failed (${res.status})`;
setFields(
Object.fromEntries(
DERIVED_FIELDS.map((f) => [f.id, { status: "error", error: msg } as FieldState])
)
);
return;
}
const data = json.data ?? {};
setFields(
Object.fromEntries(
DERIVED_FIELDS.map((f) => {
const v = data[f.id];
if (v === undefined || v === null) {
return [f.id, { status: "error", error: "Not found" } as FieldState];
}
return [f.id, { status: "done", value: v } as FieldState];
})
)
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
setFields(
Object.fromEntries(
DERIVED_FIELDS.map((f) => [f.id, { status: "error", error: msg } as FieldState])
)
);
} finally {
setSubmitting(false);
}
}
const visible = DERIVED_FIELDS.filter((f) => f.visible);
const hidden = DERIVED_FIELDS.filter((f) => !f.visible);
const anyTouched = Object.values(fields).some((s) => s.status !== "idle");
return (
<form
onSubmit={onSubmit}
className="border border-[#e5e5e5] bg-white p-6 space-y-5"
>
<div>
<label className="block text-[13px] font-medium text-[#111827] mb-1.5">
Company
</label>
<div className="flex gap-2">
<input
type="text"
value={seed}
onChange={(e) => setSeed(e.target.value)}
placeholder="Stripe, stripe.com, jane@stripe.com..."
className="flex-1 border border-[#e5e5e5] bg-white text-[13px] text-[#111827] px-3 py-2 focus:outline-none focus:border-[var(--color-brand)]"
/>
<button
type="submit"
disabled={submitting || !seed.trim()}
className="bg-[var(--color-brand)] text-white text-[13px] font-medium px-4 py-2 disabled:opacity-50"
>
{submitting ? "Enriching..." : "Enrich"}
</button>
</div>
<p className="text-[12px] text-[#6b7280] mt-1.5">
Hit Enrich. One Exa call populates every field below.
</p>
</div>
{visible.map((f) => (
<DerivedFieldRow key={f.id} label={f.label} state={fields[f.id]} />
))}
{anyTouched && (
<div className="pt-3 border-t border-dashed border-[#e5e5e5]">
<div className="text-[11px] font-medium uppercase tracking-wider text-[#9ca3af] mb-3">
Captured silently (not rendered to the user)
</div>
<div className="space-y-2">
{hidden.map((f) => (
<HiddenFieldRow key={f.id} id={f.id} state={fields[f.id]} />
))}
</div>
</div>
)}
</form>
);
}
function DerivedFieldRow({
label,
state,
}: {
label: string;
state: FieldState;
}) {
return (
<div>
<label className="block text-[13px] font-medium text-[#111827] mb-1.5">
{label}
</label>
<ValueBox state={state} />
</div>
);
}
function HiddenFieldRow({ id, state }: { id: string; state: FieldState }) {
return (
<div className="flex items-baseline gap-3 text-[13px]">
<span className="font-mono text-[12px] text-[#6b7280] w-44 shrink-0">
{id}
</span>
<span className="text-[#374151] min-w-0 break-words">
<InlineValue state={state} />
</span>
</div>
);
}
function ValueBox({ state }: { state: FieldState }) {
if (state.status === "enriching") {
return (
<div className="h-9 border border-[#e5e5e5] bg-[#fafafa] flex items-center px-3">
<div className="h-3 w-2/3 rounded bg-[#e5e7eb] animate-pulse" />
</div>
);
}
if (state.status === "error") {
return (
<div className="border border-[#fecaca] bg-[#fef2f2] text-[13px] text-[#b91c1c] px-3 py-2">
{state.error}
</div>
);
}
if (state.status === "done") {
return (
<div className="border border-[#e5e5e5] bg-[#fafafa] text-[13px] text-[#111827] px-3 py-2">
{formatValue(state.value)}
</div>
);
}
return (
<div className="border border-[#e5e5e5] bg-white text-[13px] text-[#9ca3af] px-3 py-2">
—
</div>
);
}
function InlineValue({ state }: { state: FieldState }) {
if (state.status === "enriching") {
return (
<span className="inline-block h-2.5 w-24 rounded bg-[#e5e7eb] animate-pulse align-middle" />
);
}
if (state.status === "error") {
return <span className="text-[#b91c1c]">{state.error}</span>;
}
if (state.status === "done") {
return <>{formatValue(state.value)}</>;
}
return <span className="text-[#9ca3af]">—</span>;
}
function formatValue(v: string | number | string[]) {
if (Array.isArray(v)) return v.join(", ");
return String(v);
}