Typed in your repo, checked on the server.
defineForm() or JSX defines the fields once; the same rules run in the rendered form and on submit.
Compare Form.io with Fillo when the form lives in a component app: a typed schema in your repo, no database to operate, and fields your own code renders.
Form.io turns forms into resources on a platform you run. Fillo is for when the form should be a typed schema in your codebase, rendered by your components, with nothing to operate behind it.
defineForm() or JSX defines the fields once; the same rules run in the rendered form and on submit.
Real DOM in your tree, under your app's own Content Security Policy, styled by your stylesheet.
app.acme.co/requests/newAccess request
Grid, versions, exports, API reads, and signed webhooks — with nothing to size or fail over.
Browser-direct uploads to your connected Drive, Box, or S3-compatible bucket, verified by Fillo.
Form.io is a form and API platform. You author forms as JSON, render them with formio.js or its React, Angular, and Vue wrappers, and its server turns each form into a REST resource with its own submissions and permissions. Run it on their cloud, or entirely inside your own network.
Fillo is narrower and hosted: one typed schema, fields rendered by your own components, responses in a workspace, nothing for you to operate.
With @formio/react, the form is a JSON definition your app points at, and the library renders it from formio.js templates:
import { Form } from "@formio/react";
export function ContactForm() {
return <Form src="https://example.form.io/contact" onSubmit={console.log} />;
}With @usefillo/react, the fields are components in your tree and the schema is typed:
import { Fillo, when, createClient } from "@usefillo/react";
const client = createClient({ key: process.env.NEXT_PUBLIC_FILLO_KEY });
export function ContactForm() {
return (
<Fillo.Form id="contact" title="Contact us" client={client}>
<Fillo.Email id="email" label="Work email" required />
<Fillo.LongText
id="details"
label="What do you need?"
visibleIf={when("email").answered()}
/>
</Fillo.Form>
);
}A Fillo condition is a record — field ID, operator, value — not an expression. The same rule decides what renders and what Fillo's server accepts on submit, and there is no expression string for the renderer to evaluate.
| Decision | Fillo | Form.io |
|---|---|---|
| What you run | Nothing | Their cloud, or self-hosted: a MongoDB-compatible database, container, load balancer, availability design |
| Schema | Typed defineForm() or JSX in your repo; conditions are data | JSON definition stored as a server resource; logic can include JavaScript |
| Rendering | Your React components, a DOM renderer, or a fillo-form custom element | formio.js renderer with React, Angular, and Vue wrappers |
| Builder | Hosted editor for your team | Embeddable builder you can ship to your own users |
| File uploads | Browser-direct to Google Drive, Box, or S3-compatible storage you connect | Storage providers configured per project |
| Working with responses | Grid, versions, CSV, Management API, signed webhooks | Submission resources with role-based access |
Form.io's enterprise self-hosting page is the source for the database requirement, and lists Atlas, DocumentDB, Cosmos DB, and Mongo Enterprise Advanced as the options. Three long-running formio.js issues are worth reading before committing the renderer to a component app: shadow DOM support has been open since 2019, and in April 2026 a maintainer noted there is still no explicit support; JavaScript conditionals need unsafe-eval in the page's CSP, with disabling evaluations or the protected evaluator as the documented alternatives; and a typing gap on the base component class, open since January 2024, has as any or @ts-ignore as its workaround. All four checked on August 6, 2026.
Fillo does not replace what Form.io is best at: no embeddable builder for your end users, no offline capture, no PDF generation, no per-form REST resource with its own permission rules. If one of those is why Form.io is in the stack, moving removes capability rather than plumbing.
Fillo also starts a new response history. Submissions in your database stay yours, but cannot be imported as Fillo responses with schema versions and delivery state.
| Existing Form.io setup | Expected work |
|---|---|
A JSON form rendered by @formio/react | Express the fields as a Fillo schema and render with @usefillo/react or the DOM renderer |
| JavaScript conditionals and custom validators | Map them to declarative conditions and field rules; dynamic behavior moves into your own code |
| An embedded builder your users author in | No equivalent — decide whether those users still need to author forms |
| Submission resources with role-based access | Responses live in the Fillo workspace; reads go through the Management API and webhooks |
Open the editor, or define one form in code with defineForm() and sync it from the repo. Render it on a real route before moving the rest.
Updated