Why I built Vant Flow: An Angular schema-driven form platform for fast-changing business workflow

I did not build Vant Flow because I wanted to build another form builder. I built it because I kept running into a delivery wall.
Github Repo: https://github.com/DonnC/vant-flow
I work in a banking environment in Zimbabwe, inside a digital team where workflows do not stay still for long. A compliance rule changes, a department wants a new approval step, or a team needs to turn a simple process into a multi-step stepper flow. My internal clients do not care if I build from scratch or use a template—they just need the solution to work.
This is the practical story of the problem I was solving, the approach I chose, and why this model has worked for me.
The problem was never just collecting data
The hard part was dealing with business documents that behave like mini applications.
In my context, that usually means:
fields that become required only in certain cases
sections that appear or disappear based on values, roles, or status
approvals, review notes, and follow-up actions
tables, signatures, and attachments
workflows that begin as a simple form and later become multi-step
the same document needing to support authoring, runtime, and readonly replay
There are good products in this space already. This is not a claim that everything else is bad.
But in my environment, a few constraints kept coming up:
paid platforms are not always an option
procurement and security reviews take time
some tools work well for basic form capture but become awkward when the workflow behaves more like part of the application itself
some platforms want to own too much of the stack when I need the host app to stay in control
So I wanted something more specific: a schema-driven way to build forms that change often, carry real business logic, and still fit inside an existing application architecture.
The Approach: Schema Once, Render Everywhere
design the form once, store it as schema, and let the renderer execute it at runtime instead of rebuilding Angular screens every time a workflow changes.
At the center of that is a shared schema contract called DocumentDefinition.
That schema can then power:
form builder and rendering
preview mode for testing
readonly replay for support, review, or audit
The platform is heavily inspired by the ideas and ergonomics of the Frappe ecosystem , but implemented as a focused Angular-first tool. It is split into three main layers:
VfBuilder: A visual editor for authoring schemas.
VfRenderer: A runtime engine that turns those schemas into live forms.
Host Control: The host application (your app) still owns the heavy lifting: authentication, storage, and APIs.
This separation is vital. I did not want a platform that tries to swallow the whole app; I wanted a tool that fits into my existing architecture.
What the developer experience looks like
The core setup is intentionally simple.
Register the provider:
import { ApplicationConfig } from '@angular/core';
import { provideVfFlow } from 'vant-flow';
export const appConfig: ApplicationConfig = {
providers: [provideVfFlow()]
};
Render a schema:
<vf-renderer
[document]="document"
[initialData]="initialData"
[metadata]="metadata"
(formAction)="handleAction($event)"
></vf-renderer>
Open the builder:
<vf-builder
[initialSchema]="document"
[previewMetadata]="previewMetadata"
(schemaChange)="onSchemaChange($event)"
></vf-builder>
That is the main loop:
author schema
store schema
render schema
respond to runtime events in host code
What I like about this model is that the frontend stops treating every workflow as a brand-new page.
Why it felt useful in real work
The strength of the approach is not just "forms from JSON". Plenty of projects can do that.
What made it useful for me is that the same document can support:
builder authoring
live runtime execution
dynamic conditions
stepper flows
richer workflow actions
readonly replay later
That changes the economics of delivery.
Instead of rebuilding screens every time a department changes a process, a lot of that change can move into schema, runtime rules, and host metadata.
Making it Dynamic with Scripting
Static schema alone is not enough for operational workflows. You also need runtime behavior. For example: You need to calculate totals from tables, update filters on the fly, or block actions until validation passes.
Vant Flow supports two layers:
declarative rules like
depends_onandmandatory_depends_onscripted behavior through a constrained
frmAPI
This means the business behavior travels with the document definition allowing you to handle things like:
make a field required only after a certain value is selected
set fields to readonly after approval
show or hide sections at runtime
update remote lookup filters dynamically
calculate totals from table rows
block actions until validation passes
For example:
frm.on('status', (value, frm) => {
if (value === 'Approved') {
frm.set_df_property('batch_id', 'read_only', true);
}
});
And for a table total:
// whenever parts table is updated, recompute the total cost of parts
frm.on('parts_used', (rows, frm) => {
const total = (rows || []).reduce((sum, row) => {
return sum + ((Number(row.qty) || 0) * (Number(row.rate) || 0));
}, 0);
frm.set_value('parts_total', total);
});
This is the kind of logic I used to see hardcoded inside custom page components. I wanted the behavior to travel with the document definition instead
Real-World Use Cases
This approach has been practical for several scenarios we deal with daily:
KYC and Onboarding: Handling complex document uploads and signatures.
Internal Approvals: Multi-step flows for procurement or leave requests.
Audit and Inspection: Capturing data in the field using features like camera capture and replaying it later in a readonly mode for auditors.
The renderer stays host-agnostic, meaning your app controls how uploads are handled through a mediaHandler. This is vital for us because we need to point attachments to our own secure object storage or CDN-backed services.
The "Extra" Goodies
There are a few parts of the approach that I think make it much more useful in real projects.
Link fields
These are remote autocomplete fields, not just static selects. They can point to backend search endpoints, map returned data, and change filters at runtime.
Attachments and signatures
The renderer supports a mediaHandler, so the host application can control uploads and store compact file references instead of raw payloads in form state.
Host-controlled runtime state
The host can still control runtime behavior directly through inputs like:
runFormScriptsreadonlyFieldshiddenFieldsdisabledActionButtonshiddenActionButtons
Readonly replay
This is one of the features that matters a lot in enterprise work. The same schema can be reused later against saved data for support, audit, or compliance review.
AI and MCP
As an AI enthusiast, I wanted to see how we could make the authoring process faster. I included support for MCP (Model Context Protocol) so AI agents can scaffold forms directly from a prompt or even an uploaded image of a paper form or assist a user to fill out a form.
A Realistic Perspective
I will not act like this is a perfect solution for every team. Building and owning your own platform comes with trade-offs.
a flexible schema model needs discipline
scripting needs guardrails
the host app still owns workflow orchestration and permissions
building your own platform means owning maintenance and documentation
So no, I would not present this as a miracle solution.
But if you keep building workflow-heavy apps and you are tired of turning every process change into another custom frontend rebuild, this pattern is worth serious consideration.
Final Thought
Vant Flow came from a practical need: making workflow changes cheaper without giving up control.
I like solutions that are reusable, grounded, and close to how the business actually works. Vant Flow is one example of that approach, and if you work on internal tools, regulated systems, or operational workflows, you may find the pattern useful for your next project.


