Quick Start

Get up and running with Stack in 5 minutes. Deploy to production in 30 minutes.

Prerequisites

  • Node.js 22.18 or newer
  • pnpm package manager (install with npm install -g pnpm)
  • Basic knowledge of Next.js and TypeScript

Two Ways to Get Started

Choose the method that works best for you:

Option 1: Use the CLI (Recommended)

The fastest way to scaffold a new project with everything configured:

bash
npm create opensaas-app@latest my-app
cd my-app

The scaffolder installs dependencies and runs the generator for you; add --no-install to do those yourself.

Optional: Add --with-auth flag to include Better-auth:

bash
npm create opensaas-app@latest my-app --with-auth

Optional: Add --with-ai flag to enable AI development tools:

bash
npm create opensaas-app@latest my-app --with-ai

This creates a complete Next.js project with:

  • Stack pre-configured
  • User & Post models with relationships
  • Admin UI at /admin
  • All necessary dependencies
  • Proper TypeScript configuration
  • Ready to generate and run

Skip to Step 5: Run the app

Option 2: Manual Setup

Add Stack to an existing Next.js project:

1. Create a New Project (if needed)

bash
npx create-next-app@latest my-app
cd my-app

2. Install Stack

bash
pnpm add @opensaas/stack-core @opensaas/stack-ui @prisma/orm-postgres
pnpm add -D @opensaas/stack-cli prisma

3. Create Your Config

Create opensaas.config.ts in your project root:

typescript
import { config, list } from '@opensaas/stack-core'
import { text, timestamp, relationship } from '@opensaas/stack-core/fields'

export default config({
  db: {
    provider: 'postgresql',
  },
  lists: {
    Post: list({
      fields: {
        title: text({
          validation: { isRequired: true },
        }),
        content: text({
          validation: { isRequired: true },
        }),
        publishedAt: timestamp(),
        author: relationship({ ref: 'User.posts' }),
      },
      access: {
        operation: {
          query: () => true,
          create: ({ session }) => !!session?.userId,
          update: ({ session }) => !!session?.userId,
          delete: ({ session }) => !!session?.userId,
        },
      },
    }),
    User: list({
      fields: {
        name: text({ validation: { isRequired: true } }),
        email: text({ validation: { isRequired: true } }),
        posts: relationship({ ref: 'Post.author', many: true }),
      },
    }),
  },
})

4. Generate

Add the scripts the starter ships with to package.json:

json
{
  "scripts": {
    "dev": "opensaas dev",
    "build": "pnpm generate && next build",
    "generate": "opensaas generate",
    "db:update": "opensaas db update"
  }
}

Then generate:

bash
pnpm generate

This creates:

  • prisma/contract.ts - The Contract module, with prisma/contract.json and prisma/contract.d.ts emitted beside it
  • prisma.config.ts - Prisma CLI configuration
  • .opensaas/types.ts - TypeScript types
  • .opensaas/context.ts - Context factory

Commit everything but .opensaas/, which is regenerated.

5. Run the app

bash
pnpm dev

pnpm dev is opensaas dev: it starts the Dev database — a Postgres the stack runs for your project, nothing to install — reconciles it with your config, and starts Next. Set DATABASE_URL in .env to use a Postgres of your own instead.

6. Use the Context in Your App

Create a server action in app/actions.ts:

typescript
'use server'

import { getContext } from '@/.opensaas/context'

export async function getPosts() {
  const context = await getContext()
  return context.db.Post.include('author').all()
}

export async function createPost(data: { title: string; content: string }) {
  const context = await getContext({ userId: 'user-1' }) // In real app, get from session
  return context.db.Post.create({ data })
}

7. Create a Page

Create app/page.tsx:

typescript
import { getPosts } from './actions'

export default async function HomePage() {
  const posts = await getPosts()

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-4xl font-bold mb-8">Posts</h1>
      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="border p-4 rounded-lg">
            <h2 className="text-2xl font-semibold">{post.title}</h2>
            <p className="text-gray-600 mt-2">{post.content}</p>
            {post.author && (
              <p className="text-sm text-gray-500 mt-2">By {post.author.name}</p>
            )}
          </div>
        ))}
      </div>
    </div>
  )
}

8. Deploy to Production

Ready to deploy your app? Follow our comprehensive deployment guide:

Deploy to Vercel + Neon →

The deployment process takes ~10-15 minutes and includes:

  • Setting up a production PostgreSQL database (Neon)
  • Configuring environment variables
  • Deploying to Vercel
  • Running database migrations

What's Next?

Now that you have a basic setup, explore these topics:

Tip: The access control engine automatically secures all database operations. No record is returned without passing access checks.

AI-Assisted Development

Stack includes a Claude Code plugin for feature-driven development. Instead of manually configuring infrastructure, describe what you want to build and let Claude guide you through implementation.

Features:

  • Interactive feature wizards that generate complete code
  • Documentation search via /opensaas-docs
  • Specialized agent that understands OpenSaaS patterns

Claude Code Plugin Guide →

Common Issues

"Cannot find module '@/.opensaas/context'"

Make sure you've run pnpm generate to create the generated files.

"No database connection URL is set and no dev database is running"

Run the app through pnpm dev, which starts the Dev database, or set DATABASE_URL to a Postgres of your own.

Access control returns empty results

Check your access control functions - they might be denying access. Remember that denied access returns [] or null, not an error.