A Sanity-inspired, database-agnostic CMS built with SvelteKit V2 (Svelte 5)
ā ļø Early Development: Expect breaking changes and incomplete features. Not recommended for production use yet.
| Package | Description |
|---|---|
@aphex/cms-core |
Database-agnostic core engine with admin UI and API handlers |
@aphex/postgresql-adapter |
PostgreSQL implementation with Drizzle ORM |
@aphex/storage-s3 |
S3-compatible storage (R2, AWS S3, MinIO, etc.) |
@aphex/graphql-plugin |
Auto-generated GraphQL API from schemas |
@aphex/ui |
Shared shadcn-svelte component library |
@aphex/studio |
Reference implementation app |
š” Architecture deep-dive: See ARCHITECTURE.md for detailed design patterns and internals.
š” Adding UI components: Run
pnpm shadcn <component-name>to add shadcn-svelte components to@aphex/ui
nvm for version management)# Clone and install
git clone https://github.com/IcelandicIcecream/aphex.git
cd aphex
pnpm install
# Configure environment
cd apps/studio
cp .env.example .env
# Default values work for local development
cd ../..
# Start database and migrate
pnpm db:start
pnpm db:migrate
# Start dev server
pnpm dev
š Admin UI: http://localhost:5173/admin
By default, uses local filesystem. For cloud storage:
pnpm add @aphex/storage-s3
// apps/studio/src/lib/server/storage/index.ts
import { s3Storage } from '@aphex/storage-s3';
export const storageAdapter = s3Storage({
bucket: env.R2_BUCKET,
endpoint: env.R2_ENDPOINT,
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
publicUrl: env.R2_PUBLIC_URL
}).adapter;
// aphex.config.ts
import { storageAdapter } from './src/lib/server/storage';
export default createCMSConfig({
storage: storageAdapter // Pass your adapter
});
Content models live in your app as TypeScript objects:
// apps/studio/src/lib/schemaTypes/page.ts
export const page: SchemaType = {
name: 'page',
type: 'document',
title: 'Page',
fields: [
{
name: 'title',
type: 'string',
title: 'Title',
validation: (Rule) => Rule.required().max(100)
},
{
name: 'slug',
type: 'slug',
title: 'URL Slug',
source: 'title', // Auto-generate from title
validation: (Rule) => Rule.required()
},
{
name: 'content',
type: 'array',
title: 'Content Blocks',
of: [
{ type: 'textBlock' },
{ type: 'imageBlock' },
{ type: 'catalogBlock' }
]
},
{
name: 'author',
type: 'reference',
title: 'Author',
to: [{ type: 'author' }] // Reference to other documents
}
]
};
Register schemas in your config:
// aphex.config.ts
import { page, author, textBlock } from './src/lib/schemaTypes';
export default createCMSConfig({
schemaTypes: [page, author, textBlock],
// ...
});
Available field types: string, text, number, boolean, slug, image, array, object, reference
The admin UI is a responsive 3-panel layout inspired by Sanity Studio:
# Just IDs (default)
GET /api/documents/123
# Resolve first-level references
GET /api/documents/123?depth=1
# Resolve nested references
GET /api/documents/123?depth=2
Circular reference protection prevents infinite loops. Max depth: 5.
// Install plugin
import { createGraphQLPlugin } from '@aphex/graphql-plugin';
export default createCMSConfig({
plugins: [
createGraphQLPlugin({
endpoint: '/api/graphql',
enableGraphiQL: true
})
]
});
Visit /api/graphql for GraphiQL interface with auto-generated schema.
# Development
pnpm dev # Start all packages in watch mode
pnpm dev:studio # Start studio app only
pnpm dev:package # Start cms-core package only
pnpm dev:docs # Start dev server
# Building
pnpm build # Build all packages (Turborepo)
pnpm preview # Preview production build
# Database
pnpm db:start # Start PostgreSQL (Docker)
pnpm db:push # Push schema changes (dev)
pnpm db:generate # Generate migrations
pnpm db:migrate # Run migrations (prod)
pnpm db:studio # Open Drizzle Studio
# Code Quality
pnpm lint # Prettier + ESLint check
pnpm format # Format code with Prettier
pnpm check # Type-check all packages
# UI Components (shadcn-svelte ā @aphex/ui)
pnpm shadcn button # Add button component
pnpm shadcn dialog # Add dialog component
# Components shared between cms-core & studio
Batteries included with Better Auth:
curl http://localhost:5173/api/documents?docType=page \
-H "x-api-key: your-api-key-here"
Generate keys from /admin/settings.
Bring your own auth: Implement the
AuthProviderinterface to use Auth.js, Lucia, or custom solutions.
We welcome contributions! Here's how to get started:
# Clone repo
git clone https://github.com/IcelandicIcecream/aphex.git
cd aphex
pnpm install
# Start database
pnpm db:start
pnpm db:migrate
# Run dev server
pnpm dev
pnpm formatpnpm check$state, $derived, $effect)DatabaseAdapter interface in a new packageStorageAdapter interfaceCMSPlugin interfaceSee ARCHITECTURE.md for detailed extension guides.
Include:
Inspired by Sanity.io ⢠Built with SvelteKit, Drizzle ORM, Better Auth, and shadcn-svelte