What is Statix CMS?
Statix CMS is a free, lightweight content management system that uses GitHub as its content database, Cloudflare R2 for media storage, and Turso for user/auth data.
Unlike traditional CMS packages, Statix CMS is not installed as a dependency. When you run npx create-statix-cms, you get a complete, standalone Next.js application that you fully own and control.
How It Works
Statix CMS uses a unique architecture that separates concerns across three services.
GitHub
Content stored as JSON files. Every save creates a Git commit with full version history and metadata.
Cloudflare R2
S3-compatible media and image storage served via public URL. Organize into folders, track references.
Turso
Serverless SQLite for users, sessions, audit logs, and invitations. Edge-compatible session validation.
Authentication — Better Auth
- Email OTP via Resend
- GitHub OAuth (optional)
- Google OAuth (optional)
Quick Start
Create a new project
Scaffold your CMS with a single command.
| npx create-statix-cms my-cms |
| cd my-cms |
Set up required services
You need GitHub (content storage), Turso (user database), and Resend (email OTP). All have free tiers. Optional: Cloudflare R2, GitHub OAuth, Google OAuth.
Configure environment
Fill in .env with your credentials.
| GITHUB_TOKEN=ghp_xxxxxxxxxxxx |
| GITHUB_OWNER=your-username |
| GITHUB_REPO=my-cms-content |
| BETTER_AUTH_SECRET=your-secret-here |
| BETTER_AUTH_URL=http://localhost:3000 |
| TURSO_DATABASE_URL=libsql://your-db.turso.io |
| TURSO_AUTH_TOKEN=your-turso-token |
| RESEND_API_KEY=re_xxxxxxxxxxxx |
| RESEND_FROM_EMAIL=cms@yourdomain.com |
Start the dev server
| npm run dev |
| # or |
| bun run dev |
Create your admin account
Sign in with OTP, then promote yourself to Owner.
| # Add to .env: |
| INITIAL_ADMIN_EMAIL=your@email.com |
| # Then run: |
| npm run seed:admin |
Configure your content
Edit src/statix.config.ts to define your collections and fields.
Prerequisites
GitHub Personal Access Token
RequiredGo to github.com/settings/tokens, generate a classic token with repo scope. Create a separate repository for content.
Turso Database
RequiredSign up at turso.tech. Create a database and get the connection URL and auth token.
| turso db create my-cms |
| turso db show my-cms --url |
| turso db tokens create my-cms |
Resend (Email OTP)
RequiredSign up at resend.com, create an API key, and verify your sender domain.
Cloudflare R2
OptionalCreate a bucket, generate API token with Read & Write permissions, enable public access.
GitHub OAuth
OptionalCreate OAuth App at github.com/settings/developers. Callback: {BETTER_AUTH_URL}/api/auth/callback/github
Google OAuth
OptionalCreate OAuth 2.0 Client at Google Cloud Console. Redirect URI: {BETTER_AUTH_URL}/api/auth/callback/google
Environment Variables
All variables are validated at startup using Zod. The app will not start if required variables are missing.
| Variable | Required | Description |
|---|---|---|
| GITHUB_TOKEN | required | GitHub Personal Access Token with repo scope |
| GITHUB_OWNER | required | GitHub username or organization |
| GITHUB_REPO | required | Repository name for content storage |
| GITHUB_BRANCH | optional | Branch to use (default: main) |
| BETTER_AUTH_SECRET | required | Auth secret — generate with openssl rand -base64 32 |
| BETTER_AUTH_URL | required | Full site URL (e.g., http://localhost:3000) |
| TURSO_DATABASE_URL | required | Turso connection URL (libsql://...turso.io) |
| TURSO_AUTH_TOKEN | required | Turso authentication token |
| RESEND_API_KEY | required | Resend API key for sending OTP emails |
| RESEND_FROM_EMAIL | required | Verified sender email address |
| GITHUB_CLIENT_ID | optional | GitHub OAuth app Client ID |
| GITHUB_CLIENT_SECRET | optional | GitHub OAuth app Client Secret |
| GOOGLE_CLIENT_ID | optional | Google OAuth Client ID |
| GOOGLE_CLIENT_SECRET | optional | Google OAuth Client Secret |
| R2_ACCOUNT_ID | optional | Cloudflare account ID |
| R2_ACCESS_KEY_ID | optional | R2 API token access key |
| R2_SECRET_ACCESS_KEY | optional | R2 API token secret key |
| R2_BUCKET_NAME | optional | R2 bucket name |
| NEXT_PUBLIC_MEDIA_BASE_URL | optional | Public URL for R2 bucket |
| INITIAL_ADMIN_EMAIL | optional | Email to promote to Owner via seed:admin |
Configuration
All content modeling is done in src/statix.config.ts — the single source of truth for your CMS structure.
Base Configuration
| import { StatixConfig } from "@/statix/types"; |
| export const statixConfig: StatixConfig = { |
| github: { |
| owner: process.env.GITHUB_OWNER || "", |
| repo: process.env.GITHUB_REPO || "", |
| branch: process.env.GITHUB_BRANCH || "main", |
| }, |
| mediaFolder: "uploads", |
| i18n: { |
| locales: ["en", "tr"], |
| defaultLocale: "en", |
| }, |
| roles: [], |
| collections: [], |
| }; |
Singleton Example
Single pages with unique content (e.g., Home, About, Contact):
| { |
| slug: "home", |
| label: "Home Page", |
| type: "singleton", |
| path: "content/home", |
| icon: "Home", |
| fields: [ |
| { name: "title", label: "Page Title", type: "text", required: true, localized: true }, |
| { name: "heroImage", label: "Hero Image", type: "image" }, |
| { name: "ctaText", label: "Button Text", type: "text" }, |
| ], |
| } |
Collection Example
Repeatable items (e.g., Blog Posts, Team Members):
| { |
| slug: "blog", |
| label: "Blog Posts", |
| path: "content/blog", |
| icon: "FileText", |
| titleField: "title", |
| fields: [ |
| { name: "title", label: "Title", type: "text", required: true, localized: true }, |
| { name: "date", label: "Publish Date", type: "date", required: true }, |
| { name: "featuredImage", label: "Featured Image", type: "image" }, |
| { name: "content", label: "Content", type: "blocks", localized: true }, |
| ], |
| } |
i18n Configuration
| i18n: { |
| locales: ["en", "tr", "de", "fr"], |
| defaultLocale: "en", |
| } |
Content Modeling
12 field types available for building your content schema.
text
placeholder, localized
Single-line text input
textarea
rows, placeholder, localized
Multi-line text input
richtext
placeholder, localized
WYSIWYG rich text editor
image
Image picker from media library
file
File upload
number
Numeric input
select
options: [{label, value}]
Dropdown selection
date
Date picker
checkbox
Checkbox toggle
switch
Toggle switch
list
fields: Field[]
Repeatable group of fields
blocks
blocks: [{type, label, fields}]
Drag-and-drop content blocks
Roles & Permissions
Three system roles with granular permissions, plus support for custom roles.
| Role | Manage Users | View Monitor | Manage Media | Manage Trash | Content |
|---|---|---|---|---|---|
| Owner | ✓ | ✓ | ✓ | ✓ | Full |
| Admin | ✓ | ✓ | ✓ | ✓ | Full |
| Editor | ✗ | ✗ | ✗ | ✗ | View, Create, Edit |
Custom Roles
Define custom roles in statix.config.ts with per-collection permissions: canView, canCreate, canEdit, canDelete, canPublish.
Admin Panel
Dashboard
/admin
Collection statistics, localization progress, recent activity feed.
Collections
/admin/[slug]
List view with search, pagination, and status indicators.
Content Editor
/admin/[slug]/[id]
Field-based form, block editor, locale switching, draft/publish.
Media Library
/admin/media
Upload, browse, organize, search, move, and delete media files.
Users
/admin/users
Create/invite users, assign roles, ban/unban, per-user audit logs.
Trash
/admin/trash
Browse soft-deleted content and media. Restore or permanently delete.
Monitor
/admin/monitor
Audit logs, activity charts, commit timeline, GitHub API rate limit.
API Reference
All API routes (except /api/auth and /api/media/serve) require authentication. Protected by CSRF validation and rate limiting (100 req/min per IP).
Authentication
| Method | Endpoint | Description |
|---|---|---|
| * | /api/auth/[...all] | Better Auth handler (login, logout, OTP, OAuth) |
Content
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/content/[collectionSlug]/[id] | Get content item |
| POST | /api/content/[collectionSlug]/[id] | Create or update content |
| DELETE | /api/delete/[collectionSlug]/[id] | Soft-delete content |
| GET | /api/collections/[slug] | Get collection items list |
Media
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/upload | Upload a file |
| GET | /api/media/list | List media files |
| POST | /api/media/delete | Soft-delete media |
| POST | /api/media/move | Move media between folders |
| GET | /api/media/references | Find content using a media file |
| GET | /api/media/stats | Storage statistics |
| GET | /api/media/serve/[...path] | Serve media files (public) |
Trash
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/trash/list | List soft-deleted items |
| POST | /api/trash/restore | Restore item from trash |
| DELETE | /api/trash/delete | Permanently delete item |
| GET | /api/trash/media/[filename] | Get trashed media metadata |
Admin
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/users | List users |
| POST | /api/admin/users | Create or update user |
| POST | /api/admin/users/[id]/avatar | Upload user avatar |
| GET | /api/admin/activity | Activity feed |
| GET | /api/admin/audit | Audit logs |
Tech Stack
| Category | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.x |
| UI | React | 19.x |
| Styling | Tailwind CSS | 4.x |
| Language | TypeScript | 5.x |
| Auth | Better Auth | 1.x |
| Database ORM | Drizzle ORM | 0.45.x |
| Database | Turso (libsql) | — |
| GitHub API | Octokit | 5.x |
| Object Storage | AWS SDK S3 (Cloudflare R2) | 3.x |
| State | Zustand | 5.x |
| Data Fetching | TanStack React Query | 5.x |
| Forms | React Hook Form | 7.x |
| Validation | Zod | 4.x |
| Rich Text | ProseKit | 0.19.x |
| Drag & Drop | dnd-kit | 6.x |
| Charts | Recharts | 2.x |
| Resend | 6.x | |
| Toasts | Sonner | 2.x |
| Icons | Tabler Icons React | 3.x |
| Components | shadcn/ui + Base UI | — |
Deployment
Deploy to Vercel (recommended) or any platform that supports Next.js.
Push your project to GitHub
Import the repository in Vercel Dashboard
Set all environment variables
Deploy
After Deployment
- Update BETTER_AUTH_URL to your production domain
- Update OAuth callback URLs to production domain
- Generate a new BETTER_AUTH_SECRET for production
Other Platforms
Pre-launch Checklist
- All required environment variables are set
- BETTER_AUTH_SECRET generated fresh for production
- BETTER_AUTH_URL set to production domain
- OAuth callback URLs updated to production domain
- Turso database created for production
- R2 bucket configured with public access (if using media)
- First admin user seeded via npm run seed:admin
Project Structure
| src/ |
| ├── app/ |
| │ ├── (statix)/ |
| │ │ ├── admin/ |
| │ │ │ ├── page.tsx # Dashboard |
| │ │ │ ├── [collectionSlug]/ # Collection list + editor |
| │ │ │ ├── media/ # Media library |
| │ │ │ ├── users/ # User management |
| │ │ │ ├── trash/ # Trash management |
| │ │ │ └── monitor/ # Audit & monitoring |
| │ │ ├── auth/ # Sign-in, invite acceptance |
| │ │ └── api/ # API routes (20 endpoints) |
| │ ├── layout.tsx # Root layout |
| │ └── page.tsx # Public home page |
| │ |
| ├── statix/ |
| │ ├── components/ |
| │ │ ├── ui/ # shadcn/ui base components |
| │ │ ├── editor/ # Rich text & block editor |
| │ │ ├── fields/ # Field type renderers |
| │ │ ├── collections/ # Collection list views |
| │ │ ├── dashboard/ # Dashboard widgets |
| │ │ ├── media/ # Media library UI |
| │ │ ├── users/ # User management UI |
| │ │ ├── trash/ # Trash UI |
| │ │ ├── monitor/ # Monitoring charts |
| │ │ ├── activity/ # Activity feed |
| │ │ ├── layout/ # Navigation, breadcrumbs |
| │ │ ├── shared/ # Reusable components |
| │ │ └── skeletons/ # Loading placeholders |
| │ ├── hooks/ # 18 custom React hooks |
| │ ├── lib/ # Core utilities |
| │ ├── stores/ # Zustand state stores |
| │ ├── types/ # TypeScript definitions |
| │ ├── db/ # Drizzle ORM schema |
| │ └── content/ # ui.json (translations) |
| │ |
| ├── statix.config.ts # Content model configuration |
| ├── middleware.ts # CSRF, rate limiting, auth |
| └── lib/ |
| └── utils.ts # General utilities |
app/(statix)/Route group for all CMS routesstatix/components/All React components, organized by featurestatix/hooks/18 custom hooks for data fetching, state, etc.statix/lib/Core utilities (GitHub API, R2, auth, rate limiting)statix.config.tsSingle source of truth for content modeling