OpenSaaS Stack Migration
Expert guidance for migrating existing projects to OpenSaaS Stack.
When to Use This Skill
Use this skill when:
- •Planning a migration from Prisma, KeystoneJS, or Next.js
- •Designing access control patterns
- •Configuring
opensaas.config.ts - •Troubleshooting migration issues
- •Explaining OpenSaaS Stack concepts
Migration Process
1. Install Required Packages
IMPORTANT: Always install packages before starting migration
Detect the user's package manager (check for package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lockb) and use their preferred package manager.
Required packages:
# Using npm npm install --save-dev @opensaas/stack-cli npm install @opensaas/stack-core # Using pnpm pnpm add -D @opensaas/stack-cli pnpm add @opensaas/stack-core # Using yarn yarn add -D @opensaas/stack-cli yarn add @opensaas/stack-core # Using bun bun add -D @opensaas/stack-cli bun add @opensaas/stack-core
Optional packages (based on user needs):
- •
@opensaas/stack-auth- If the project needs authentication - •
@opensaas/stack-ui- If the project needs the admin UI - •
@opensaas/stack-tiptap- If the project needs rich text editing - •
@opensaas/stack-storage- If the project needs file storage - •
@opensaas/stack-rag- If the project needs semantic search/RAG
Database adapters (required for Prisma 7):
SQLite:
npm install better-sqlite3 @prisma/adapter-better-sqlite3
PostgreSQL:
npm install pg @prisma/adapter-pg
Neon (serverless PostgreSQL):
npm install @neondatabase/serverless @prisma/adapter-neon ws
2. Uninstall Old Packages (KeystoneJS Only)
IMPORTANT: For KeystoneJS projects, uninstall KeystoneJS packages before installing OpenSaaS
KeystoneJS migrations should preserve the existing file structure and just swap packages. Do NOT create a new project structure.
# Detect package manager and uninstall KeystoneJS packages npm uninstall @keystone-6/core @keystone-6/auth @keystone-6/fields-document # Or with pnpm pnpm remove @keystone-6/core @keystone-6/auth @keystone-6/fields-document
Remove all @keystone-6/* packages from package.json.
3. Schema Analysis
Prisma Projects:
- •Analyze existing
schema.prisma - •Identify models, fields, and relationships
- •Note any Prisma-specific features used
KeystoneJS Projects:
- •Review list definitions in
keystone.config.tsorkeystone.ts - •Map KeystoneJS fields to OpenSaaS fields
- •Identify access control patterns
- •Note the existing file structure - preserve it during migration
4. Access Control Design
Common Patterns:
// Public read, authenticated write
operation: {
query: () => true,
create: ({ session }) => !!session?.userId,
update: ({ session }) => !!session?.userId,
delete: ({ session }) => !!session?.userId,
}
// Author-only access
operation: {
query: () => true,
update: ({ session, item }) => item.authorId === session?.userId,
delete: ({ session, item }) => item.authorId === session?.userId,
}
// Admin-only
operation: {
query: ({ session }) => session?.role === 'admin',
create: ({ session }) => session?.role === 'admin',
update: ({ session }) => session?.role === 'admin',
delete: ({ session }) => session?.role === 'admin',
}
// Filter-based access
operation: {
query: ({ session }) => ({
where: { authorId: { equals: session?.userId } }
}),
}
5. Field Mapping
Prisma to OpenSaaS:
| Prisma Type | OpenSaaS Field |
|---|---|
String | text() |
Int | integer() |
Boolean | checkbox() |
DateTime | timestamp() |
Enum | select({ options: [...] }) |
Relation | relationship({ ref: '...' }) |
KeystoneJS to OpenSaaS:
| KeystoneJS Field | OpenSaaS Field |
|---|---|
text | text() |
integer | integer() |
checkbox | checkbox() |
timestamp | timestamp() |
select | select() |
relationship | relationship() |
password | password() |
6. Database Configuration
SQLite (Development):
import { PrismaBetterSQLite3 } from '@prisma/adapter-better-sqlite3'
import Database from 'better-sqlite3'
export default config({
db: {
provider: 'sqlite',
url: process.env.DATABASE_URL || 'file:./dev.db',
prismaClientConstructor: (PrismaClient) => {
const db = new Database(process.env.DATABASE_URL || './dev.db')
const adapter = new PrismaBetterSQLite3(db)
return new PrismaClient({ adapter })
},
},
})
PostgreSQL (Production):
import { PrismaPg } from '@prisma/adapter-pg'
import pg from 'pg'
export default config({
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL,
prismaClientConstructor: (PrismaClient) => {
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })
const adapter = new PrismaPg(pool)
return new PrismaClient({ adapter })
},
},
})
KeystoneJS Migration Strategy
CRITICAL: KeystoneJS projects should be migrated IN PLACE
Do NOT create a new project structure. Instead:
File Structure Preservation
Keep existing files and update them:
- •
Rename config file:
- •
keystone.config.ts→opensaas.config.ts - •OR
keystone.ts→opensaas.config.ts
- •
- •
Update imports in ALL files:
typescript// Before (KeystoneJS) import { config, list } from '@keystone-6/core' import { text, relationship, timestamp } from '@keystone-6/core/fields' // After (OpenSaaS) import { config, list } from '@opensaas/stack-core' import { text, relationship, timestamp } from '@opensaas/stack-core/fields' - •
Rename KeystoneJS concepts to OpenSaaS:
- •
keystone.config.ts→opensaas.config.ts - •
Keystonereferences →OpenSaaSor remove entirely - •Keep all other file names and structure as-is
- •
- •
Update schema/list definitions:
- •Keep existing list definitions
- •Update field imports from
@keystone-6/core/fieldsto@opensaas/stack-core/fields - •Adapt access control syntax (KeystoneJS and OpenSaaS are similar)
- •Keep existing GraphQL API file structure
- •
Preserve API routes and pages:
- •Keep existing Next.js pages
- •Update any KeystoneJS context calls to use OpenSaaS context
- •Maintain existing route structure
Import Mapping
| KeystoneJS Import | OpenSaaS Import |
|---|---|
@keystone-6/core | @opensaas/stack-core |
@keystone-6/core/fields | @opensaas/stack-core/fields |
@keystone-6/auth | @opensaas/stack-auth |
@keystone-6/fields-document | @opensaas/stack-tiptap |
Example: KeystoneJS to OpenSaaS Config
Before (keystone.config.ts):
import { config, list } from '@keystone-6/core'
import { text, relationship, timestamp } from '@keystone-6/core/fields'
export default config({
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL,
},
lists: {
Post: list({
fields: {
title: text({ validation: { isRequired: true } }),
content: text({ ui: { displayMode: 'textarea' } }),
author: relationship({ ref: 'User.posts' }),
publishedAt: timestamp(),
},
}),
},
})
After (opensaas.config.ts):
import { config, list } from '@opensaas/stack-core'
import { text, relationship, timestamp } from '@opensaas/stack-core/fields'
import { PrismaPg } from '@prisma/adapter-pg'
import pg from 'pg'
export default config({
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL,
prismaClientConstructor: (PrismaClient) => {
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })
const adapter = new PrismaPg(pool)
return new PrismaClient({ adapter })
},
},
lists: {
Post: list({
fields: {
title: text({ validation: { isRequired: true } }),
content: text(), // Note: OpenSaaS text() doesn't have ui.displayMode
author: relationship({ ref: 'User.posts' }),
publishedAt: timestamp(),
},
}),
},
})
Steps for KeystoneJS Migration
- •Uninstall KeystoneJS packages (see step 2 above)
- •Install OpenSaaS packages (see step 1 above)
- •Rename
keystone.config.tstoopensaas.config.ts - •Find and replace in ALL project files:
- •
@keystone-6/core→@opensaas/stack-core - •
@keystone-6/core/fields→@opensaas/stack-core/fields - •
@keystone-6/auth→@opensaas/stack-auth
- •
- •Add Prisma adapter to database config (required for Prisma 7)
- •Update context creation in API routes/pages
- •Test - the app structure should remain identical
DO NOT:
- •Create new folders or reorganize the project
- •Move files to different locations
- •Create a new "OpenSaaS structure"
- •Change API endpoints or routes
DO:
- •Keep existing file structure
- •Update imports only
- •Adapt config to OpenSaaS syntax
- •Preserve existing API routes and pages
Common Migration Challenges
Challenge: Preserving Existing Data
Solution:
- •Use
opensaas generateto create Prisma schema - •Use
prisma db pushinstead of migrations for existing databases - •Never use
prisma migrate devwith existing data
Challenge: Complex Access Control
Solution:
- •Start with simple boolean access control
- •Iterate to filter-based access as needed
- •Use field-level access for sensitive data
Challenge: Custom Field Types
Solution:
- •Create custom field builders extending
BaseFieldConfig - •Implement
getZodSchema,getPrismaType,getTypeScriptType - •Register UI components for admin interface
Challenge: KeystoneJS Document Field
Solution:
- •Replace with
@opensaas/stack-tiptaprich text field - •Or create custom field type for document structure
- •May require data migration for existing documents
Migration Checklist
For Prisma Projects:
- • Detect package manager (npm, pnpm, yarn, or bun)
- • Install required packages (@opensaas/stack-cli, @opensaas/stack-core)
- • Install optional packages (auth, ui, etc. based on needs)
- • Install database adapter (better-sqlite3, pg, etc.)
- • Analyze existing schema
- • Design access control patterns
- • Create
opensaas.config.ts - • Configure database adapter in config
- • Run
opensaas generate(ornpx opensaas generate) - • Run
prisma generate(ornpx prisma generate) - • Run
prisma db push(ornpx prisma db push) - • Test access control
- • Verify admin UI (if using @opensaas/stack-ui)
- • Update application code to use context
- • Test all CRUD operations
- • Deploy to production
For KeystoneJS Projects:
- • Detect package manager (npm, pnpm, yarn, or bun)
- • Uninstall ALL KeystoneJS packages (@keystone-6/*)
- • Install required packages (@opensaas/stack-cli, @opensaas/stack-core)
- • Install optional packages (auth, ui, tiptap for document fields)
- • Install database adapter (pg, @prisma/adapter-pg for PostgreSQL)
- • Rename
keystone.config.tstoopensaas.config.ts - • Update imports in config file (KeystoneJS → OpenSaaS)
- • Find and replace imports in ALL project files
- • Add Prisma adapter to database config
- • Update context creation in API routes
- • Analyze and adapt access control patterns
- • Run
opensaas generate - • Run
prisma generate - • Run
prisma db push - • Test existing API routes
- • Test existing pages/UI
- • Test all CRUD operations
- • Verify no broken imports
- • Deploy to production
Best Practices
- •Start Simple: Begin with basic access control, refine later
- •Test Access Control: Verify permissions work as expected
- •Use Context Everywhere: Replace direct Prisma calls with
context.db - •Leverage Plugins: Use
@opensaas/stack-authfor authentication - •Version Control: Commit
opensaas.config.tsto git - •Document Decisions: Comment complex access control logic
Reporting Issues
When you encounter bugs or missing features in OpenSaaS Stack:
If during migration you discover:
- •Bugs in OpenSaaS Stack packages
- •Missing features that would improve the migration experience
- •Documentation gaps or errors
- •API inconsistencies or unexpected behavior
Use the github-issue-creator agent to create a GitHub issue on the OpenSaasAU/stack repository:
Invoke the github-issue-creator agent with: - Clear description of the bug or missing feature - Steps to reproduce (if applicable) - Expected vs actual behavior - Affected files and line numbers - Your suggested solution (if you have one)
This ensures bugs and feature requests are properly tracked and addressed by the OpenSaaS Stack team, improving the experience for future users.
Example:
If you notice that the migration command doesn't properly handle Prisma enums, invoke the github-issue-creator agent:
"Found a bug: The migration generator doesn't convert Prisma enums to OpenSaaS select fields. Enums are being ignored during schema analysis in packages/cli/src/migration/introspectors/prisma-introspector.ts"
The agent will create a detailed GitHub issue with reproduction steps and proposed solution.