smithery.ai

opensaas-migration

Expert knowledge for migrating projects to OpenSaaS Stack.

First seen Apr 13, 2026

Installation

$ npx skills add https://smithery.ai

Summary

  • Expert knowledge for migrating projects to OpenSaaS Stack.
  • Invoke whenever the user mentions migrating from KeystoneJS, Prisma, or an existing Next.js project; asks about access control patterns or opensaas.config.ts; or is troubleshooting any aspect of an OpenSaaS Stack migration.
  • Don't wait for the user to say "migration" — trigger whenever the conversation touches these areas.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 19,663 B
  • docs SUMMARY.md 199 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

OpenSaaS Stack Migration

Expert guidance for migrating existing projects to OpenSaaS Stack.

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.ts or keystone.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 — return the Prisma filter DIRECTLY (no `where` wrapper);
// the engine merges it into the query's where clause
operation: {
  query: ({ session }) =>
    session ? { authorId: { equals: session.userId } } : false,
}

5. Field Mapping

Prisma to OpenSaaS:

Prisma Type OpenSaaS Field
String text()
Int integer()
Boolean checkbox()
DateTime timestamp()
Decimal decimal()
Json json()
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()
decimal decimal()
calendarDay calendarDay()
json json()
virtual virtual()requires changes (no GraphQL, use hooks.resolveOutput)

6. Database Configuration

SQLite (Development):

import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'

export default config({
  db: {
    provider: 'sqlite',
    url: process.env.DATABASE_URL || 'file:./dev.db',
    prismaClientConstructor: (PrismaClient) => {
      const adapter = new PrismaBetterSqlite3({ url: process.env.DATABASE_URL || 'file:./dev.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:

  1. Rename config file:

- keystone.config.tsopensaas.config.ts - OR keystone.tsopensaas.config.ts

  1. 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' ```

  1. Rename KeystoneJS concepts to OpenSaaS:

- keystone.config.tsopensaas.config.ts - Keystone references → OpenSaaS or remove entirely - Keep all other file names and structure as-is

  1. Update schema/list definitions:

- Keep existing list definitions - Update field imports from @keystone-6/core/fields to @opensaas/stack-core/fields - Adapt access control syntax (KeystoneJS and OpenSaaS are similar) - Keep existing GraphQL API file structure

  1. 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({ ui: { displayMode: 'textarea' } }), // ui.displayMode carries over from Keystone
        author: relationship({ ref: 'User.posts' }),
        publishedAt: timestamp(),
      },
    }),
  },
})

Steps for KeystoneJS Migration

  1. Uninstall KeystoneJS packages (see step 2 above)
  2. Install OpenSaaS packages (see step 1 above)
  3. Rename keystone.config.ts to opensaas.config.ts
  4. 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

  1. Add Prisma adapter to database config (required for Prisma 7)
  2. Migrate virtual fields — if any virtual() fields exist, invoke the keystone-virtual-fields-context skill
  3. Migrate context.graphql calls — search for context.graphql.run(, context.graphql.raw(, context.query.; for simple reads replace with context.db.*; for nested/joined data use defineFragment + context.db.{list}.findMany({ query: fragment }); invoke the migrate-context-calls skill for detailed patterns
  4. 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 generate to create Prisma schema
  • Use prisma db push instead of migrations for existing databases
  • Never use prisma migrate dev with 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-tiptap rich text field
  • Or create custom field type for document structure
  • May require data migration for existing documents

Challenge: Virtual Fields

Keystone virtual fields use graphql.field({ type: graphql.String, resolve }). OpenSaaS Stack has no GraphQL — virtual fields use hooks.resolveOutput with a type property instead.

Quick example:

// Keystone
fullName: virtual({
  field: graphql.field({
    type: graphql.String,
    resolve: (item) => `${item.firstName} ${item.lastName}`,
  }),
})

// OpenSaaS Stack
fullName: virtual({
  type: 'string',
  hooks: {
    resolveOutput: ({ item }) => `${item.firstName} ${item.lastName}`,
  },
})

Field arguments are not supported in OpenSaaS Stack. For detailed patterns including context queries and custom types, invoke the keystone-virtual-fields-context skill.

Challenge: context.graphql Calls

Keystone apps often use context.graphql.run() for type-safe data access from routes, server actions, and hooks. OpenSaaS Stack has no GraphQL — use context.db.{listName}.{method}() directly, or the new fragment-based query utilities for nested/joined data.

Simple queries (no nesting):

// Keystone
const { posts } = await context.graphql.run({
  query: `query { posts(where: { status: { equals: published } }) { id title } }`,
})

// OpenSaaS Stack
const posts = await context.db.post.findMany({
  where: { status: { equals: 'published' } },
})

Queries with nested/related data (fragments — recommended for Keystone migrations):

OpenSaaS Stack provides defineFragment for composable, fully typed queries — the closest equivalent to Keystone GraphQL fragments and codegen types. Pass the fragment directly to context.db operations using the query parameter.

// Keystone — GraphQL fragment + codegen types
import type { PostFragment } from './__generated__/graphql'

const { posts } = await context.graphql.run({
  query: `
    fragment AuthorFields on User { id name }
    query { posts { id title author { ...AuthorFields } } }
  `,
})

// OpenSaaS Stack — defineFragment + context.db (no codegen, no GraphQL)
import type { User, Post } from '@/.opensaas/prisma-client/client'
import { defineFragment, type ResultOf } from '@opensaas/stack-core'

const authorFragment = defineFragment<User>()({ id: true, name: true } as const)
const postFragment = defineFragment<Post>()({
  id: true,
  title: true,
  author: authorFragment,
} as const)

type PostData = ResultOf<typeof postFragment>
// → { id: string; title: string; author: { id: string; name: string } | null }

// Primary API: pass query to context.db operations
const posts = await context.db.post.findMany({ query: postFragment })
// posts: PostData[]

// With filter, orderBy, pagination
const filtered = await context.db.post.findMany({
  query: postFragment,
  where: { published: true },
  orderBy: { createdAt: 'desc' },
  take: 10,
})

// Single record
const post = await context.db.post.findUnique({ where: { id }, query: postFragment })

// Nested relationship filtering with RelationSelector
const commentFrag = defineFragment<Comment>()({ id: true, body: true } as const)
const postWithComments = defineFragment<Post>()({
  id: true,
  comments: { query: commentFrag, where: { approved: true }, take: 5 },
} as const)
const postsWithComments = await context.db.post.findMany({ query: postWithComments })

List names are camelCase: Postcontext.db.post, BlogPostcontext.db.blogPost. Access control is enforced automatically. For detailed patterns including sudo access, invoke the migrate-context-calls skill.

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 (or npx opensaas generate)
  • Run prisma generate (or npx prisma generate)
  • Run prisma db push (or npx 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.ts to opensaas.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
  • Migrate virtual fields (if any) — replace graphql.field() + resolve() with hooks.resolveOutput; invoke keystone-virtual-fields-context skill
  • Migrate context.graphql calls (if any) — for simple reads use context.db.*; for nested/related data use defineFragment + context.db.{list}.findMany({ query: fragment }) from @opensaas/stack-core; invoke migrate-context-calls skill for detailed patterns
  • 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

  1. Start Simple: Begin with basic access control, refine later
  2. Test Access Control: Verify permissions work as expected
  3. Use Context Everywhere: Replace direct Prisma calls with context.db
  4. Leverage Plugins: Use @opensaas/stack-auth for authentication
  5. Version Control: Commit opensaas.config.ts to git
  6. 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.

Resources