A production-ready, Cloudflare-native service admin template built with Payload CMS v3, Next.js (App Router), Cloudflare D1 (SQLite), and Cloudflare R2 object storage.
Designed as an extensible starter kit for building custom back-office portals, internal tools, and administrative dashboards on the Cloudflare serverless edge.
- Framework: Payload CMS v3 (Code-first, type-safe headless CMS & admin engine)
- Frontend / SSR: Next.js (App Router, React 19)
- Runtime: Cloudflare Workers via @opennextjs/cloudflare
- Database: Cloudflare D1 (Serverless distributed SQLite with
@payloadcms/db-d1-sqlite) - Storage: Cloudflare R2 (S3-compatible asset storage via
@payloadcms/storage-r2) - Testing: Vitest & Playwright
Note: Deploying Next.js via OpenNext on Cloudflare Workers currently requires the Cloudflare Workers Paid plan due to worker bundle size requirements (>3MB).
- Node.js
>= 24.15.0 - pnpm
>= 9.x - Wrangler CLI
git clone https://github.com/AIKitLLC/cloudflare-admin-template.git my-service-admin
cd my-service-admin
pnpm installAuthenticate Wrangler with your Cloudflare account:
pnpm wrangler loginCopy the example environment file:
cp .env.example .envGenerate a random secret for PAYLOAD_SECRET:
openssl rand -hex 32Start the local development server (Wrangler will emulate D1 and R2 locally):
pnpm devOpen http://localhost:3000/admin to access the admin panel and create your initial administrator account.
├── src/
│ ├── app/ # Next.js App Router (Admin & API endpoints)
│ ├── collections/ # Payload Collection definitions
│ │ ├── Users.ts # User authentication collection
│ │ └── Media.ts # Asset upload collection (R2)
│ ├── migrations/ # D1 database migrations
│ ├── payload.config.ts # Main Payload CMS configuration & bindings
│ └── payload-types.ts # Auto-generated TypeScript types
├── tests/ # Integration and E2E test suites
├── wrangler.jsonc # Cloudflare Workers, D1 & R2 configuration
├── open-next.config.ts # OpenNext build adapter configuration
└── package.json
Define your collections using TypeScript code in src/collections/:
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
slug: 'customers',
admin: {
useAsTitle: 'name',
},
access: {
read: ({ req: { user } }) => Boolean(user),
},
fields: [
{ name: 'name', type: 'text', required: true },
{ name: 'email', type: 'email', required: true, unique: true },
{ name: 'status', type: 'select', options: ['active', 'suspended'], defaultValue: 'active' },
],
}Add your collection to src/payload.config.ts:
import { Customers } from './collections/Customers'
export default buildConfig({
collections: [Users, Media, Customers],
// ...
})Generate updated TypeScript types:
pnpm generate:typesCreate your D1 Database:
pnpm wrangler d1 create <YOUR_DATABASE_NAME>Create your R2 Bucket:
pnpm wrangler r2 bucket create <YOUR_BUCKET_NAME>Update wrangler.jsonc with your D1 database_id and R2 bucket name.
pnpm payload migrate:create
pnpm run deploy# Run unit & integration tests
pnpm test:int
# Run end-to-end Playwright tests
pnpm test:e2eMIT © AIKit LLC