final-form: Framework-Agnostic Form State with Opt-In Subscriptions
🏁 Framework agnostic, high performance, subscription-based form state management
At a glance
- What is it?
- final-form is a zero-dependency TypeScript library that keeps form state outside your UI framework and notifies only the subscribers that asked for a given slice of state. It suits teams that need validation and field state to behave the same way in React, vanilla JS, or anything else.
- Who is it for?
- Adopt final-form if you maintain forms in more than one rendering layer, or if re-render volume is the constraint you are fighting, because the subscription API lets a component listen only to the field it draws. Do not adopt it if you want a batteries-included form component with inputs and layout; the README points to companion libraries for that, and the core package ships state only.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 130 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem final-form solves, and who it is for
Most form libraries are written against one rendering layer. The state, the validation timing, and the re-render policy all live inside components, so moving the same form to a different layer means rewriting the logic. final-form separates that: the package description calls it "Framework agnostic, high performance, subscription-based form state management", and the README lists framework agnosticism and zero dependencies as two of its four headline claims. The remaining two are opt-in subscriptions and a 5.1k gzipped bundle, both stated in the README.
The intended user is a team building forms where the state machine matters more than the markup. If your fields need to know about each other (a confirm-password field watching another, a submit button watching overall validity), that coordination has to live somewhere. final-form puts it in a store you create yourself, outside any component tree.
It is not a component library. The README points to separate pages for getting started, philosophy, examples, API, and companion libraries, which tells you the core package is deliberately narrow. If you want a drop-in field component, the core package is not that.
How the subscription model actually works
The mechanism is a form store plus a subscription registry. You create the store, register fields with it, and any observer can subscribe to a named slice of state. The README's fourth claim is the operative one: "Opt-in subscriptions - only update on the state you need!" A component that draws one input can subscribe to that input's value and error, and it will not be notified when an unrelated field changes.
That is the whole performance argument, and it is a real one for large forms. In a naive controlled-form setup, typing in field 40 of 60 re-renders everything that reads the form object. Here the notification set is whatever subscribed to the affected slice, so the cost of a keystroke scales with the number of interested observers rather than the size of the form.
The repository layout supports reading this as a state-first design. src/ holds the implementation, examples/ contains async-registration.js, react-useSyncExternalStore.js, a react/ directory, and a vanilla/ directory, and examples/readme.md sits alongside them. The presence of a vanilla example next to a React one is the clearest evidence that the store is meant to be usable without a rendering framework at all.
One consequence worth stating plainly: subscriptions are opt-in, which means they are also opt-out by omission. If an observer subscribes too broadly, you get the re-render behaviour you were trying to avoid, and nothing in the library will warn you. The subscription keys are a design decision you own.
Installing final-form and wiring a first field
The package is published on npm as final-form; the README links to the npm package page and the badge in the README tracks the version there. The README gives no install command of its own, so follow the package page it links to.
The package.json declares "main": "dist/final-form.cjs.js" and "module": "dist/final-form.es.js", so bundlers that read the module field get the ES build and Node-style consumers get CommonJS. TypeScript users get types from "typings": "dist/index.d.ts". Only the dist directory is published, per the files array, so you cannot import from src/ in an installed copy.
The README does not reproduce the API inline. It routes readers to final-form.org/docs/final-form/getting-started for the first steps and to final-form.org/docs/final-form/api for the option list, so check those pages before relying on any particular option key.
The pattern the README's claims imply is: create a form, register a field with it, then subscribe an observer to the slice that field needs. The examples/ directory is where to look next, specifically examples/vanilla/ if you are not using React, and examples/react-useSyncExternalStore.js if you are and want to see how the store bridges into React's rendering model.
Where final-form is the wrong choice
The core package has no UI. That is a deliberate boundary, but it means the first hour with final-form is spent on plumbing rather than on a form that renders. If your requirement is a working login screen today, a component library that ships inputs, labels, and error rendering will get you there faster, and final-form will feel like you are building the library before you build the screen.
The subscription model also puts a correctness burden on you. Because updates are opt-in, a component that forgets to subscribe to an error slice will simply never show that error, with no runtime complaint. This is the same class of bug as a missing dependency array in a hook, and it is equally invisible until a user hits it.
The README does not document a rollback or migration path between major versions, and the release history shows a v5.0.0 line that followed pre-releases (v5.0.0-3 in May 2025, then v5.0.0 in June 2025, then v5.0.1 in May 2026). If you are pinned to an older major, the README gives you no upgrade guide; the docs site is the only place that might, and the README does not point at one for version migration.
Finally, if your forms are small (three or four fields, no cross-field logic), the subscription machinery buys you little. The 5.1k gzipped figure the README quotes is small, but it is not zero, and the conceptual overhead of a store plus observers is real.
How final-form differs from React Hook Form and Formik
The closest comparison is React Hook Form, and the difference is where state lives. React Hook Form reads values from the DOM through refs and keeps most state out of React entirely, so it is fast for exactly the reason final-form is fast, but it is built around React's ref model and its API assumes React. final-form keeps an explicit store that any layer can drive, which is why examples/vanilla/ exists in this repository and why the README can claim framework agnosticism without qualification.
Formik is the other obvious reference point, and it takes the opposite approach on updates: it is a React component that holds form state and re-renders its subtree, which is simple to reason about and heavier as forms grow. final-form's subscription registry is the direct answer to that re-render cost.
The trade is abstraction. React Hook Form and Formik both give you components and hooks you can drop in. final-form gives you a store and expects you to connect it, which is more work up front and more control afterward. If your team has never written an observer against a store, budget for that learning curve.
Maintenance status, licence, and upgrade cost
The repository is not archived, and the last push was on 2026-05-30. The most recent release listed is v5.0.1 on 2026-05-05, roughly three weeks before that push. The release cadence visible in the release list is sparse: v5.0.0-3 in May 2025, v5.0.0 in June 2025, then v5.0.1 in May 2026. That is a stable line rather than a fast-moving one, which cuts both ways: fewer breaking changes to absorb, and less assurance that an issue you file will be addressed quickly.
Licensing has an inconsistency worth checking before you ship. The package.json declares "license": "MIT", and the repository contains a LICENSE file at the top level. The repository metadata reported for this project says NOASSERTION, which usually means an automated classifier could not match the file to a known licence text. The package manifest is the more specific of the two statements, but if your legal review requires certainty, read the LICENSE file itself rather than either summary. Nothing here is legal advice.
The upgrade cost is concentrated in major versions. Between v5.0.0-3 and v5.0.0 there was a pre-release cycle, which implies the maintainers used pre-releases for the breaking change. The README does not describe a codemod, a migration guide, or a deprecation policy, so a major upgrade means reading the changelog and the API page yourself. The dependency footprint is the one cost you will not pay: the README states zero dependencies, and package.json confirms the listed packages are all devDependencies.
Editorial conclusion
Adopt final-form if you maintain forms in more than one rendering layer, or if re-render volume is the constraint you are fighting, because the subscription API lets a component listen only to the field it draws. Do not adopt it if you want a batteries-included form component with inputs and layout; the README points to companion libraries for that, and the core package ships state only. Before committing, verify the subscription keys your components actually need against the API page at final-form.org, and confirm the package.json license field, which reads MIT even though the repository metadata reports NOASSERTION.
Frequently asked questions
What is final-form?
It is a framework-agnostic form state management library written in TypeScript and published on npm as final-form. The README describes it as subscription-based, with zero dependencies and opt-in subscriptions so observers update only on the state they need.
How do I install final-form from npm?
The README links to the final-form page on npm and gives no install command of its own. The published package exposes dist/final-form.cjs.js as main, dist/final-form.es.js as module, and dist/index.d.ts for typings.
Does final-form work without React?
Yes. The README claims framework agnosticism, and the repository includes a vanilla example directory alongside a React one, which indicates the store is meant to be driven without a rendering framework.
What licence does final-form use?
The package.json declares MIT and the repository contains a LICENSE file, while the repository metadata reports NOASSERTION. Read the LICENSE file directly if you need certainty for legal review.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/final-form-final-form)