PJPalJS

@paljs/admin

Introduction

A React admin UI package for Prisma-based applications. Provides a PrismaTable component that automatically generates full CRUD interfaces from your Prisma schema — tables, forms, filtering, sorting, pagination, and relation management.

In v9, the admin package uses React 19, Tailwind CSS 4, HeadlessUI, TanStack React Table, and DND-kit.

Installation

Peer Dependencies

code
react >= 19
react-dom >= 19
react-hook-form
@apollo/client

Quick Start

1. Generate Admin Schema

Add admin generation to your paljs.config.ts:

typescript
import { defineConfig } from '@paljs/generator/config';
 
export default defineConfig({
  generateAdmin: {
    enabled: true,
    output: './admin',
    routerType: 'app',
  },
});

Run prisma generate to create the admin schema and pages.

2. Set Up the Admin Provider

typescript
import { AdminProvider } from '@paljs/admin';
import schema from './generated/paljs/admin/schema.json';
 
export default function AdminLayout({ children }) {
  return (
    <AdminProvider
      schema={schema}
      pagesPath="/admin/"
      pageSize={10}
      pageSizeOptions={[10, 20, 50, 100]}
    >
      {children}
    </AdminProvider>
  );
}

3. Use PrismaTable

typescript
import { PrismaTable } from '@paljs/admin';
 
export default function UsersPage() {
  return <PrismaTable model="User" />;
}

Main Components

PrismaTable

The primary component. Renders a full CRUD interface for any Prisma model:

  • Data table with sorting, filtering, and pagination
  • Create modal with auto-generated form
  • Edit page with relation management
  • Delete with confirmation
  • Connect/disconnect many-to-many relations
typescript
import { PrismaTable } from '@paljs/admin';
 
<PrismaTable
  model="Post"
  // Optional: render prop for custom query access
  children={({ context, query }) => (
    <div>Total: {query.data?.findCountPost}</div>
  )}
/>

DynamicTable

Lower-level table component used by PrismaTable. Handles data fetching, pagination, and CRUD actions:

typescript
import DynamicTable from '@paljs/admin/PrismaTable/dynamicTable';
 
<DynamicTable
  model="User"
  inEdit={false}
  filter={undefined}
  parent={undefined}
  connect={undefined}
  onConnect={undefined}
  headerActions={<CustomActions />}
/>

Settings

Configuration UI for customizing admin field visibility, ordering, and behavior:

typescript
import { Settings } from '@paljs/admin';
 
<Settings />

The Settings component reads and writes to the admin schema JSON file, letting you:

  • Show/hide fields in list and edit views
  • Reorder fields
  • Set field as read-only
  • Configure field display names

Form

Auto-generated form based on Prisma model fields:

typescript
import Form from '@paljs/admin/PrismaTable/Form';
 
<Form
  model="User"
  action="create"  // or "update"
  data={{}}
  onCancel={() => {}}
  onSave={() => {}}
/>

Features

Table Features

  • Sorting — Click column headers to sort ascending/descending
  • Filtering — Filter by any field type (string, number, boolean, enum, date, relations)
  • Pagination — Configurable page size with navigation
  • Column visibility — Show/hide columns via settings

Form Features

  • Auto-generated fields — Text, number, boolean, enum, datetime inputs
  • Relation fields — Search and select related records via modal
  • Validation — Required field enforcement via react-hook-form
  • Create and edit — Same form component for both actions

Relation Management

  • One-to-one / Many-to-one — Search modal to select related record
  • Many-to-many — Sub-table with connect/disconnect buttons
  • Nested editing — Edit related records inline via tabs

Styling

The admin package uses Tailwind CSS 4 for styling. Ensure your project includes Tailwind CSS:

The components use standard Tailwind utility classes and are fully customizable.

GraphQL Integration

PrismaTable uses Apollo Client for data fetching. It expects the following GraphQL operations to be available on your server (generated by @paljs/generator):

  • findMany{Model} — List records
  • findCount{Model} — Count records for pagination
  • createOne{Model} — Create a record
  • updateOne{Model} — Update a record
  • deleteOne{Model} — Delete a record

Wrap your admin app in an ApolloProvider:

typescript
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
 
const client = new ApolloClient({
  uri: '/api/graphql',
  cache: new InMemoryCache(),
});
 
export default function App({ children }) {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

AdminProvider Props

typescript
interface ContextProps {
  schema: AdminSchema;       // Generated admin schema
  pagesPath: string;         // Base path for admin pages (e.g., "/admin/")
  pageSize: number;          // Default rows per page
  pageSizeOptions: number[]; // Page size dropdown options
  push: (path: string) => void;  // Router push function
  query: Record<string, string>; // URL query params
  onCancelCreate?: Function;     // Custom cancel handler
  onSaveCreate?: Function;       // Custom save handler
  onSaveUpdate?: Function;       // Custom update handler
  defaultOrderBy?: Record<string, any>; // Default sort per model
}

Types

TableParentRecord

Used for relation sub-tables:

typescript
interface TableParentRecord {
  name: string;        // Parent model name
  value: any;          // Parent record data
  field: string;       // Relation field name
  updateRecord?: () => Promise<any>;
}

AdminSchemaModel

Defines the admin schema for a single model:

typescript
interface AdminSchemaModel {
  id: string;          // Model name
  idField: string;     // Primary key field name
  displayFields: string[];
  fields: AdminSchemaField[];
}

Command Palette

Search for a command to run...