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:8080

In 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.json

The 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.