Guessers

List, edit and show pages generated from a resource's Zod schema.

ListGuesser, EditGuesser and ShowGuesser build a resource's pages from its Zod schema. Use them to get a working admin before you write the views, then copy the code they print and replace them one at a time.

The examples use @chelone/react; Vue lists what differs in @chelone/vue.

Using the guessers

Pass a guesser as the view prop of a <Resource>, and register the schema with defineResources:

src/resources/index.ts
import { defineResources } from "@chelone/core";
import { z } from "zod";
 
export const postSchema = z.object({
  id: z.number().optional(),
  title: z.string().min(1).describe("Headline"),
  body: z.string(),
  published: z.boolean().optional(),
  created_at: z.string().datetime().optional(),
});
 
export const resources = defineResources({
  posts: { schema: postSchema },
});
src/App.tsx
import {
  Admin,
  EditGuesser,
  ListGuesser,
  Resource,
  ShowGuesser,
} from "@chelone/react";
import { dataProvider, queryClient } from "./providers";
import { resources } from "./resources";
 
export function App() {
  return (
    <Admin
      dataProvider={dataProvider}
      queryClient={queryClient}
      resources={resources}
    >
      <Resource
        name="posts"
        list={ListGuesser}
        edit={EditGuesser}
        show={ShowGuesser}
      />
    </Admin>
  );
}

A guesser reads the schema and never the records. The columns, inputs and rows are the same before any data loads, and a resource's pages do not change with what its API happens to return. A .describe("Headline") on a field becomes its label; without one the label comes from the field name.

EditGuesser leaves out the id field. <Edit> validates against the same schema when the form saves.

Schema type to component

describeResource in @chelone/core maps each field of the schema. Optional, nullable, default, branded and refined wrappers are unwrapped first.

Schema typeList column and show rowEdit input
z.string()text columnTextInput
z.string().email()EmailFieldTextInput
z.string().url()UrlFieldTextInput
z.string().date()DateFieldDateInput
z.string().datetime()DateField with showTimeDateTimeInput
z.number()NumberFieldNumberInput
z.boolean()BooleanFieldBooleanInput
z.date()DateFieldDateInput
z.enum([...]), z.nativeEnum(...), a union of string or number literalsSelectFieldSelectInput with the values as choices
z.array(z.string())TextArrayFieldTextArrayInput
a field listed in referencesReferenceFieldReferenceInput
an array field listed in referencesReferenceArrayFieldReferenceArrayInput

A plain z.string() column is a bare DataTable.Col, and a bare RecordField in the show page.

describeResource reads Zod 3 schemas. A Zod 4 schema describes as nothing, so the resource shows GuesserEmpty.

References

A field is a reference only when you say so. List it under references on the resource config, keyed by field name, with the target resource as the value:

src/resources/index.ts
export const resources = defineResources({
  authors: { schema: authorSchema },
  posts: {
    schema: postSchema,
    references: { author_id: "authors" },
  },
});

The guessers never read a field name for an _id suffix. author_id above is a reference because of the references entry, and a category_id with no entry is a plain number.

Fields that are left out

A field the guessers cannot map is unsupported: an object, a record, an array of objects, or any other type the table does not list. The guesser leaves it out of the page and lists it in the console, with the reason.

The code in the console

Each guesser prints the code you would write by hand to the console: the imports, then a component with one column, input or row per field. It prints once per resource, so a re-render does not repeat it. It is off in production builds, and enableLog switches it on or off:

const PostList = () => <ListGuesser enableLog={false} />;
 
<Resource name="posts" list={PostList} />;

Fields the guesser left out follow the code as Skipped: lines.

A resource with no schema

A resource that has no schema in defineResources, or an <Admin> with no resources, shows GuesserEmpty. It names the resource and tells you to register a schema with defineResources. A guesser makes no data request in this state.

Vue

@chelone/vue exports the same four components. Differences:

  • Pass the registry as the registry prop of <Admin>, not resources.
  • The printed code is a single-file component, with <script setup> and a <template>.
  • ReferenceInput takes no autocomplete child, so the printed edit code has none.
src/App.vue
<script setup lang="ts">
import {
  Admin,
  EditGuesser,
  ListGuesser,
  Resource,
  ShowGuesser,
} from "@chelone/vue";
import { dataProvider, queryClient } from "./providers";
import { resources } from "./resources";
</script>
 
<template>
  <Admin
    :data-provider="dataProvider"
    :query-client="queryClient"
    :registry="resources"
  >
    <Resource
      name="posts"
      :list="ListGuesser"
      :edit="EditGuesser"
      :show="ShowGuesser"
    />
  </Admin>
</template>

Props

ListGuesser

PropTypeDefaultDescription
actionsfalse | ReactElement<unknown, string | JSXElementConstructor<any>>—Header actions (create/export/filter buttons); nothing by default.
classNamestring—
componentElementType<any, keyof IntrinsicElements>—Element/component wrapping the children (defaults to `"div"`).
enableLogboolean—Log the equivalent hand-written list once per resource. Defaults to on outside production.
perPagenumber—Rows-per-page for the initial query.
resourcestring—Override the ambient resource (defaults to the `<Resource>` context).
titleReactNode—Header title. A string/element renders as an `<h2>`; `false` omits it.

EditGuesser

PropTypeDefaultDescription
actionsReactNode—Header actions (show/delete buttons); nothing by default.
classNamestring—
componentElementType<any, keyof IntrinsicElements>—
enableLogboolean—Log the equivalent hand-written edit page once per resource. Defaults to on outside production.
idIdentifier—Override the record id (defaults to the `:id` route param).
onSuccess(record: TRecord) => void | Promise<void>—Runs after a successful save with the SERVER record, post-reconciliation.
resourcestring—Override the ambient resource (defaults to the `<Resource>` context).
sanitizeEmptyValuesboolean—ra parity: nullify/strip empty TOP-LEVEL values before save. Default false.
schemaStandardSchemaV1<Partial<Record<string, unknown>>, unknown>—Resource schema (zod or any Standard Schema) — wired as the default form validator by the submit pipeline (T2).
titleReactNode—Header title. A string/element renders as an `<h2>`; `false` omits it.
validateFormLevelValidate<Partial<Record<string, unknown>>>—Form-level validate fn (ra contract) — consumed by the submit pipeline (T2).

ShowGuesser

PropTypeDefaultDescription
actionsReactNode—Header actions (an edit button); nothing by default.
classNamestring—
componentElementType<any, keyof IntrinsicElements>—
enableLogboolean—Log the equivalent hand-written show page once per resource. Defaults to on outside production.
idIdentifier—Override the record id (defaults to the `:id` route param).
resourcestring—Override the ambient resource (defaults to the `<Resource>` context).
titleReactNode—Header title. A string/element renders as an `<h2>`; `false` omits it.

GuesserEmpty

PropTypeDefaultDescription
messageReactNode—Replaces the default message. A string is translated, with itself as the fallback.
resourcestring—The resource named in the default message. Falls back to the resource context.
titleReactNode—Replaces the default title. A string is translated, with itself as the fallback.