Getting started
quillflow has four parts. You can use them together or one at a time:
| Part | What it does |
|---|---|
| Authoring | Write forms in the designer, in Excel or in YAML. The compiler turns them into one canonical JSON definition. |
| The engine | Runs a form: rules, calculations, lookups and validation. There are identical versions for TypeScript and .NET. |
<qf-form> |
A web component that shows a form in any page: plain HTML, Blazor, React, Angular, Vue. |
| Server SDKs | Serve forms, run lookups and re-validate submissions. Available for Node and ASP.NET Core. |
Try it in two minutes
You need Node.js 24 or later (the examples run TypeScript directly). The .NET parts also need the .NET 10 SDK.
git clone <the quillflow repository> quillflow && cd quillflow
npm install
npm run designer # the designer on http://localhost:8090
npm run example # a demo portal on http://localhost:8080In the designer, choose Open example → Change of address and type an address in the preview. In the demo portal, fill in the loan application. The lookups (company, properties, valuation) answer from a pretend register running in the demo server.
Your first form
Create contact.yaml:
id: contact
version: "1"
title: { da: Kontakt os, en: Contact us }
languages: [da, en]
fields:
- name: name
type: text
label: { da: Navn, en: Name }
required: yes
- name: topic
type: select_one
choices: topics
label: { da: Hvad drejer det sig om?, en: What is it about? }
required: yes
- name: case_number
type: text
label: { da: Sagsnummer, en: Case number }
relevant: topic = "case"
constraint: 'MATCHES(., "^[0-9]{4}-[0-9]{6}$")'
constraintMessage: { da: "Skriv sagsnummeret som 2026-000123.", en: "Write the case number as 2026-000123." }
choices:
topics:
- { value: case, label: { da: En sag, en: A case } }
- { value: other, label: { da: Noget andet, en: Something else } }Compile it:
npx quillflow compile contact.yaml -o contact.jsonThe same form could just as well be an Excel workbook, or built in the designer. See Authoring forms.
Show it in a page
Serve contact.json from an API (next section), then add the web component to any page:
<link rel="stylesheet" href="quillflow.css" />
<script type="module" src="quillflow.bundle.js"></script>
<qf-form form="contact" api="/api"></qf-form>See Embedding for Blazor, events, theming and your own widgets.
Serve it
The form needs a small API: it fetches its definition, runs lookups and sends the submission.
// Node
import { createHandler, memoryFormStore, toNodeListener } from "@quillflow/server-node";
const handler = createHandler({
basePath: "/api",
forms: memoryFormStore([contact]),
onSubmit: async (submission) => ({ reference: await cases.create(submission.data) }),
});
http.createServer(toNodeListener(handler)).listen(8080);// ASP.NET Core
builder.Services.AddQuillflow()
.AddFormsFromDirectory("forms")
.OnSubmit(async (submission, ctx, ct) => new SubmissionReceipt(await cases.CreateAsync(submission.Data, ct)));
app.MapQuillflow("/api");Every submission is checked again on the server with the same rules the browser used. Only visible
fields reach onSubmit, and calculated values are recomputed. See Servers.