The endpoint isn't the whole backend.
One schema can render the form, check the answer on the server, and shape the response your team reviews.
Compare Formspree with Fillo for an in-product form: one schema renders native fields, validates on the server, and keeps uploads and responses together.
Formspree is the simpler choice when your form is already built. Fillo adds the part before submit—the fields and rules—and the workspace after it.
One schema can render the form, check the answer on the server, and shape the response your team reviews.
Start from a schema instead of wiring every input, validation rule, and submit state yourself.
app.acme.co/support/newContact support
Review the answer, its account context, files, version, and delivery state in one place.
Uploads travel browser-direct to connected storage while Fillo keeps the response record and reference.
If the form is already built and only needs somewhere to send submissions, an endpoint may be everything the team needs. Point the HTML at an endpoint or use its React hook, then keep owning the fields, state, and layout in your app.
Fillo is useful when the form still needs to be built. It renders the fields inside your app and also handles server checks, uploads, responses, and exports.
Formspree covers the submission step; Fillo covers building, validating, and managing the form around it.
Formspree's React library gives your component a useForm() submission handler. You still write the inputs:
import { useForm } from "@formspree/react";
export function ContactForm() {
const [state, handleSubmit] = useForm("your-form-id");
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Work email</label>
<input id="email" name="email" type="email" required />
<button disabled={state.submitting}>Send</button>
</form>
);
}Fillo can render the fields from JSX or a published schema:
import { Fillo, createClient } from "@usefillo/react";
import "@usefillo/react/styles.css";
const fillo = createClient({ key: import.meta.env.VITE_FILLO_KEY });
export function ContactForm() {
return (
<Fillo.Form client={fillo} id="contact" title="Contact us">
<Fillo.Email id="email" label="Work email" required />
</Fillo.Form>
);
}You can use Fillo's default styling, change it, or replace individual fields with your own components. The same field names and rules are then used in the form and on Fillo's server.
| Decision | Fillo | Formspree |
|---|---|---|
| Starting point | A schema rendered by Fillo | HTML or JSX you write, optionally starting from a template |
| React integration | Native renderer, field components, and headless hooks | useForm() and related helpers for submitting your component |
| Validation | One schema used by the client and Fillo's server | HTML/client validation plus Formspree dashboard or configuration rules |
| File uploads | Browser-direct to connected customer storage, with the reference kept on the response | Multipart uploads stored with Formspree submissions on eligible plans |
| Working with responses | Response grid, detail, context, versions, exports, and delivery state | Submissions dashboard, email notifications, routing, workflows, and integrations |
| Changing fields | Edit an editor-managed form in Fillo or publish a code-managed schema | Change the HTML or JSX in your application |
Formspree Workflows can add server-side validation, integrations, automatic field mapping, and delivery retries. The difference is that Fillo also renders the fields and uses the same rules for the form, server checks, and saved responses. Check Fillo plans and limits, Formspree's current account limits, and its Workflow documentation before estimating production usage.
Fillo is not a drop-in endpoint for existing markup. Moving means expressing the form as a Fillo schema and choosing the default, customized, or headless renderer. If the current form is stable and its Formspree workflow already works, that rewrite may have no payoff.
Fillo also starts a new response history. Export any Formspree submissions you need to retain before cutover; they cannot be imported as native Fillo responses with schema versions and delivery state.
| Existing Formspree setup | Expected work |
|---|---|
| One HTML or React form posting to Formspree | Recreate the fields as a Fillo schema, render it on the same route, and test success and error states |
| Custom validation and conditional UI | Map each rule to Fillo visibility, validation, pages, or custom field behavior and test every path |
| Notifications, routing, or integrations | Reconnect each destination as a Fillo notification, webhook, or integration and verify delivery |
| File uploads and submission history | Preserve the old files and export; connect the new file destination before switching traffic |
Read the headless form builder overview, then open the editor and rebuild one real form before moving the rest.
Updated