The URL is the state
Filters, pagination, the chosen layout and the open item all live in the address bar. A shared link reopens exactly what the sender was looking at.
Lists, detail pages, forms and filters for every resource — declared once, never drawn by hand.
The product
The form engine underneath
One command writes a Vite and React back office around it.
$ npx react-resource-view init my-admin -b api-platform -u https://localhostThen cd my-admin && npm install && npm run dev — the example resource, listed from your API.
Before and after
A back office is the same work for every resource of your API. Write it by hand and each one costs a table, a form and its filters; declare it and each one costs about twenty lines.
Typically a few hundred lines.
import { createViewResource, layout } from "react-resource-view"
export const articles = createViewResource<Article>("articles", {
name: "Articles",
path: "/api/articles",
form: {
inputs: {
title: { label: "Title", required: true },
author: { label: "Author" },
status: { label: "Status", controller: SelectInputController },
},
},
views: {
list: {
filters: { inputs: { title: { label: "Search a title" } } },
layouts: [layout.table(), layout.cards()],
},
},
})Is it for you?
Written once
A list has no separate column definition. The fields you describe are the form the reader edits, the columns the table shows and the payload your API receives — so take the description below apart and watch both halves follow.
Live — edit the description
Fields · 4 columns
The table has no column list of its own. Every switch above moves the form and the list at the same time — because they read the same object.
Read many ways
A layout is a line in views.list.layouts, not a screen you build. Declare several and the reader picks — the filters, the permissions and the forms carry over untouched.
Create your own layout: one file — three components and a layout.custom() call. One command writes it where you want it.
$ npx react-resource-view create-view-variantForms + views
A page builder is not a third package. The blocks you stack and edit are a react-data-form form, saved as plain JSON; once published, the page is a record of a react-resource-view resource — listed, filtered and reopened in the back office like any other. The same pairing gives you a CV editor, a product sheet or a blog.
Filters, pagination, the chosen layout and the open item all live in the address bar. A shared link reopens exactly what the sender was looking at.
Neither package knows your router, your API client or your brand. Each touch point is a port with a default, injected once at startup.
A dialect holds what each backend spells differently — pages, filters, envelopes, errors — so one declared resource renders against API Platform, Spring Boot, Supabase, Strapi, FastAPI or Django, any REST API that publishes OpenAPI, or a dialect you write.
A validator per field for the browser, and a mapper that puts a 422 from the server back on the fields that caused it.
Labels go through react-mini-i18n, so your application and the libraries translate from the same place.
Table, cards, item list, columns, split, calendar and timeline — declared side by side, switched by the reader. An eighth of your own is one command and one file.
Resource, dialect, port, scope — the concepts and how they fit together
Where it comes from
I moved from Evernote to Notion in 2018, and missed it in every app I wrote.
2018
One set of records: a table, a board or a calendar, a click apart.
2018 – 2022
The view is frozen in the code: another view means another screen.
2022 → today
Describe the resource once. The reader picks the view, and the link keeps it.
The quickstart goes from zero to a list on your API in five minutes. The form package it builds on stands on its own, when all you need is a form.
Both are written and maintained by Salvador Cardona — the rest of his work is on the author’s portfolio.