Resource
Declare an entity, its CRUD views and its routes. Its Zod schema comes from defineResources.
A <Resource> declares one entity of your admin. Its name is the registry key
and the route base (/<name>/*), and each view prop (list, edit, create,
show) both registers a route and flags the capability that drives menus and
action buttons.
Declaring resources
import { Resource } from "@chelone/react";
import { PostCreate, PostEdit, PostList, PostShow } from "./posts";
<Resource
name="posts"
list={PostList}
edit={PostEdit}
create={PostCreate}
show={PostShow}
/>;This registers four routes — /posts, /posts/create, /posts/:id,
/posts/:id/show — and tells the sidebar the resource has a list. Because the
resource also has edit and show views, the list rows link to them.
Point at a Zod schema
The other half of a resource is its schema. <Resource> has no schema prop:
the schema comes from defineResources, keyed by the resource name. Hand the
registry to <Admin resources={…}>. Because the resource carries a schema, its
Create/Edit forms validate against it automatically — no per-input validate
props:
import { defineResources } from "@chelone/core";
import { z } from "zod";
export const postSchema = z.object({
id: z.number().optional(),
title: z.string().min(1, "Title is required").max(120),
body: z.string().min(1),
author_id: z.number({ message: "Pick an author" }),
published: z.boolean().optional(),
});
export const resources = defineResources({
posts: { schema: postSchema },
});import { Admin, Resource } from "@chelone/react";
import { dataProvider, queryClient } from "./providers";
import { PostEdit, PostList } from "./posts";
import { resources } from "./resources";
export function App() {
return (
<Admin
dataProvider={dataProvider}
queryClient={queryClient}
resources={resources}
>
<Resource name="posts" list={PostList} edit={PostEdit} />
</Admin>
);
}The schema is the single source of truth: it types the records and drives form validation.
To get List, Edit and Show pages from the same schema, use the guessers.
Labels and menus
recordRepresentation controls how a record names itself (a field name, a
function, or a node) — used in titles and breadcrumbs. icon sets the menu icon,
and options.group buckets the resource under a sidebar group.
import { Resource } from "@chelone/react";
import { FileText } from "lucide-react";
import { PostList } from "./posts";
<Resource
name="posts"
list={PostList}
icon={FileText}
recordRepresentation="title"
options={{ group: "Content" }}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Extra nested routes appended after the CRUD routes. |
| create | ResourceViewChild | — | The create view. Presence sets `hasCreate`. |
| edit | ResourceViewChild | — | The edit view. Presence sets `hasEdit`. |
| hasCreate | boolean | — | |
| hasEdit | boolean | — | |
| hasList | boolean | — | Explicit route-flag overrides, OR-ed with component presence (e.g. a resource whose list view is registered elsewhere but should still show a "create" affordance). |
| hasShow | boolean | — | |
| icon | unknown | — | The menu / breadcrumb icon (a React component). |
| list | ResourceViewChild | — | The list view (component or element). Presence sets `hasList`. |
| name | string | — | The resource name — the registry key and the route base (`<name>/*`). |
| options | Record<string, unknown> | — | Open extension bag — the shadcn menu reads `options.group` (D9). |
| recordRepresentation | unknown | — | How a record renders as a label (string field name, function, or node). |
| show | ResourceViewChild | — | The show view. Presence sets `hasShow`. |
Tips
- A view prop's presence sets the capability flag (
listpresent →hasList); override with the explicithasList/hasCreate/… props when a view lives elsewhere. childrenon a<Resource>append extra nested routes after the CRUD routes.