// Expert knowledge for migrating projects to OpenSaaS Stack. Use when discussing migration strategies, access control patterns, or OpenSaaS Stack configuration best practices.
| name | opensaas-migration |
| description | Expert knowledge for migrating projects to OpenSaaS Stack. Use when discussing migration strategies, access control patterns, or OpenSaaS Stack configuration best practices. |
Expert guidance for migrating existing projects to OpenSaaS Stack.
Use this skill when:
opensaas.config.tsIMPORTANT: 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/RAGDatabase 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
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.
Prisma Projects:
schema.prismaKeystoneJS Projects:
keystone.config.ts or keystone.tsCommon 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 } }
}),
}
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() |
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 })
},
},
})
CRITICAL: KeystoneJS projects should be migrated IN PLACE
Do NOT create a new project structure. Instead:
Keep existing files and update them:
Rename config file:
keystone.config.ts → opensaas.config.tskeystone.ts → opensaas.config.tsUpdate imports in ALL files:
// 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.tsKeystone references → OpenSaaS or remove entirelyUpdate schema/list definitions:
@keystone-6/core/fields to @opensaas/stack-core/fieldsPreserve API routes and pages:
| 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 |
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(),
},
}),
},
})
keystone.config.ts to opensaas.config.ts@keystone-6/core → @opensaas/stack-core@keystone-6/core/fields → @opensaas/stack-core/fields@keystone-6/auth → @opensaas/stack-authDO NOT:
DO:
Solution:
opensaas generate to create Prisma schemaprisma db push instead of migrations for existing databasesprisma migrate dev with existing dataSolution:
Solution:
BaseFieldConfiggetZodSchema, getPrismaType, getTypeScriptTypeSolution:
@opensaas/stack-tiptap rich text fieldopensaas.config.tsopensaas generate (or npx opensaas generate)prisma generate (or npx prisma generate)prisma db push (or npx prisma db push)keystone.config.ts to opensaas.config.tsopensaas generateprisma generateprisma db pushcontext.db@opensaas/stack-auth for authenticationopensaas.config.ts to gitWhen you encounter bugs or missing features in OpenSaaS Stack:
If during migration you discover:
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.