A modern, open-source multi-tenant e-commerce framework. Deploy it on your own infrastructure and give merchants everything they need to run an online store — product management, order tracking, storefront rendering, custom domains, and payment processing.
Built as a TypeScript monorepo with Turborepo, tRPC, Next.js 15, Hono, Drizzle ORM, and PostgreSQL.
- Overview
- Architecture
- Tech Stack
- Project Structure
- Getting Started
- CLI Reference
- Environment Variables
- License Plans
- Telemetry
- Packages
- Apps
- Database Schema
- API Reference
- Authentication
- Multi-Tenancy
- Role-Based Access Control
- Development Commands
- Deployment
- Roadmap
- Contributing
KStack is a self-hosted e-commerce platform framework. You run it on your own domain, your own servers, and your own database. Merchants create stores through a dashboard you control.
Out of the box you get:
- Subdomain store routing (
mystore.yourdomain.com) or custom domain per store - Product and inventory management with variants
- Order management with fulfillment tracking
- Customer accounts and guest checkout
- Storefront with theme customization
- Multi-staff access with role-based permissions
- Payment processing (Paystack)
- Coupon and discount codes
- Product reviews
- Shipping rate configuration
- Contact message inbox
- AI-assisted product tools
- Legal pages (Privacy Policy, Terms, Disclaimer)
- Maintenance mode and store controls
┌─────────────────────────────────────────────────┐
│ EDGE / CDN (Cloudflare) │
│ SSL termination, wildcard subdomain routing │
└──────────────────────┬──────────────────────────┘
│
┌──────────────────────▼──────────────────────────┐
│ apps/gateway (Hono — planned) │
│ Resolves *.yourdomain.com hostname → tenant │
│ Injects X-KStack-Tenant-ID header │
└──────┬─────────────────────────────┬────────────┘
│ │
┌──────▼──────┐ ┌────────▼──────────┐
│ apps/store- │ │ apps/api │
│ front │ │ Hono + tRPC │
│ Next.js SSR │ │ Port 3001 │
└─────────────┘ └────────────────────┘
│
┌────────▼──────────┐
│ PostgreSQL │
│ Port 5432 │
└────────────────────┘
apps/dashboard → Next.js 15 merchant admin (Port 3002)
apps/storefront → Next.js 15 public store (Port 3003)
| Layer | Technology | Reason |
|---|---|---|
| Monorepo | Turborepo + pnpm | Incremental builds, workspace linking |
| Language | TypeScript 5.x (strict) | End-to-end type safety |
| API server | Hono + tRPC v11 | Fast edge-ready HTTP, type-safe RPC |
| Frontend | Next.js 15 (App Router) | SSR, ISR, server components |
| UI | Tailwind CSS v3 + lucide-react | Utility-first, no runtime CSS |
| ORM | Drizzle ORM | Type-safe, zero-magic, readable SQL output |
| Database | PostgreSQL 16 | JSONB columns, row-level isolation |
| Auth | Custom JWT (jose) + bcryptjs | No third-party lock-in |
| Validation | Zod | Shared schemas across client & server |
| Linting | Biome | Single binary replaces ESLint + Prettier |
| Containers | Docker Compose | Local Postgres |
kstack/
├── apps/
│ ├── api/ # Hono + tRPC API server (port 3001)
│ │ └── src/
│ │ ├── index.ts # Hono app entry, CORS, health check
│ │ ├── context.ts # tRPC context (JWT → user + tenantId)
│ │ ├── trpc.ts # Procedure types (public / protected / admin)
│ │ ├── router.ts # Root router composition
│ │ ├── lib/
│ │ │ ├── telemetry.ts # Anonymous usage telemetry
│ │ │ └── license.ts # License plan resolution
│ │ └── routers/
│ │ ├── auth.ts # register, login, refresh, me, logout
│ │ ├── products.ts # CRUD, variants, images
│ │ ├── orders.ts # list, get, updateStatus, notes, cancel
│ │ ├── tenant.ts # settings, domains, maintenance, controls
│ │ ├── public.ts # resolveShop, storefront products, contact
│ │ ├── reviews.ts # product reviews (purchase-gated)
│ │ ├── coupons.ts # discount codes
│ │ ├── shipping.ts # shipping rates
│ │ ├── contact.ts # contact message inbox
│ │ └── storefront.ts # themes, pages
│ │
│ ├── dashboard/ # Next.js 15 merchant admin (port 3002)
│ │ └── src/
│ │ ├── app/
│ │ │ ├── (auth)/
│ │ │ │ ├── login/page.tsx
│ │ │ │ └── register/page.tsx
│ │ │ └── [slug]/
│ │ │ ├── page.tsx # Overview — stats, recent activity
│ │ │ ├── products/ # Product management
│ │ │ ├── orders/ # Order management
│ │ │ ├── customers/ # Customer list
│ │ │ ├── collections/ # Product collections
│ │ │ ├── coupons/ # Discount codes
│ │ │ ├── shipping/ # Shipping rates
│ │ │ ├── reviews/ # Review moderation
│ │ │ ├── contact/ # Contact message inbox
│ │ │ ├── analytics/ # Store analytics
│ │ │ └── settings/ # Store settings, domains, team
│ │ ├── components/
│ │ │ └── sidebar.tsx
│ │ └── lib/
│ │ ├── trpc.ts
│ │ └── auth-store.ts
│ │
│ └── storefront/ # Next.js 15 public storefront (port 3003)
│ └── src/
│ ├── app/[slug]/ # Per-tenant storefront pages
│ │ ├── page.tsx # Home — product sections
│ │ ├── products/[handle]/ # Product detail
│ │ ├── cart/ # Shopping cart
│ │ ├── checkout/ # Checkout (guest + account)
│ │ ├── orders/ # Order tracking
│ │ ├── account/ # Customer account
│ │ ├── contact/ # Contact form
│ │ ├── wishlist/ # Wishlist
│ │ └── legal/[page]/ # Privacy, Terms, Disclaimer
│ ├── components/
│ │ ├── shop-footer.tsx
│ │ └── ...
│ └── middleware.ts # Subdomain → slug rewrite
│
├── packages/
│ ├── db/ # Drizzle ORM: schema, client, migrations
│ │ └── src/
│ │ ├── client.ts
│ │ ├── index.ts
│ │ └── schema/
│ │ ├── tenants.ts
│ │ ├── users.ts
│ │ ├── products.ts
│ │ ├── orders.ts
│ │ ├── storefront.ts
│ │ ├── coupons.ts
│ │ ├── reviews.ts
│ │ ├── shipping.ts
│ │ ├── contact.ts
│ │ ├── analytics.ts
│ │ └── platform.ts # frameworkConfig key/value store
│ │
│ ├── auth/
│ │ └── src/
│ │ ├── jwt.ts
│ │ ├── password.ts
│ │ └── middleware.ts
│ │
│ ├── types/
│ └── config/
│
├── infra/
│ └── docker-compose.yml
│
├── .env.example
├── biome.json
├── turbo.json
├── pnpm-workspace.yaml
└── package.json
The fastest way. Run this anywhere — no cloning required:
npx create-kstack-appOr with a project name:
npx create-kstack-app my-shopThe CLI will ask you a few questions (domain, Paystack keys, email, license key), then automatically clone the framework, generate a .env with strong random secrets, install dependencies, and — if Docker is running — start Postgres and push the schema. You'll be at the "create your first store" step in under two minutes.
Testing locally against a local copy of the framework:
npx create-kstack-app my-shop --source ./kstack-framework- Node.js >= 20
- pnpm >= 9 (
npm install -g pnpm) - Docker + Docker Compose
git clone <repo-url> kstack
cd kstack
pnpm installcp .env.example .envEdit .env and set at minimum:
JWT_SECRET— at least 32 random characters (openssl rand -base64 48)JWT_REFRESH_SECRET— at least 32 random characters, different from above
Everything else can stay as defaults for local development.
docker compose -f infra/docker-compose.yml up -dpnpm db:pushYou should see [✓] Changes applied.
pnpm dev| App | URL | Description |
|---|---|---|
| API | http://localhost:3001 | tRPC + REST server |
| Dashboard | http://localhost:3002 | Merchant admin panel |
| Storefront | http://localhost:3003 | Public storefront |
pnpm kstack store:createFill in the prompts (name, email, password, shop name, slug). Once created, open the dashboard and log in:
http://localhost:3002/login
To visit your storefront, go to http://localhost:3003/{your-shop-slug}.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://kstack:kstack@localhost:5432/kstack |
PostgreSQL connection string |
REDIS_URL |
redis://localhost:6379 |
Redis connection string |
JWT_SECRET |
(required) | Access token signing secret (min 32 chars) |
JWT_REFRESH_SECRET |
(required) | Refresh token signing secret (min 32 chars) |
NODE_ENV |
development |
development or production |
API_PORT |
3001 |
API server port |
NEXT_PUBLIC_API_URL |
http://localhost:3001 |
API URL used by the dashboard and storefront |
NEXT_PUBLIC_ROOT_DOMAIN |
— | Apex domain for subdomain routing (e.g. yourdomain.com) |
STORAGE_ENDPOINT |
— | S3-compatible storage endpoint (Cloudflare R2, MinIO, etc.) |
STORAGE_ACCESS_KEY_ID |
— | Storage access key |
STORAGE_SECRET_ACCESS_KEY |
— | Storage secret key |
STORAGE_BUCKET |
kstack-dev |
Storage bucket name |
STORAGE_PUBLIC_URL |
— | Public CDN URL for stored assets |
PAYSTACK_SECRET_KEY |
— | Paystack secret key |
PAYSTACK_WEBHOOK_SECRET |
— | Paystack webhook signing secret |
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY |
— | Paystack public key (client-side) |
RESEND_API_KEY |
— | Resend API key for transactional email |
EMAIL_FROM |
— | From address for outgoing emails |
KSTACK_LICENSE_KEY |
— | License key for Pro/Enterprise features (see License Plans) |
KSTACK_TELEMETRY |
true |
Set to false to opt out of anonymous usage telemetry |
KStack is free and open source under the MIT license. The Community plan has no restrictions.
A license key unlocks Pro and Enterprise features for commercial deployments.
| Plan | Price | Who it's for |
|---|---|---|
| Community | Free | Developers, self-hosted personal projects |
| Pro | Paid | Businesses running KStack commercially |
| Enterprise | Paid | Large-scale or white-label deployments |
Get a license key at https://kstack.dev/pricing, then add it to your environment:
KSTACK_LICENSE_KEY=your-key-hereOn startup the API validates your key against the license server and prints:
KStack API → http://localhost:3001
License PRO ✓
In your API routers, import the helpers from the license module:
import { isPro, isEnterprise, getCurrentPlan } from "../lib/license";
// Check before running a procedure
if (!isPro()) {
throw new TRPCError({ code: "FORBIDDEN", message: "This feature requires a Pro license." });
}- The license is validated once at startup against
https://api.kstack.dev/license/validate - The result is cached in your local database for 24 hours — your server keeps running even if the license API is unreachable
- If validation fails (network error, invalid key), the instance falls back to the Community plan gracefully — it never crashes
- The cache endpoint and API URL are configurable via
KSTACK_LICENSE_API_URL
KStack collects anonymous usage data to help prioritise features and fix bugs. This is enabled by default.
| Field | Description |
|---|---|
installId |
A random UUID generated once and stored in your local database |
version |
The KStack version string from package.json |
tenantCount |
Number of stores on this instance (an integer) |
nodeEnv |
development or production |
- Any merchant, customer, product, or order data
- Personal or personally identifiable information
- IP addresses
- License keys or financial information
Set KSTACK_TELEMETRY=false in your .env:
KSTACK_TELEMETRY=falseOn the very first startup, KStack prints a notice to the terminal explaining what is collected and how to opt out. This notice is shown once only.
Drizzle ORM schema definitions, database client, and migration tooling.
Exports:
db— Drizzle database instancewithTransaction(fn)— Run operations in a transaction- All schema table definitions
Usage:
import { db, products, eq } from "@kstack/db";
const rows = await db.select().from(products).where(eq(products.tenantId, id));Commands:
pnpm db:push # Push schema directly to database (dev only)
pnpm db:generate # Generate SQL migration files
pnpm db:migrate # Apply migration files
pnpm db:studio # Open Drizzle Studio at localhost:4983
pnpm db:seed # Seed demo store with sample productsJWT token management, password hashing, and Hono middleware.
JWT functions:
import { signAccessToken, signRefreshToken, verifyAccessToken } from "@kstack/auth";
const accessToken = await signAccessToken({ sub, email, tenantId, role }); // 15min TTL
const refreshToken = await signRefreshToken(userId); // 30d TTL
const payload = await verifyAccessToken(token); // throws if invalidPassword functions:
import { hashPassword, verifyPassword } from "@kstack/auth";
const hash = await hashPassword("mypassword"); // bcrypt, 12 rounds
const valid = await verifyPassword("mypassword", hash); // booleanHono middleware:
import { requireAuth, requireAdminRole } from "@kstack/auth";
app.get("/protected", requireAuth, (c) => {
const user = c.get("user"); // JwtPayload
return c.json({ user });
});Shared Zod schemas and TypeScript types used across all apps and packages.
import { TenantSchema, TenantPlan } from "@kstack/types";
// TenantPlan: "free" | "starter" | "pro" | "enterprise"
import { RegisterSchema, LoginSchema, JwtPayload, UserRole } from "@kstack/types";
// UserRole: "owner" | "admin" | "staff"
import { CreateProductSchema, ProductStatus } from "@kstack/types";
// ProductStatus: "draft" | "active" | "archived"
import { OrderSchema, OrderStatus, FinancialStatus } from "@kstack/types";
// OrderStatus: "pending" | "processing" | "shipped" | "delivered" | "cancelled" | "refunded"
// FinancialStatus: "pending" | "paid" | "refunded" | "partially_refunded" | "failed"Shared base configurations.
@kstack/config/tsconfig— TypeScript base config (strict,noUncheckedIndexedAccess,exactOptionalPropertyTypes)@kstack/config/tailwind— Tailwind base theme
Port: 3001
Hono HTTP server with tRPC adapter. All business logic lives here.
Endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/health |
Server health check |
POST |
/webhook/paystack |
Paystack payment webhook |
ALL |
/trpc/* |
tRPC batch handler |
tRPC procedure types:
| Type | Auth required | Role required |
|---|---|---|
publicProcedure |
No | — |
protectedProcedure |
Yes (Bearer JWT) | Any authenticated user |
adminProcedure |
Yes (Bearer JWT) | owner or admin only |
Port: 3002
Next.js 15 merchant admin. All routes are under /{shopSlug}/.
| Route | Description |
|---|---|
/login |
Sign in to existing shop |
/register |
Info page — stores are created via pnpm kstack store:create |
/{slug} |
Overview — stats and recent activity |
/{slug}/products |
Product management |
/{slug}/orders |
Order management with fulfilment tracking |
/{slug}/customers |
Customer list |
/{slug}/collections |
Product collections |
/{slug}/coupons |
Discount codes |
/{slug}/shipping |
Shipping rates |
/{slug}/reviews |
Review moderation |
/{slug}/contact |
Contact message inbox |
/{slug}/analytics |
Store analytics |
/{slug}/settings |
Store settings, team, domains, legal pages, store controls |
Port: 3003
Next.js 15 SSR app that renders any tenant's shop.
How tenant resolution works:
- In development, stores are accessed at
http://localhost:3003/{slug} - In production with
NEXT_PUBLIC_ROOT_DOMAIN=yourdomain.comset, stores can be accessed at{slug}.yourdomain.comvia subdomain routing (handled by Next.js middleware) - Merchants can also add a custom domain via Settings → Custom Domains
Storefront routes:
| Route | Description |
|---|---|
/{slug} |
Home page — On Sale, Recommended, New Arrivals sections |
/{slug}/products/{handle} |
Product detail with variant selector and reviews |
/{slug}/cart |
Shopping cart |
/{slug}/checkout |
Checkout (guest or account login) |
/{slug}/orders |
Order tracking |
/{slug}/account |
Customer account |
/{slug}/contact |
Contact form |
/{slug}/wishlist |
Wishlist |
/{slug}/legal/privacy |
Privacy Policy |
/{slug}/legal/terms |
Terms & Conditions |
/{slug}/legal/disclaimer |
Disclaimer |
Legal pages are auto-generated from a comprehensive default template. Merchants can customise them from Settings → Legal Pages in the dashboard.
tenants ──< domains
tenants ──< merchantUsers >── users
users ──< refreshTokens
tenants ──< products ──< variants
products ──< productImages
tenants ──< collections ──<> products
tenants ──< customers ──< orders ──< orderLineItems
orders.lineItems >── variants
tenants ──< themes ──< pages
tenants ──< coupons
tenants ──< shippingRates
tenants ──< contactMessages
frameworkConfig (key/value)
| Column | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
slug |
text | Unique store slug |
name |
text | Display name |
plan |
enum | free | starter | pro | enterprise |
email |
text | Contact email |
logo_url |
text | Optional |
maintenance_mode |
boolean | Shows maintenance page to visitors |
frozen_at |
timestamptz | NULL = active; non-null = store frozen |
legal_pages |
jsonb | { privacy?, terms?, disclaimer? } custom markdown |
contact_info |
jsonb | Phone, address, business hours etc. |
social_links |
jsonb | Facebook, Instagram, Twitter etc. |
Products have one or more variants. Each variant has price, inventory, SKU, and option values (e.g. { color: "red", size: "M" }).
| Column | Type | Notes |
|---|---|---|
status |
enum | pending | processing | shipped | delivered | cancelled | refunded |
financial_status |
enum | pending | paid | refunded | partially_refunded | failed |
total |
numeric | Final amount charged |
shipping_address |
jsonb | Address snapshot |
Stores messages submitted through the storefront contact form.
| Column | Type | Notes |
|---|---|---|
status |
enum | new | read | replied |
name, email, subject, message |
text |
Internal key/value store used for install ID, telemetry notice state, and license cache.
All API calls go through tRPC at POST http://localhost:3001/trpc/{procedure}.
# Query
curl http://localhost:3001/trpc/auth.me \
-H "Authorization: Bearer <token>"
# Mutation
curl -X POST "http://localhost:3001/trpc/auth.login?batch=1" \
-H "Content-Type: application/json" \
-d '{"0":{"json":{"email":"you@example.com","password":"yourpassword"}}}'Type: Public mutation
Input:
{
name: string;
email: string;
password: string; // min 8 characters
shopName: string;
shopSlug: string; // lowercase, hyphens, 3–32 chars
}Returns: { accessToken, refreshToken, user, tenant }
Type: Public mutation
Input: { email: string; password: string; }
Returns: Same shape as auth.register.
Type: Public mutation
Input: { refreshToken: string; }
Returns: { accessToken, refreshToken } — old token is immediately revoked.
Type: Protected query
Returns: { user: { id, email, name }, tenant: { id, slug, name, plan } }
Type: Protected query
Input: { status?, limit?, offset? }
Type: Protected mutation
Input: { title, description?, handle?, status?, tags? }
Type: Protected mutation
Input:
{
productId: string;
data: { title, price, sku?, comparePrice?, inventory?, options?, imageUrl? }
}Type: Protected query
Input: { status?, financialStatus?, limit?, offset? }
Type: Admin mutation
Input: { id: string; status: OrderStatus; reason?: string }
Type: Admin mutation
Input:
{
name?: string;
email?: string;
logoUrl?: string | null;
socialLinks?: Record<string, string>;
contactInfo?: Record<string, string>;
legalPages?: { privacy?: string; terms?: string; disclaimer?: string };
}Type: Admin mutation
Input: { enabled: boolean }
Type: Admin mutation
Input: { frozen: boolean }
Type: Public query
Input: { slug: string }
Returns the public shop data used by the storefront. Returns NOT_FOUND if the store is frozen.
- Access token — 15-minute TTL, signed with
JWT_SECRET, sent asAuthorization: Bearer <token> - Refresh token — 30-day TTL, stored server-side as a SHA-256 hash, rotated on every use
Every auth.refresh call revokes the old token and issues a fresh pair. A revoked token presented again signals potential theft and is rejected.
Auth state persisted to localStorage under key kstack_auth:
{
accessToken: string | null;
refreshToken: string | null;
user: { id, email, name } | null;
tenant: { id, slug, name } | null;
}Every table has a tenant_id column. All tRPC procedures scope queries to ctx.tenantId extracted from the JWT — a merchant can never access another merchant's data even with a valid token.
// Always scoped — impossible to leak cross-tenant
await ctx.db.select().from(products).where(eq(products.tenantId, ctx.tenantId));| Role | Description |
|---|---|
owner |
Full access including billing, staff management, and store deletion |
admin |
Products, orders, settings, domains — everything except store deletion |
staff |
Read access, order processing |
protectedProcedure— any authenticated roleadminProcedure—owneroradminonly
KStack ships two CLIs.
Run with npx — no install needed.
npx create-kstack-app # interactive setup
npx create-kstack-app my-shop # pre-fill project name
npx create-kstack-app my-shop --source ./kstack-framework # use local copy
npx create-kstack-app --skip-install # skip pnpm install step
npx create-kstack-app --skip-git # skip git init
npx create-kstack-app --version # print versionRun inside any KStack project with pnpm kstack.
# Create a new store (owner account + tenant) — no API server required
pnpm kstack store:create
# Scaffold a new module (generates router, dashboard page, DB schema, wires everything up)
pnpm kstack module:create KStack_Loyalty
pnpm kstack module:create KStack_Blog --description "Blog posts and articles"
pnpm kstack module:create KStack_Referrals --no-schema # skip DB schema
pnpm kstack module:create KStack_Widget --dry-run # preview without writing files
# List all registered modules and their status
pnpm kstack module:list
# Show framework version, module count, and license plan
pnpm kstack infoWhat module:create generates:
| File | Description |
|---|---|
apps/api/src/routers/{name}.ts |
tRPC router with list, get, create, update, delete stubs |
apps/dashboard/src/app/[slug]/{name}/page.tsx |
Dashboard page with table + create form |
packages/db/src/schema/{name}.ts |
Drizzle table with id, tenantId, name, createdAt, updatedAt |
Patches apps/api/src/router.ts |
Adds import + registers router in appRouter |
Patches packages/db/src/schema/index.ts |
Adds export * from "./{name}" |
Patches apps/dashboard/src/components/sidebar.tsx |
Adds nav item |
Patches apps/dashboard/src/lib/modules.ts |
Registers module in the Modules settings list |
Updates modules.json |
Registers module in the manifest |
# Start all dev servers
pnpm dev
# Start individual apps
pnpm --filter=@kstack/api dev
pnpm --filter=@kstack/dashboard dev
pnpm --filter=@kstack/storefront dev
# Build all
pnpm build
# Type-check all
pnpm typecheck
# Lint + format
pnpm lint
pnpm format
# Database
pnpm db:push # Push schema to DB (dev — no migration files)
pnpm db:generate # Generate SQL migration files
pnpm db:migrate # Apply migration files
pnpm db:studio # Open Drizzle Studio at localhost:4983
pnpm db:seed # Seed demo store
# Infrastructure
docker compose -f infra/docker-compose.yml up -d # Start Postgres
docker compose -f infra/docker-compose.yml down # Stop Postgres
docker compose -f infra/docker-compose.yml down -v # Stop + wipe data| Service | Recommended platform |
|---|---|
apps/api |
Railway, Fly.io, or any Node.js host |
apps/dashboard |
Vercel |
apps/storefront |
Vercel |
| PostgreSQL | Neon, Supabase, or managed RDS |
| File storage | Cloudflare R2 (zero egress fees) |
- Add a wildcard DNS record:
*.yourdomain.com→ CNAME to your storefront host - Set
NEXT_PUBLIC_ROOT_DOMAIN=yourdomain.comon your storefront deployment - Set
ROOT_DOMAIN=yourdomain.comon your API deployment - For merchant custom domains: the merchant adds a CNAME
shop.theirdomain.com → yourdomain.com, then verifies via the dashboard by adding a DNS TXT record_kstack-verify=<token>
# Generate strong secrets
openssl rand -base64 48 # run twice — one for JWT_SECRET, one for JWT_REFRESH_SECRET
NODE_ENV=production
DATABASE_URL=postgresql://user:password@host:5432/kstack
JWT_SECRET=<generated>
JWT_REFRESH_SECRET=<generated>
NEXT_PUBLIC_ROOT_DOMAIN=yourdomain.com
ROOT_DOMAIN=yourdomain.com
ALLOWED_ORIGINS=https://dashboard.yourdomain.com,https://yourdomain.com
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
EMAIL_FROM=noreply@yourdomain.com
RESEND_API_KEY=<your key>- Monorepo setup (Turborepo + pnpm)
- Full database schema
- Auth package (JWT, bcrypt, middleware)
- API server (tRPC — all modules)
- Merchant dashboard
- Storefront (SSR, subdomain routing, custom domains)
- Product management with variants, images, collections
- Order management with fulfilment and notes
- Customer accounts and guest checkout
- Paystack payment integration
- Coupon and discount codes
- Shipping rate configuration
- Product reviews (purchase-gated)
- Contact form with inbox
- Store controls (maintenance mode, freeze, delete)
- Legal pages (Privacy, Terms, Disclaimer)
- License plan system
- Anonymous telemetry with opt-out
-
create-kstack-app— interactive project creator (npx create-kstack-app) -
kstackCLI — module scaffold,module:list,infocommands
- Page builder (Craft.js drag-and-drop)
- Monaco code editor for raw HTML/CSS/JS pages
- Analytics dashboard (revenue charts, top products)
- Multi-currency support
- Staff invitation via email
- DNS verification worker
- Plugin / app marketplace
- Mobile merchant app (Expo)
- B2B / wholesale pricing tiers
- AI-assisted product descriptions
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Follow the module naming convention in CLAUDE.md
- Ensure
pnpm typecheckandpnpm lintpass - Open a pull request with a clear description of what and why
MIT with Commons Clause — free to use, modify, and self-host. You cannot sell KStack itself as a hosted SaaS service (i.e. run a platform where third-party merchants pay you to host their stores). See LICENSE for the full text and plain-language summary.
For commercial deployments with Pro or Enterprise features, a license key is required. See License Plans.