@everystack/admin
v0.2.0
Published
Declarative admin dashboard framework for PostgREST APIs
Readme
@everystack/admin
Declarative admin dashboard framework for PostgREST APIs. Configuration-driven CRUD screens, dashboard widgets, image field renderers, and data hooks — all wired to @everystack/api.
Install
pnpm add @everystack/admin @everystack/api @everystack/ui react react-nativeQuick Start
Define your admin configuration and render with AdminRoot:
import { AdminRoot, EverystackAdapter } from '@everystack/admin';
import { createClient } from '@everystack/api/client';
import type { AdminConfig } from '@everystack/admin';
const client = createClient({ baseUrl: '/api', getToken: () => token });
const adapter = new EverystackAdapter({ client });
const config: AdminConfig = {
resources: {
users: {
label: 'Users',
fields: {
id: { type: 'text', label: 'ID' },
name: { type: 'text', label: 'Name' },
email: { type: 'text', label: 'Email' },
avatar: { type: 'image', label: 'Avatar' },
createdAt: { type: 'datetime', label: 'Created' },
},
listFields: ['name', 'email', 'createdAt'],
searchFields: ['name', 'email'],
},
posts: {
label: 'Posts',
fields: {
id: { type: 'text', label: 'ID' },
title: { type: 'text', label: 'Title' },
content: { type: 'textarea', label: 'Content' },
status: { type: 'select', label: 'Status', options: ['draft', 'published'] },
},
},
},
menu: [
{ label: 'Users', resource: 'users', icon: 'users' },
{ label: 'Posts', resource: 'posts', icon: 'file-text' },
],
};
function AdminApp() {
return <AdminRoot config={config} adapter={adapter} />;
}Entry Points
| Import | Description |
|--------|-------------|
| @everystack/admin | Config types, hooks, screens, adapter, components |
| @everystack/admin/presets/twitter | Pre-built Twitter clone admin preset |
Admin Configuration
Field Types
| Type | Description |
|------|-------------|
| text | Single-line text input |
| textarea | Multi-line text |
| number | Numeric input |
| boolean | Toggle switch |
| select | Dropdown with options array |
| datetime | Date/time display |
| image | Image upload with ImageFieldInput |
| avatar | Avatar upload with AvatarFieldInput |
| json | JSON display |
| relation | Foreign key reference |
Resource Config
interface ResourceConfig {
label: string;
fields: Record<string, FieldDefinition>;
listFields?: string[]; // Columns shown in list view
searchFields?: string[]; // Fields searchable via text input
defaultSort?: string; // Default ordering (e.g., 'createdAt.desc')
dashboard?: ResourceDashboardConfig; // Per-resource analytics
}Data Adapter
The EverystackAdapter bridges admin hooks to the @everystack/api client:
import { EverystackAdapter } from '@everystack/admin';
import { createClient } from '@everystack/api/client';
const client = createClient({ baseUrl: '/api', getToken: () => token });
const adapter = new EverystackAdapter({
client,
// Optional: custom fetch for RPC calls
rpcFetch: async (name, body) => { /* ... */ },
});The adapter implements the DataAdapter interface:
interface DataAdapter {
list(resource: string, params: ListParams): Promise<ListResult>;
getOne(resource: string, id: string): Promise<DataRecord>;
create(resource: string, data: DataRecord): Promise<DataRecord>;
update(resource: string, id: string, data: DataRecord): Promise<DataRecord>;
delete(resource: string, id: string): Promise<void>;
aggregate(resource: string, params: AggregateParams): Promise<AggregateResult>;
rpc(name: string, body: unknown): Promise<unknown>;
subscribe?(resource: string, callback: (event: SubscriptionEvent) => void): () => void;
}Hooks
useAdminData
CRUD data hook for any resource:
const { data, total, isLoading, error, refetch } = useAdminData('posts', {
page: 1,
perPage: 20,
sort: 'createdAt.desc',
filters: { status: 'eq.published' },
});useAggregates
Server-side aggregation hooks:
const { data: count } = useCount('posts');
const { data: grouped } = useGroupedCount('posts', 'status');
const { data: stats } = useStats('posts', 'views', ['sum', 'avg', 'max']);useRpc
Call server-side RPC functions:
const { data, isLoading, call } = useRpc('dashboard-stats', {
autoFetch: true,
body: { period: '7d' },
});useStorageConfig
Access storage configuration for image uploads:
const { uploadUrl, cdnUrl } = useStorageConfig();Screens
Built-in screens that render from config:
| Component | Description |
|-----------|-------------|
| AdminLayout | Navigation sidebar + content area |
| AdminDashboard | Overview with stat cards |
| ResourceList | Paginated table with filters |
| ResourceShow | Read-only record detail |
| ResourceCreate | Create form |
| ResourceEdit | Edit form |
| ResourceDashboard | Per-resource analytics |
Manual Screen Usage
import { AdminLayout, ResourceList, ResourceEdit } from '@everystack/admin';
// In your router:
<AdminLayout config={config}>
<ResourceList resource="posts" config={config.resources.posts} />
</AdminLayout>Image Fields
Image upload field renderers for use in create/edit forms:
import { ImageFieldInput, AvatarFieldInput, ImageDisplay } from '@everystack/admin';
// In a form
<ImageFieldInput
value={formData.coverImage}
onChange={(key) => setFormData({ ...formData, coverImage: key })}
storageConfig={{ uploadUrl: '/api/storage', cdnUrl: 'https://cdn.example.com' }}
/>
<AvatarFieldInput
value={formData.avatar}
onChange={(key) => setFormData({ ...formData, avatar: key })}
storageConfig={storageConfig}
/>
// Read-only display
<ImageDisplay value={record.coverImage} cdnUrl="https://cdn.example.com" />Dashboard Widgets
SVG-based chart components for analytics:
import { StatDials, DimensionBars, TimeHistogram, EventTable } from '@everystack/admin';
<StatDials data={[{ label: 'Users', value: 1234, trend: 12 }]} />
<DimensionBars data={[{ label: 'iOS', value: 60 }, { label: 'Android', value: 40 }]} />
<TimeHistogram data={timeSeriesData} />
<EventTable columns={columns} data={events} />Presets
Pre-built admin configurations for common schemas:
import { twitterPreset } from '@everystack/admin/presets/twitter';
const config = twitterPreset({
// Override any resource config
resources: {
posts: { label: 'Tweets' },
},
});Utilities
import { toCamelCase, toSnakeCase, resolveImageUrl, uploadFile } from '@everystack/admin';
// Naming conversion
toCamelCase('created_at'); // 'createdAt'
toSnakeCase('createdAt'); // 'created_at'
// Image URL resolution
const url = resolveImageUrl('uploads/abc.jpg', { cdnUrl: 'https://cdn.example.com' });
// File upload
const result = await uploadFile(file, {
uploadUrl: '/api/storage/presign',
getToken: () => token,
});Peer Dependencies
| Package | Version |
|---------|---------|
| @everystack/api | >=0.1.0 |
| @everystack/ui | >=0.1.0 |
| react | >=18.0.0 |
| react-native | >=0.72.0 |
Part of everystack — a self-hosted application stack for Expo apps on AWS.
License
MIT
