{"id":49080795,"url":"https://github.com/formloom/formloom","last_synced_at":"2026-04-23T07:01:37.661Z","repository":{"id":352591974,"uuid":"1190353418","full_name":"formloom/formloom","owner":"formloom","description":"LLM-driven dynamic forms — schema, tooling, headless React renderer, Zod adapter.","archived":false,"fork":false,"pushed_at":"2026-04-20T11:52:15.000Z","size":212,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T13:50:53.490Z","etag":null,"topics":["agents","ai","anthropic","form","formgeneration","llm","openai","react","structured-output","typescript","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/formloom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-24T07:48:08.000Z","updated_at":"2026-04-20T11:51:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/formloom/formloom","commit_stats":null,"previous_names":["formloom/formloom"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/formloom/formloom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formloom%2Fformloom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formloom%2Fformloom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formloom%2Fformloom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formloom%2Fformloom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/formloom","download_url":"https://codeload.github.com/formloom/formloom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formloom%2Fformloom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32169657,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T02:19:40.750Z","status":"ssl_error","status_checked_at":"2026-04-23T02:17:55.737Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai","anthropic","form","formgeneration","llm","openai","react","structured-output","typescript","zod"],"created_at":"2026-04-20T13:10:40.073Z","updated_at":"2026-04-23T07:01:37.629Z","avatar_url":"https://github.com/formloom.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Formloom\n\n[![CI](https://github.com/formloom/formloom/actions/workflows/ci.yml/badge.svg)](https://github.com/formloom/formloom/actions/workflows/ci.yml)\n[![npm @formloom/schema](https://img.shields.io/npm/v/@formloom/schema.svg?label=%40formloom%2Fschema)](https://www.npmjs.com/package/@formloom/schema)\n[![npm @formloom/llm](https://img.shields.io/npm/v/@formloom/llm.svg?label=%40formloom%2Fllm)](https://www.npmjs.com/package/@formloom/llm)\n[![npm @formloom/react](https://img.shields.io/npm/v/@formloom/react.svg?label=%40formloom%2Freact)](https://www.npmjs.com/package/@formloom/react)\n[![npm @formloom/zod](https://img.shields.io/npm/v/@formloom/zod.svg?label=%40formloom%2Fzod)](https://www.npmjs.com/package/@formloom/zod)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n\n\u003e **Some moments are better served by a dropdown than a dialogue.**\n\u003e Formloom lets your LLM hand the user a real UI mid-conversation, when the moment calls for one, and pick the chat back up when it's done.\n\n---\n\n## What it feels like, with Formloom\n\nYour user wants to book a consultation. The model decides this is a form moment, not a chat moment, and hands over an interface they already know how to use:\n\n```text\nYou  ›  I'd like to book a consultation.\nBot  ›  Sure. Grab a few details below.\n\n        ┌────────────────────────────────────────┐\n        │                                        │\n        │  Book a consultation                   │\n        │                                        │\n        │  Name    [ Alice Chen           ]      │\n        │  Email   [ alice@example.com    ]      │\n        │  Date    [ Fri, Apr 24     ▾    ]      │\n        │  Time    ○ Morning                     │\n        │          ● Afternoon                   │\n        │                                        │\n        │          [   Submit   ]                │\n        │                                        │\n        └────────────────────────────────────────┘\n\nYou  ›  (fills it once, submits)\nBot  ›  Booked for Fri, Apr 24 at 2:00 PM.\n        Confirmation sent to alice@example.com.\n```\n\nA tap on a date picker instead of typing \"Friday afternoon.\" A radio instead of a sentence. A file dialog instead of a file-size explanation. The user can see the whole task at once, fix a mistake from earlier without scrolling, and submit with a single click.\n\nThey're not being interviewed. They're getting something done.\n\nThe form renders with *your* components, in *your* design system. Formloom just owns the moment when the LLM reaches for a UI instead of another message.\n\n## What it replaces\n\nThe same moment, the old way:\n\n```text\nYou  ›  I'd like to book a consultation.\nBot  ›  Sure! What's your full name?\nYou  ›  Alice Chen\nBot  ›  And your email?\nYou  ›  alice@example.com\nBot  ›  What date works?\nYou  ›  Friday?\nBot  ›  This Friday or next?\nYou  ›  …\n```\n\nNothing here is *broken*. The model is polite, the user is patient. But every turn is a tiny writing exercise, a sentence composed for a machine that could have handed over a button. Chat treats every input as prose. That's wonderful for open-ended conversation. It's the wrong mode for picking a date, choosing one of three options, or uploading a file.\n\nFormloom gives the LLM a way to say, mid-conversation: *\"this part should be tactile, not typed.\"*\n\n## What Formloom is\n\nFormloom is the bridge between an LLM turn and a real UI.\n\nMid-conversation, your model can effectively say:\n\n\u003e *\"This part deserves an interface. Here's the shape of it.\"*\n\nIt emits a small JSON schema. Formloom validates it, your frontend renders it using *your* components, the user submits, and the structured result flows straight back into the conversation.\n\nThree pieces, each doing one thing well:\n\n- **A schema vocabulary** the model already knows how to speak. Seven field primitives, no more.\n- **A parser + validator** so nothing malformed ever reaches your UI.\n- **A headless React hook** that turns the schema into *your* form, not ours.\n\nYour chat stays a chat. Your design system stays yours. Formloom just owns the handoff.\n\n## Why this matters\n\n- **Familiar beats novel.** Users have tapped date pickers, radios, and dropdowns since the 90s. Let them use muscle memory instead of composing sentences.\n- **The whole task, at a glance.** A form shows everything up front. Chat drips it out one question at a time, so the user can't skim, can't plan, can't see how close they are to done.\n- **Fix mistakes where you made them.** Edit field one after filling field five. In chat, that's a scroll-up, a re-explain, and a hope that the model understands.\n- **Your design system stays yours.** Formloom renders nothing on its own. It hands you the schema and state; your components render the pixels.\n- **Plays nicely with every provider.** OpenAI, Anthropic, Gemini, Mistral, Ollama. Use a tool call, a `response_format`, or a plain-text fallback. No lock-in.\n\n## A schema looks like this\n\n```json\n{\n  \"version\": \"1.2\",\n  \"title\": \"Job Application\",\n  \"fields\": [\n    {\n      \"id\": \"name\",\n      \"type\": \"text\",\n      \"label\": \"Full name\",\n      \"validation\": { \"required\": true }\n    },\n    {\n      \"id\": \"years\",\n      \"type\": \"number\",\n      \"label\": \"Years of experience\",\n      \"validation\": { \"min\": 0, \"max\": 60, \"integer\": true, \"required\": true }\n    },\n    {\n      \"id\": \"employment_type\",\n      \"type\": \"radio\",\n      \"label\": \"Employment type\",\n      \"options\": [\n        { \"value\": \"full_time\", \"label\": \"Full-time\" },\n        { \"value\": \"contract\", \"label\": \"Contract\" }\n      ],\n      \"validation\": { \"required\": true }\n    },\n    {\n      \"id\": \"day_rate\",\n      \"type\": \"number\",\n      \"label\": \"Day rate (USD)\",\n      \"showIf\": { \"field\": \"employment_type\", \"equals\": \"contract\" }\n    }\n  ],\n  \"sections\": [\n    { \"id\": \"about\", \"title\": \"About you\", \"fieldIds\": [\"name\", \"years\"] },\n    { \"id\": \"role\", \"title\": \"Role details\", \"fieldIds\": [\"employment_type\", \"day_rate\"] }\n  ],\n  \"submitLabel\": \"Submit application\"\n}\n```\n\nField types are deliberately small:\n\n`text` • `boolean` • `radio` • `select` • `date` • `number` • `file`\n\nRich behavior comes from composition: conditional visibility (`showIf`), grouping (`sections`), validation rules, rendering hints, and async validators in React.\n\n## Which package do you need?\n\n| Package | Use it when you need... |\n|---------|--------------------------|\n| [`@formloom/schema`](packages/schema/) | Schema types, validator, `showIf` engine, safe regex handling |\n| [`@formloom/llm`](packages/llm/) | System prompt, provider tool definitions, parser, submission formatter |\n| [`@formloom/react`](packages/react/) | Headless React hooks to render and submit Formloom forms |\n| [`@formloom/zod`](packages/zod/) | Zod / Standard Schema adapters for server-side validation |\n\nMost React apps want:\n\n```bash\nnpm install @formloom/schema @formloom/llm @formloom/react\n```\n\nAdd `@formloom/zod` if you want server-side validation that matches the client exactly.\n\n## Three steps, end to end\n\n### 1. Ask the model for a form\n\n```ts\nimport {\n  FORMLOOM_SYSTEM_PROMPT,\n  FORMLOOM_TOOL_OPENAI,\n  parseFormloomResponse,\n} from \"@formloom/llm\";\n\nconst response = await openai.chat.completions.create({\n  model: \"your-model\",\n  messages: [\n    { role: \"system\", content: `You are a helpful assistant.\\n\\n${FORMLOOM_SYSTEM_PROMPT}` },\n    { role: \"user\", content: \"I want to book an appointment\" },\n  ],\n  tools: [FORMLOOM_TOOL_OPENAI],\n});\n\nconst toolCall = response.choices[0].message.tool_calls?.[0];\nconst parsed = parseFormloomResponse(toolCall?.function.arguments);\n```\n\n`FORMLOOM_SYSTEM_PROMPT` teaches the model the vocabulary. `FORMLOOM_TOOL_OPENAI` fixes the return shape. `parseFormloomResponse` validates everything before it ever touches your UI.\n\n### 2. Render it, your way\n\n```tsx\nimport { useFormloom } from \"@formloom/react\";\n\nfunction MyForm({ schema, onDone }) {\n  const form = useFormloom({ schema, onSubmit: onDone });\n\n  return (\n    \u003cform onSubmit={(e) =\u003e { e.preventDefault(); void form.handleSubmit(); }}\u003e\n      {form.visibleFields.map(({ field, state, onChange, onBlur }) =\u003e (\n        \u003cdiv key={field.id}\u003e\n          \u003clabel\u003e{field.label}\u003c/label\u003e\n          {field.type === \"text\" \u0026\u0026 (\n            \u003cinput\n              value={(state.value as string | null) ?? \"\"}\n              onChange={(e) =\u003e onChange(e.target.value)}\n              onBlur={onBlur}\n            /\u003e\n          )}\n          {state.touched \u0026\u0026 state.error !== null \u0026\u0026 \u003cp\u003e{state.error}\u003c/p\u003e}\n        \u003c/div\u003e\n      ))}\n\n      \u003cbutton type=\"submit\" disabled={form.isSubmitting || !form.isValid}\u003e\n        {schema.submitLabel ?? \"Submit\"}\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n`useFormloom` owns state, visibility, validation, and submission. `visibleFields` already respects `showIf`, so hidden fields never render and never submit. You own every pixel.\n\n### 3. Send the result back\n\n```ts\nimport { formatSubmission } from \"@formloom/llm\";\n\nconst nextMessage = formatSubmission(submittedData, {\n  provider: \"openai\",\n  toolCallId: toolCall.id,\n});\n```\n\n`formatSubmission` wraps the submitted data in the shape the provider expects. Append it to the conversation. The model continues, this time with clean structured values in hand.\n\n## Integration modes\n\nPick whichever fits your flow:\n\n| Mode | Best for | What you reach for |\n|------|----------|---------------------|\n| **Tool calling** | Chat flows where the model chooses when a form is needed | `FORMLOOM_SYSTEM_PROMPT`, provider tool export, `parseFormloomResponse`, `formatSubmission` |\n| **`response_format`** | Deterministic flows where the model must always return a schema | `FORMLOOM_RESPONSE_FORMAT_OPENAI`, `parseFormloomResponse` |\n| **Text fallback** | Models without tool calling | `FORMLOOM_TEXT_PROMPT`, `parseFormloomResponse` |\n\nProvider tool exports shipped today:\n\n`FORMLOOM_TOOL_OPENAI` • `FORMLOOM_TOOL_ANTHROPIC` • `FORMLOOM_TOOL_GEMINI` • `FORMLOOM_TOOL_MISTRAL` • `FORMLOOM_TOOL_OLLAMA`\n\n## Features you get\n\n- Schema validation before a single pixel renders\n- Conditional fields via `showIf` with `equals`, `in`, `notEmpty`, composed via `allOf` / `anyOf` / `not`\n- Section grouping for longer forms\n- Multi-step wizards with `useFormloomWizard` — one section per step, validation-gated `next()`\n- Option descriptions (two-line radio/select) and opt-in \"Other…\" freeform input on radio/select\n- `readOnly` / `disabled` view modes for recap and review surfaces\n- File uploads, inline or delegated to your upload handler\n- Async field validators in React (debounced, abortable, flushed on submit)\n- Live-sync hook `onValueChange` to stream partial answers to an LLM while the user types\n- Custom widget variants via `hints.variant` — host-defined, opaque, declaration-mergeable\n- Capability profiles — one declaration narrows the system prompt, tool JSON Schema, and validator per surface\n- ReDoS-safe regex handling for LLM-authored patterns\n- Zod and Standard Schema adapters for server-side parity\n\n## Examples in this repo\n\n| Example | What it shows | Run it with |\n|---------|----------------|-------------|\n| [`examples/basic-react`](examples/basic-react/) | Hardcoded schemas, no LLM. All seven field types, sections, hints, `showIf` | `pnpm --filter @formloom/example-basic-react dev` |\n| [`examples/provider-free`](examples/provider-free/) | Simulated tool-call, `response_format`, and text-prompt flows. No API key needed | `pnpm --filter @formloom/example-provider-free dev` |\n| [`examples/fullstack`](examples/fullstack/) | End-to-end chat app where the model generates a form and receives the submission back | `pnpm --filter @formloom/example-fullstack dev` |\n\nFor the fullstack example, copy the env file first:\n\n```bash\ncp examples/fullstack/.env.example examples/fullstack/.env\n```\n\nThen add your API key.\n\n## Development\n\nThis repo is a `pnpm` workspace using Turborepo.\n\n```bash\npnpm install\npnpm build\npnpm test\npnpm lint\npnpm typecheck\n```\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for setup, PR flow, and changesets.\n\nFor security issues, see [SECURITY.md](SECURITY.md). Please don't open a public vulnerability report.\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformloom%2Fformloom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fformloom%2Fformloom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformloom%2Fformloom/lists"}