Agent Skills › adrianhajdin/ghost-ai

adrianhajdin/ghost-ai

GitHub

Clerk后端API探索与执行工具,支持浏览标签、检查端点及发起认证请求。涵盖用户管理、组织操作等场景。强制校验密钥与作用域,对写操作提供详细安全提示和不可逆删除警告,并提供常用操作的快速模板。

19 skills 240

Install All Skills

npx skills add adrianhajdin/ghost-ai --all -g -y
More Options

List skills in collection

npx skills add adrianhajdin/ghost-ai --list

Skills in Collection (19)

Clerk后端API探索与执行工具,支持浏览标签、检查端点及发起认证请求。涵盖用户管理、组织操作等场景。强制校验密钥与作用域,对写操作提供详细安全提示和不可逆删除警告,并提供常用操作的快速模板。
列出或管理用户 创建或管理组织 调用任何Clerk API端点 执行POST/PATCH/PUT/DELETE等写操作
.agents/skills/clerk-backend-api/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill clerk-backend-api -g -y
SKILL.md
Frontmatter
{
    "name": "clerk-backend-api",
    "license": "MIT",
    "metadata": {
        "inputs": [
            {
                "name": "CLERK_SECRET_KEY",
                "required": true,
                "description": "Clerk secret key (sk_*) for Backend API calls"
            }
        ]
    },
    "description": "Clerk Backend REST API explorer and executor. Browse tags, inspect endpoint schemas, and execute authenticated requests. Use when listing users, managing organizations, or calling any Clerk API endpoint.",
    "allowed-tools": "Bash, Read, Grep, Skill, WebFetch"
}

Options context

User Prompt: $ARGUMENTS

CRITICAL: Mandatory checks before EVERY write request

Before ANY POST / PATCH / PUT / DELETE, you MUST do ALL of the following in your response:

  1. Check CLERK_SECRET_KEY — verify it is set:

    echo $CLERK_SECRET_KEY | head -c 10
    

    If empty, stop and ask the user. Do not proceed without a valid key.

  2. Check CLERK_BAPI_SCOPES — run:

    echo $CLERK_BAPI_SCOPES
    

    Inspect the output. If scopes are missing or do not include the required write permission, tell the user: "This is a write operation and your current scopes may not allow it. Rerun with --admin to bypass?" Do NOT attempt the request and fail — ask first.

  3. For DELETE requests: warn explicitly that the action is IRREVERSIBLE and list exactly what data will be permanently destroyed (user record, all sessions, all memberships, all associated data). Require explicit confirmation before proceeding. This warning is MANDATORY — never skip it.

  4. For metadata operations: always explain which metadata type is being used and why (see Metadata types section below).


FAST PATH: Common operations (use directly, no spec fetching needed)

For the operations below, skip spec fetching and execute immediately using these exact templates. Substitute $CLERK_SECRET_KEY, $USER_ID, $ORG_ID, $EMAIL as needed from the user's context.

Create organization + invite member (two-step)

# Step 1 — Create organization
ORG=$(curl -s -X POST "https://api.clerk.com/v1/organizations" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"Acme Corp\", \"created_by\": \"$USER_ID\"}")
echo "$ORG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d, indent=2))"

# Step 2 — Extract org ID
ORG_ID=$(echo "$ORG" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

# Step 3 — Invite member with role
curl -s -X POST "https://api.clerk.com/v1/organizations/${ORG_ID}/invitations" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"email_address\": \"user@example.com\", \"role\": \"org:admin\"}" \
  | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"

Roles: use "org:admin" or "org:member" (always prefix with org:).

SDK equivalent (for Next.js / TypeScript projects with @clerk/nextjs or @clerk/backend)

import { clerkClient } from '@clerk/nextjs/server'
// OR if using @clerk/backend directly:
// import { createClerkClient } from '@clerk/backend'
// const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })

// Step 1: Create organization
const org = await clerkClient.organizations.createOrganization({
  name: 'Acme Corp',
  createdBy: userId,  // required — the ID of the user creating the org
})

// Step 2: Invite member to the org
const invitation = await clerkClient.organizations.createOrganizationInvitation({
  organizationId: org.id,
  emailAddress: 'user@example.com',
  role: 'org:admin',  // or 'org:member'
})

Update user metadata

Always explain the three metadata types before asking which to use:

Type Field Readable by Writable by Use for
Public public_metadata Client + Server Server only Plan tier, roles, feature flags the frontend reads
Private private_metadata Server only Server only Stripe IDs, compliance flags, internal identifiers
Unsafe unsafe_metadata Client + Server Client + Server Ephemeral UI state, onboarding steps (client-writable — avoid sensitive data)

For plan: 'pro' and onboarded: true — use public_metadata (frontend-readable, server-writable):

curl -s -X PATCH "https://api.clerk.com/v1/users/${USER_ID}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"public_metadata": {"plan": "pro", "onboarded": true}}' \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Updated user {d[\"id\"]}: public_metadata={d.get(\"public_metadata\")}')"

SDK equivalent:

import { clerkClient } from '@clerk/nextjs/server'
// OR: import { createClerkClient } from '@clerk/backend'

await clerkClient.users.updateUser(userId, {
  publicMetadata: { plan: 'pro', onboarded: true },   // readable by client, writable server-only
  // privateMetadata: { stripeId: 'cus_xxx' },         // server-only read AND write
  // unsafeMetadata: { step: 'welcome' },              // client-writable, avoid sensitive data
})

Note: REST API uses snake_case (public_metadata). SDK uses camelCase (publicMetadata).

List users (last 7 days)

curl -s "https://api.clerk.com/v1/users?limit=100&offset=0&order_by=-created_at&created_at=gt:$(date -d '7 days ago' +%s 2>/dev/null || date -v-7d +%s)000" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
    print(f'Found {len(data)} users:')
    for u in data:
        print(f'  {u[\"id\"]}: {u.get(\"email_addresses\", [{}])[0].get(\"email_address\", \"no email\")}')
else:
    print(json.dumps(data, indent=2))
"

Delete user (confirm required)

# ONLY run after explicit user confirmation
curl -s -X DELETE "https://api.clerk.com/v1/users/${USER_ID}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Deleted: {d}')"

Clerk Backend API — Full Endpoint Reference

Base URL: https://api.clerk.com/v1 Auth: Authorization: Bearer $CLERK_SECRET_KEY on every request.

Users

List users

GET /v1/users
Query params: limit (max 500, default 10), offset, order_by (+/-created_at, +/-updated_at, +/-email_address, +/-web3wallet, +/-first_name, +/-last_name, +/-phone_number, +/-username, +/-last_active_at, +/-last_sign_in_at), email_address[], phone_number[], username[], web3wallet[], user_id[], query, created_at (ISO 8601 range: gt:TIMESTAMP or lt:TIMESTAMP in Unix ms)
Returns: array of User objects

Get user

GET /v1/users/{user_id}
Returns: User object

Update user

PATCH /v1/users/{user_id}
Body (JSON, snake_case): { public_metadata, private_metadata, unsafe_metadata, first_name, last_name, username, ... }

Delete user — IRREVERSIBLE

DELETE /v1/users/{user_id}
Destroys: user record, all sessions, all memberships, all associated data
Returns: { id, object, deleted: true }

Always warn the user this is permanent and confirm before proceeding.

Organizations

Create organization

POST /v1/organizations
Body: { name: string, created_by: string (user_id), public_metadata?, private_metadata?, max_allowed_memberships? }
Returns: Organization object with { id, name, slug, ... }

List organizations

GET /v1/organizations
Query params: limit, offset, query, order_by

Invite member

POST /v1/organizations/{organization_id}/invitations
Body: { email_address: string, role: string ("org:admin" or "org:member"), public_metadata?, private_metadata? }
Returns: OrganizationInvitation object

How to execute requests

ALWAYS execute requests with direct curl commands. Use the spec-extraction scripts (api-specs-context.sh, extract-tags.js, extract-endpoint-detail.sh) to discover endpoints, but make actual API calls with curl. Do NOT use scripts/execute-request.sh — it's a local dev helper, not for agent use.

Template for GET requests:

curl -s "https://api.clerk.com/v1${PATH}${QUERY_STRING}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY"

Template for POST/PATCH requests:

curl -s -X ${METHOD} "https://api.clerk.com/v1${PATH}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '${BODY_JSON}'

Template for DELETE requests:

curl -s -X DELETE "https://api.clerk.com/v1${PATH}" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY"

After getting the response: Parse and display it clearly. Use python3 -c "import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2))" to pretty-print JSON. Extract key fields (id, email, name, etc.) and summarize them for the user.


API specs context

Before doing anything outside the FAST PATH, fetch the available spec versions and tags by running:

bash scripts/api-specs-context.sh

Use the output to determine the latest version and available tags.

Caching: If you already fetched the spec context earlier in this conversation, do NOT fetch it again. Reuse the version and tags from the previous call.


Rules

  • For common operations (list users, create org, invite, update metadata, delete user): use the FAST PATH above — do NOT fetch specs first.
  • Always disregard endpoints/schemas related to platform.
  • Always confirm before performing write requests (POST/PUT/PATCH/DELETE).
  • For DELETE operations, always warn the user that the action is irreversible and mention what data will be lost (user record, sessions, memberships). This warning is MANDATORY — never skip it.
  • For write operations (POST/PUT/PATCH/DELETE), check CLERK_BAPI_SCOPES before attempting the request. If missing or insufficient, ask the user upfront. Do NOT attempt and fail — ask before executing. This check is MANDATORY.
  • For metadata operations, always explain all three types (public, private, unsafe) and recommend the appropriate one.
  • Pagination: always use limit + offset and mention that results may be paginated for large datasets.
  • Use direct curl commands for all API calls — never use scripts/execute-request.sh.

Rate Limits & Gotchas

Rate Limits

Environment Limit
Production 1,000 requests / 10 seconds
Development 100 requests / 10 seconds
Single invitations 100 / hour
Bulk invitations 25 / hour
Org invitations 250 / hour
Frontend API sign-in creation 5 / 10 seconds
Frontend API sign-in attempts 3 / 10 seconds
List users max per page 500

currentUser() makes a real API call that counts against rate limits. Use auth() for just the session claims — it reads from the token without an API call.

Metadata Overwrites (Not Merges)

updateUser({ publicMetadata: { role: 'admin' } }) REPLACES all public metadata, not merges. To add a field without losing existing data: read first, spread, then write.

Wrong:

await clerkClient.users.updateUser(userId, { publicMetadata: { newField: 'value' } })

This DELETES all other publicMetadata fields.

Right:

const user = await clerkClient.users.getUser(userId)
await clerkClient.users.updateUser(userId, {
  publicMetadata: { ...user.publicMetadata, newField: 'value' },
})

Modes

Determine the active mode based on the user prompt in Options context:

Mode Trigger Behavior
help Prompt is empty, or contains only help / -h / --help Print usage examples (step 0)
browse Prompt is tags, or a tag name (e.g. Users) List all tags or endpoints for a tag
execute Specific endpoint (e.g. GET /users) or natural language action (e.g. "get user john_doe") Look up endpoint, execute request
detail Endpoint + help / -h / --help (e.g. GET /users help) Show endpoint schema, don't execute

Your Task

Use the LATEST VERSION from API specs context by default. If the user specifies a different version (e.g. --version 2024-10-01), use that version instead.

Determine the active mode, then follow the applicable steps below.


0. Print usage

Modes: help only — Skip for browse, execute, and detail.

Print the following examples to the user verbatim:

Browse
  /clerk-backend-api tags                         — list all tags
  /clerk-backend-api Users                        — browse endpoints for the Users tag
  /clerk-backend-api Users version 2025-11-10.yml — browse using a different version

Execute
  /clerk-backend-api GET /users             — fetch all users
  /clerk-backend-api get user john_doe      — natural language works too
  /clerk-backend-api POST /invitations      — create an invitation

Inspect
  /clerk-backend-api GET /users help        — show endpoint schema without executing
  /clerk-backend-api POST /invitations -h   — view request/response details

Options
  --admin                            — bypass scope restrictions for write/delete
  --version [date], version [date]   — use a specific spec version
  --help, -h, help                   — inspect endpoint instead of executing

Stop here.


1. Fetch tags

Modes: browse (when prompt is tags or no tag specified) — Skip for help, execute, and detail.

If using a non-latest version, fetch tags for that version:

curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | node scripts/extract-tags.js

Otherwise, use the TAGS already in API specs context.

Share tags in a table and prompt the user to select a query.


2. Fetch tag endpoints

Modes: browse (when a tag name is provided) — Skip for help, execute, and detail.

Fetch all endpoints for the identified tag:

curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | bash scripts/extract-tag-endpoints.sh "${tag_name}"

Share the results (endpoints, schemas, parameters) with the user.


3. Fetch endpoint detail

Modes: execute, detailSkip for help and browse.

For natural language prompts in execute mode, first check if the operation matches a FAST PATH entry above. If it does, skip this step and proceed directly to step 4 using the FAST PATH template.

For other endpoints, identify the matching endpoint by searching the tags in context. Fetch tag endpoints if needed to resolve the exact path and method.

Extract the full endpoint definition:

curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | bash scripts/extract-endpoint-detail.sh "${path}" "${method}"
  • ${path} — e.g. /users/{user_id}
  • ${method} — lowercase, e.g. get

detail mode: Share the endpoint definition and schemas with the user. Stop here.

execute mode: Continue to step 4.


4. Execute request

Modes: execute only.

  1. Run the mandatory checks from the CRITICAL section above.
  2. Identify required and optional parameters from the spec (step 3) or FAST PATH.
  3. Ask the user for any required path/query/body parameters that weren't provided.
  4. Build and execute a direct curl command (see How to execute requests above). Do NOT use scripts/execute-request.sh.
  5. Parse the JSON response and display it clearly. Extract and summarize key fields for the user.

Example — list users and parse response:

RESPONSE=$(curl -s "https://api.clerk.com/v1/users?limit=10" \
  -H "Authorization: Bearer $CLERK_SECRET_KEY")
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
    print(f'Found {len(data)} users:')
    for u in data:
        print(f'  {u[\"id\"]}: {u.get(\"email_addresses\", [{}])[0].get(\"email_address\", \"no email\")}')
else:
    print(json.dumps(data, indent=2))
"

See Also

  • clerk-setup - Initial Clerk install
  • clerk-orgs - Manage organizations via API
  • clerk-webhooks - Real-time event sync
提供Clerk自定义认证流程和外观定制能力。涵盖useSignIn/useSignUp钩子构建登录注册界面,以及通过主题、变量和CSS实现组件样式与品牌视觉的个性化配置。
需要自定义Clerk登录或注册界面 调整Clerk组件的颜色、字体或布局样式 品牌化认证流程外观
.agents/skills/clerk-custom-ui/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill clerk-custom-ui -g -y
SKILL.md
Frontmatter
{
    "name": "clerk-custom-ui",
    "license": "MIT",
    "metadata": {
        "author": "clerk",
        "version": "2.3.0"
    },
    "description": "Custom authentication flows and component appearance - hooks (useSignIn, useSignUp), themes, colors, fonts, CSS. Use for custom sign-in\/sign-up flows, appearance styling, visual customization, branding.",
    "allowed-tools": "WebFetch"
}

Custom UI

Prerequisite: Ensure ClerkProvider wraps your app. See clerk-setup skill.

Version: Check package.json for the SDK version — see clerk skill for the version table. This determines which custom flow references to use below.

This skill covers two areas:

  1. Custom authentication flows — build your own sign-in/sign-up UI with hooks
  2. Appearance customization — theme, style, and brand Clerk's pre-built components

What Do You Need?

Task Reference
Custom sign-in (Core 2 / LTS) core-2/custom-sign-in.md
Custom sign-up (Core 2 / LTS) core-2/custom-sign-up.md
Custom sign-in (Current SDK v7+) core-3/custom-sign-in.md
Custom sign-up (Current SDK v7+) core-3/custom-sign-up.md
Show component pattern (Current SDK) core-3/show-component.md

Custom Flow References

Task Core 2 Current
Custom sign-in (useSignIn) core-2/custom-sign-in.md core-3/custom-sign-in.md
Custom sign-up (useSignUp) core-2/custom-sign-up.md core-3/custom-sign-up.md
<Show> component (use <SignedIn>, <SignedOut>, <Protect>) core-3/show-component.md

Appearance Customization

Appearance customization applies to both Core 2 and the current SDK.

Component Customization Options

Task Documentation
Appearance prop overview https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/overview
Options (structure, logo, buttons) https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/layout
Themes (pre-built dark/light) https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/themes
Variables (colors, fonts, spacing) https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/variables
CAPTCHA configuration https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/captcha
Bring your own CSS https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/bring-your-own-css

Appearance Pattern

<SignIn
  appearance={{
    variables: {
      colorPrimary: '#0000ff',
      borderRadius: '0.5rem',
    },
    options: {
      logoImageUrl: '/logo.png',
      socialButtonsVariant: 'iconButton',
    },
  }}
/>

Core 2 ONLY (skip if current SDK): The options property was named layout. Use layout: { logoImageUrl: '...', socialButtonsVariant: '...' } instead of options.

variables (colors, typography, borders)

Property Description
colorPrimary Primary color throughout
colorBackground Background color
borderRadius Border radius (default: 0.375rem)

Opacity change: colorRing and colorModalBackdrop now render at full opacity. Use explicit rgba() values if you need transparency.

Core 2 ONLY (skip if current SDK): colorRing and colorModalBackdrop rendered at 15% opacity by default.

options (structure, logo, social buttons)

Property Description
logoImageUrl URL to custom logo
socialButtonsVariant 'blockButton' | 'iconButton' | 'auto'
socialButtonsPlacement 'top' | 'bottom'
showOptionalFields Show optional fields (default: false)

Core 2 ONLY (skip if current SDK): This property is called layout, not options. Also, showOptionalFields defaulted to true.

Themes

Install themes from @clerk/ui:

npm install @clerk/ui
import { dark } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: dark }} />

Core 2 ONLY (skip if current SDK): Themes are imported from @clerk/themes (install @clerk/themes instead of @clerk/ui). Example: import { dark } from '@clerk/themes'.

Theme stacking — pass an array where the last theme takes precedence:

import { dark, neobrutalism } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: [dark, neobrutalism] }} />

CSS color-scheme support — the default theme respects the CSS color-scheme property for automatic light/dark mode toggling.

Available themes: dark, neobrutalism, shadcn, simple

shadcn Theme

IMPORTANT: If the project has components.json (shadcn/ui installed), ALWAYS apply the shadcn theme as the FIRST customization step. This ensures Clerk components visually match the existing shadcn design system. Skip this only if the user explicitly requests a different theme.

If the project uses shadcn/ui (check for components.json in the project root), always use the shadcn theme:

import { shadcn } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: shadcn }} />

Also import shadcn CSS in your global styles:

@import 'tailwindcss';
@import '@clerk/ui/themes/shadcn.css';

Core 2 ONLY (skip if current SDK): Import from @clerk/themes and @clerk/themes/shadcn.css:

import { shadcn } from '@clerk/themes'
@import '@clerk/themes/shadcn.css';

Workflow

  1. Identify customization needs (custom flow or appearance)
  2. For custom flows: check SDK version → read appropriate core-2/ or core-3/ reference
  3. For appearance: WebFetch the appropriate documentation from table above
  4. Apply appearance prop to your Clerk components or build custom flow with hooks

Common Pitfalls

Issue Solution
Colors not applying Use colorPrimary not primaryColor
Logo not showing Put logoImageUrl inside options: {} (or layout: {} in Core 2)
Social buttons wrong Add socialButtonsVariant: 'iconButton' in options (or layout in Core 2)
Styling not working Use appearance prop, not direct CSS (unless with bring-your-own-css)
Hook returns different shape Check SDK version — Core 2 and current have completely different useSignIn/useSignUp APIs

See Also

  • clerk-setup - Initial Clerk install
  • clerk-nextjs-patterns - Next.js patterns
  • clerk-orgs - B2B organizations
提供 Next.js 与 Clerk 集成的高级模式指南,涵盖服务端与客户端认证差异、中间件配置策略、Server Actions 保护及用户级缓存机制。
Next.js 项目集成 Clerk 认证 配置 Clerk 中间件或 Server Actions 解决 Next.js 服务端组件中 auth() 未 await 导致的错误 实现基于 userId 的 API 数据缓存
.agents/skills/clerk-nextjs-patterns/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill clerk-nextjs-patterns -g -y
SKILL.md
Frontmatter
{
    "name": "clerk-nextjs-patterns",
    "license": "MIT",
    "metadata": {
        "author": "clerk",
        "version": "2.2.0"
    },
    "description": "Advanced Next.js patterns - middleware, Server Actions, caching with Clerk.",
    "allowed-tools": "WebFetch"
}

Next.js Patterns

Version: Check package.json for the SDK version — see clerk skill for the version table. Core 2 differences are noted inline with > **Core 2 ONLY (skip if current SDK):** callouts.

For basic setup, see clerk-setup skill.

What Do You Need?

Task Reference
Server vs client auth (auth() vs hooks) references/server-vs-client.md
Configure middleware (public-first vs protected-first) references/middleware-strategies.md
Protect Server Actions references/server-actions.md
API route auth (401 vs 403) references/api-routes.md
Cache auth data (user-scoped caching) references/caching-auth.md

References

Reference Description
references/server-vs-client.md await auth() vs hooks
references/middleware-strategies.md Public-first vs protected-first, proxy.ts (Next.js <=15: middleware.ts)
references/server-actions.md Protect mutations
references/api-routes.md 401 vs 403
references/caching-auth.md User-scoped caching

Mental Model

Server vs Client = different auth APIs:

  • Server: await auth() from @clerk/nextjs/server (async!)
  • Client: useAuth() hook from @clerk/nextjs (sync)

Never mix them. Server Components use server imports, Client Components use hooks.

Key properties from auth():

  • isAuthenticated — boolean, replaces the !!userId pattern
  • sessionStatus'active' | 'pending', for detecting incomplete session tasks
  • userId, orgId, orgSlug, has(), protect() — unchanged

Core 2 ONLY (skip if current SDK): isAuthenticated and sessionStatus are not available. Check !!userId instead.

Minimal Pattern

// Server Component
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  const { isAuthenticated, userId } = await auth()  // MUST await!
  if (!isAuthenticated) return <p>Not signed in</p>
  return <p>Hello {userId}</p>
}

Core 2 ONLY (skip if current SDK): isAuthenticated is not available. Use if (!userId) instead.

Conditional Rendering with <Show>

For client-side conditional rendering based on auth state:

import { Show } from '@clerk/nextjs'

<Show when="signed-in" fallback={<p>Please sign in</p>}>
  <Dashboard />
</Show>

Core 2 ONLY (skip if current SDK): Use <SignedIn> and <SignedOut> components instead of <Show>. See clerk-custom-ui skill, core-3/show-component.md for the full migration table.

Common Pitfalls

Symptom Cause Fix
undefined userId in Server Component Missing await await auth() not auth()
Auth not working on API routes Missing matcher Add `'/(api
Cache returns wrong user's data Missing userId in key Include userId in unstable_cache key
Mutations bypass auth Unprotected Server Action Check auth() at start of action
Wrong HTTP error code Confused 401/403 401 = not signed in, 403 = no permission

Session Tokens & Custom JWTs

getToken() for external APIs

Pass a custom JWT to third-party services (Hasura, Supabase, etc.) using JWT templates defined in the Clerk dashboard.

Server-side (Server Component or Route Handler):

import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  const { getToken } = await auth()
  const token = await getToken({ template: 'hasura' })
  if (!token) return <p>Not authenticated</p>

  const res = await fetch('https://api.example.com/graphql', {
    headers: { Authorization: `Bearer ${token}` },
  })
  const data = await res.json()
  return <pre>{JSON.stringify(data)}</pre>
}

Client-side (Client Component):

'use client'
import { useAuth } from '@clerk/nextjs'

export function DataFetcher() {
  const { getToken } = useAuth()

  async function fetchData() {
    const token = await getToken({ template: 'supabase' })
    if (!token) return

    const res = await fetch('https://api.example.com/data', {
      headers: { Authorization: `Bearer ${token}` },
    })
    return res.json()
  }

  return <button onClick={fetchData}>Fetch</button>
}

getToken() returns null when the user is not authenticated — always null-check before use.

useSession() for session data

Access session metadata in client components:

'use client'
import { useSession } from '@clerk/nextjs'

export function SessionInfo() {
  const { session } = useSession()
  if (!session) return null

  return (
    <p>
      Session {session.id} — last active: {session.lastActiveAt.toISOString()}
    </p>
  )
}

Manual JWT verification (no Clerk middleware)

For standalone API servers that receive Clerk session tokens from the Authorization header or the __session cookie (same-origin).

Using @clerk/backend verifyToken (recommended):

import { verifyToken } from '@clerk/backend'

const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) return res.status(401).json({ error: 'No token' })

try {
  const claims = await verifyToken(token, {
    jwtKey: process.env.CLERK_JWT_KEY,
  })
  // claims.sub = userId
} catch {
  return res.status(401).json({ error: 'Invalid token' })
}

Using jsonwebtoken (when you can't use @clerk/backend):

import jwt from 'jsonwebtoken'

const publicKey = process.env.CLERK_PEM_PUBLIC_KEY!.replace(/\\n/g, '\n')
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) return res.status(401).json({ error: 'No token' })

try {
  const claims = jwt.verify(token, publicKey, { algorithms: ['RS256'] }) as jwt.JwtPayload
  // Manually check exp and nbf (jsonwebtoken does this automatically, but verify azp if needed)
  // claims.sub = userId
} catch {
  return res.status(401).json({ error: 'Invalid or expired token' })
}

Token sources:

  • Same-origin requests: __session cookie (Clerk sets this automatically)
  • Cross-origin / mobile / API-to-API: Authorization: Bearer <token> header

CRITICAL: Always check exp and nbf claims. verifyToken from @clerk/backend handles this automatically; with raw jsonwebtoken, set ignoreExpiration: false (default) and ensure clockTolerance is minimal.

See Also

  • clerk-setup
  • clerk-orgs

Docs

Next.js SDK

通过检测项目框架并获取官方快速入门指南,自动为各类前端及后端项目集成 Clerk 认证服务,支持处理现有鉴权迁移及 shadcn/ui 主题配置。
用户请求添加 Clerk 认证 用户请求添加身份验证
.agents/skills/clerk-setup/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill clerk-setup -g -y
SKILL.md
Frontmatter
{
    "name": "clerk-setup",
    "license": "MIT",
    "metadata": {
        "author": "clerk",
        "version": "2.3.0"
    },
    "description": "Add Clerk authentication to any project by following the official quickstart guides.",
    "allowed-tools": "WebFetch"
}

Adding Clerk

Version: Check package.json for the SDK version — see clerk skill for the version table. Core 2 differences are noted inline with > **Core 2 ONLY (skip if current SDK):** callouts.

This skill sets up Clerk for authentication by following the official quickstart documentation.

Quick Reference

Step Action
1. Detect framework Check package.json dependencies
2. Fetch quickstart Use WebFetch on the appropriate docs URL
3. Follow instructions Execute steps; create proxy.ts (Next.js <=15: middleware.ts)
4. Get API keys From dashboard.clerk.com

If the project has components.json (shadcn/ui), apply the shadcn theme after setup. See clerk-custom-ui skill → shadcn Theme.

Framework Detection

Check package.json to identify the framework:

Dependency Framework Quickstart URL
next Next.js https://clerk.com/docs/nextjs/getting-started/quickstart
@remix-run/react Remix https://clerk.com/docs/remix/getting-started/quickstart
astro Astro https://clerk.com/docs/astro/getting-started/quickstart
nuxt Nuxt https://clerk.com/docs/nuxt/getting-started/quickstart
react-router React Router https://clerk.com/docs/react-router/getting-started/quickstart
@tanstack/react-start TanStack Start https://clerk.com/docs/tanstack-react-start/getting-started/quickstart
react (no framework) React SPA https://clerk.com/docs/react/getting-started/quickstart
vue Vue https://clerk.com/docs/vue/getting-started/quickstart
express Express https://clerk.com/docs/expressjs/getting-started/quickstart
fastify Fastify https://clerk.com/docs/fastify/getting-started/quickstart
expo Expo https://clerk.com/docs/expo/getting-started/quickstart

For other platforms:

  • Chrome Extension: https://clerk.com/docs/chrome-extension/getting-started/quickstart
  • Android: https://clerk.com/docs/android/getting-started/quickstart
  • iOS: https://clerk.com/docs/ios/getting-started/quickstart
  • Vanilla JavaScript: https://clerk.com/docs/js-frontend/getting-started/quickstart

Decision Tree

User Request: "Add Clerk" / "Add authentication"
    │
    ├─ Read package.json
    │
    ├─ Existing auth detected?
    │   ├─ YES → Audit → Migration plan
    │   └─ NO → Fresh install
    │
    ├─ Identify framework → WebFetch quickstart → Follow instructions
    │   └─ Next.js? → Create proxy.ts (Next.js <=15: middleware.ts)
    │
    └─ components.json exists? → YES → Apply shadcn theme (see clerk-custom-ui)

Setup Process

1. Detect the Framework

Read the project's package.json and match dependencies to the table above.

2. Fetch the Quickstart Guide

Use WebFetch to retrieve the official quickstart for the detected framework:

WebFetch: https://clerk.com/docs/{framework}/getting-started/quickstart
Prompt: "Extract the complete setup instructions including all code snippets, file paths, and configuration steps."

3. Follow the Instructions

Execute each step from the quickstart guide:

  • Install the required packages
  • Set up environment variables
  • Add the provider and proxy/middleware
  • Create sign-in/sign-up routes if needed
  • Test the integration

Next.js: Create proxy.ts (Next.js <=15: middleware.ts). See the clerk-nextjs-patterns skill for middleware strategies.

shadcn/ui detected (components.json exists): ALWAYS apply the shadcn theme. See clerk-custom-ui skill → shadcn Theme section.

4. Get API Keys

Two paths for development API keys:

Keyless (Automatic)

  • On first SDK initialization, Clerk auto-generates dev keys and shows "Claim your application" popover
  • No manual key setup required—keys are created and injected automatically
  • Simplest path for new projects

Manual (Dashboard)

  • Get keys from dashboard.clerk.com if Keyless doesn't trigger
  • Publishable Key: Starts with pk_test_ or pk_live_
  • Secret Key: Starts with sk_test_ or sk_live_
  • Set as environment variables: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY

Migrating from Another Auth Provider

If the project already has authentication, create a migration plan before replacing it.

Detect Existing Auth

Check package.json for existing auth libraries:

  • next-auth / @auth/core → NextAuth/Auth.js
  • @supabase/supabase-js → Supabase Auth
  • firebase / firebase-admin → Firebase Auth
  • @aws-amplify/auth → AWS Cognito
  • auth0 / @auth0/nextjs-auth0 → Auth0
  • passport → Passport.js
  • Custom JWT/session implementation

Migration Process

  1. Audit current auth - Identify all auth touchpoints:

    • Sign-in/sign-up pages
    • Session/token handling
    • Protected routes and middleware
    • User data storage (database tables, external IDs)
    • OAuth providers configured
  2. Create migration plan - Consider:

    • User data export - Export users and import via Clerk's Backend API
    • Password hashes - Clerk can upgrade hashes to Bcrypt transparently
    • External IDs - Store legacy user IDs as external_id in Clerk
    • Session handling - Existing sessions will terminate on switch
  3. Choose migration strategy:

    • Big bang - Switch all users at once (simpler, requires maintenance window)
    • Trickle migration - Run both systems temporarily (lower risk, higher complexity)

Migration Reference

SDK Notes

Package Names

Package Install
Next.js @clerk/nextjs
React @clerk/react
Expo @clerk/expo
React Router @clerk/react-router
TanStack Start @clerk/tanstack-react-start

Core 2 ONLY (skip if current SDK): React and Expo packages have different names: @clerk/clerk-react and @clerk/clerk-expo (with clerk- prefix).

ClerkProvider Placement (Next.js)

ClerkProvider must be placed inside <body>, not wrapping <html>:

// root layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ClerkProvider>{children}</ClerkProvider>
      </body>
    </html>
  )
}

Core 2 ONLY (skip if current SDK): ClerkProvider can wrap <html> directly.

Dynamic Rendering (Next.js)

For dynamic rendering with auth data, use the dynamic prop:

<ClerkProvider dynamic>{children}</ClerkProvider>

Node.js Requirement

Requires Node.js 20.9.0 or higher.

Core 2 ONLY (skip if current SDK): Minimum Node.js 18.17.0.

Themes Package

Themes are installed from @clerk/ui:

npm install @clerk/ui

Core 2 ONLY (skip if current SDK): Themes are from @clerk/themes instead of @clerk/ui.

shadcn Theme

If the project uses shadcn/ui (check for components.json in the project root), apply the shadcn theme so Clerk components match the app's design system:

npm install @clerk/ui
import { shadcn } from '@clerk/ui/themes'

<ClerkProvider appearance={{ theme: shadcn }}>{children}</ClerkProvider>

Also import the shadcn CSS in your global styles:

@import 'tailwindcss';
@import '@clerk/ui/themes/shadcn.css';

Core 2 ONLY (skip if current SDK): Import from @clerk/themes and @clerk/themes/shadcn.css instead.

Common Pitfalls

Level Issue Solution
CRITICAL Missing await on auth() In Next.js 15+, auth() is async: const { userId } = await auth()
CRITICAL Exposing CLERK_SECRET_KEY Never use secret key in client code; only NEXT_PUBLIC_* keys are safe
HIGH Missing middleware matcher Include API routes: `matcher: ['/((?!.\..
HIGH ClerkProvider placement Must be inside <body> in root layout (Core 2: could wrap <html>)
HIGH Auth routes not public Allow /sign-in, /sign-up in middleware config
HIGH Landing page requires auth To keep "/" public, exclude it: `matcher: ['/((?!.\..
MEDIUM Wrong import path Server code uses @clerk/nextjs/server, client uses @clerk/nextjs
MEDIUM Wrong package name Use @clerk/react not @clerk/clerk-react (Core 2 naming)

See Also

  • clerk-custom-ui - Custom sign-in/up components
  • clerk-nextjs-patterns - Advanced Next.js patterns
  • clerk-react-patterns - React SPA patterns
  • clerk-react-router-patterns - React Router patterns
  • clerk-vue-patterns - Vue patterns
  • clerk-nuxt-patterns - Nuxt patterns
  • clerk-astro-patterns - Astro patterns
  • clerk-tanstack-patterns - TanStack Start patterns
  • clerk-expo-patterns - Expo patterns
  • clerk-chrome-extension-patterns - Chrome Extension patterns
  • clerk-orgs - B2B multi-tenant organizations
  • clerk-webhooks - Webhook → database sync
  • clerk-testing - E2E testing setup
  • clerk-swift - Native iOS auth
  • clerk-android - Native Android auth
  • clerk-backend-api - Backend REST API explorer

Documentation

Clerk认证路由器,根据任务自动路由至具体技能。支持添加认证、自定义UI、多框架模式(Next.js/React/Vue等)、B2B组织、Webhooks及测试。
添加Clerk认证 设置Clerk环境 自定义登录注册流程 特定框架集成模式 B2B组织管理 Webhooks配置 E2E测试
.agents/skills/clerk/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill clerk -g -y
SKILL.md
Frontmatter
{
    "name": "clerk",
    "license": "MIT",
    "metadata": {
        "version": "2.0.0"
    },
    "description": "Clerk authentication router. Use when user asks about adding authentication, setting up Clerk, custom sign-in flows, Swift or native iOS auth, native Android auth, Next.js patterns, React patterns, Vue patterns, Nuxt patterns, Astro patterns, TanStack Start patterns, Expo patterns, React Router patterns, Chrome Extension patterns, organizations, syncing users, or testing. Automatically routes to the specific skill based on their task."
}

Clerk Skills Router

Version Detection

Check package.json to determine the Clerk SDK version. This determines which patterns to use:

Package Core 2 (LTS until Jan 2027) Current
@clerk/nextjs v5–v6 v7+
@clerk/react or @clerk/clerk-react v5–v6 v7+
@clerk/expo or @clerk/clerk-expo v1–v2 v3+
@clerk/react-router v1–v2 v3+
@clerk/tanstack-react-start < v0.26.0 v0.26.0+

Default to current if the version is unclear or the project is new. Core 2 packages use @clerk/clerk-react and @clerk/clerk-expo (with clerk- prefix); current packages use @clerk/react and @clerk/expo.

All skills are written for the current SDK. When something differs in Core 2, it's noted inline with > **Core 2 ONLY (skip if current SDK):** callouts. The exception is clerk-custom-ui, which has separate core-2/ and core-3/ directories for custom flow hooks since those APIs are entirely different between versions.


By Task

Adding Clerk to your project → Use clerk-setup

  • Framework detection and quickstart
  • Environment setup, API keys, Keyless flow
  • Migration from other auth providers

Custom sign-in/sign-up UI → Use clerk-custom-ui

  • Custom authentication flows with useSignIn / useSignUp hooks
  • Appearance and styling (themes, colors, layout)
  • <Show> component for conditional rendering

Advanced Next.js patterns → Use clerk-nextjs-patterns

  • Server vs Client auth APIs
  • Middleware strategies
  • Server Actions, caching
  • API route protection

React patterns → Use clerk-react-patterns

  • Hooks (useAuth, useUser, useClerk)
  • Protected routes, auth guards
  • Router integration

React Router patterns → Use clerk-react-router-patterns

  • Loaders & actions with auth
  • Route protection
  • SSR auth

Vue patterns → Use clerk-vue-patterns

  • Composables (useAuth, useUser, useClerk)
  • Vue Router guards
  • Pinia auth store integration

Nuxt patterns → Use clerk-nuxt-patterns

  • Server middleware auth
  • SSR auth with composables
  • Server API routes

Astro patterns → Use clerk-astro-patterns

  • SSR auth pages
  • Island components with React
  • Middleware & API routes

TanStack Start patterns → Use clerk-tanstack-patterns

  • Server functions with auth
  • Route protection via loaders
  • Vinxi server integration

Expo patterns → Use clerk-expo-patterns

  • Secure token storage
  • OAuth deep linking
  • Push notifications with auth

Chrome Extension patterns → Use clerk-chrome-extension-patterns

  • Background scripts auth
  • Popup auth flows
  • Content scripts with sync host

B2B / Organizations → Use clerk-orgs

  • Multi-tenant apps
  • Organization slugs in URLs
  • Roles, permissions, RBAC
  • Member management

Webhooks → Use clerk-webhooks

  • Real-time events
  • Data syncing
  • Notifications & integrations

E2E Testing → Use clerk-testing

  • Playwright/Cypress setup
  • Auth flow testing
  • Test utilities

Swift / native iOS auth → Use clerk-swift

  • Native iOS Swift and SwiftUI projects
  • ClerkKit and ClerkKitUI implementation guidance
  • Source-driven patterns from clerk-ios

Android / native mobile auth → Use clerk-android

  • Native Android Kotlin and Jetpack Compose projects
  • clerk-android-api and clerk-android-ui implementation guidance
  • Source-driven patterns from clerk-android
  • Do not use for Expo or React Native projects

Backend REST API → Use clerk-backend-api

  • Browse API tags and endpoints
  • Inspect endpoint schemas
  • Execute API requests with scope enforcement

Quick Navigation

If you know your task, you can directly access:

  • /clerk-setup - Framework setup
  • /clerk-custom-ui - Custom flows & appearance
  • /clerk-nextjs-patterns - Next.js patterns
  • /clerk-react-patterns - React patterns
  • /clerk-react-router-patterns - React Router patterns
  • /clerk-vue-patterns - Vue patterns
  • /clerk-nuxt-patterns - Nuxt patterns
  • /clerk-astro-patterns - Astro patterns
  • /clerk-tanstack-patterns - TanStack Start patterns
  • /clerk-expo-patterns - Expo patterns
  • /clerk-chrome-extension-patterns - Chrome Extension patterns
  • /clerk-orgs - Organizations
  • /clerk-webhooks - Webhooks
  • /clerk-testing - Testing
  • /clerk-swift - Swift/native iOS
  • /clerk-android - Native Android
  • /clerk-backend-api - Backend REST API

Or describe what you need and I'll recommend the right one.

提供 Prisma CLI 命令参考,涵盖项目初始化、客户端生成、数据库迁移、本地开发及调试等场景的最佳实践与用法指南。
prisma init prisma generate prisma migrate prisma db prisma studio prisma mcp
.agents/skills/prisma-cli/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-cli -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-cli",
    "license": "MIT",
    "metadata": {
        "author": "prisma",
        "version": "7.6.0"
    },
    "description": "Prisma CLI commands reference covering all available commands, options, and usage patterns. Use when running Prisma CLI commands, setting up projects, generating client, running migrations, managing databases, or starting Prisma's MCP server. Triggers on \"prisma init\", \"prisma generate\", \"prisma migrate\", \"prisma db\", \"prisma studio\", \"prisma mcp\"."
}

Prisma CLI Reference

Complete reference for all Prisma CLI commands. This skill provides guidance on command usage, options, and best practices for current Prisma releases.

When to Apply

Reference this skill when:

  • Setting up a new Prisma project (prisma init)
  • Generating Prisma Client (prisma generate)
  • Running database migrations (prisma migrate)
  • Managing database state (prisma db push/pull)
  • Using local development database (prisma dev)
  • Debugging Prisma issues (prisma debug)

Rule Categories by Priority

Priority Category Impact Prefix
1 Setup HIGH init
2 Generation HIGH generate
3 Development HIGH dev
4 Database HIGH db-
5 Migrations CRITICAL migrate-
6 Utility MEDIUM studio, validate, format, debug, mcp

Command Categories

Category Commands Purpose
Setup init Bootstrap new Prisma project
Generation generate Generate Prisma Client
Validation validate, format Schema validation and formatting
Development dev Local Prisma Postgres for development
Database db pull, db push, db seed, db execute Direct database operations
Migrations migrate dev, migrate deploy, migrate reset, migrate status, migrate diff, migrate resolve Schema migrations
Utility studio, mcp, version, debug Development and AI tooling

Quick Reference

Project Setup

# Initialize new project (creates prisma/ folder and prisma.config.ts)
prisma init

# Initialize with specific database
prisma init --datasource-provider postgresql
prisma init --datasource-provider mysql
prisma init --datasource-provider sqlite

# Initialize with Prisma Postgres (cloud)
prisma init --db

# Initialize with an example model
prisma init --with-model

Client Generation

# Generate Prisma Client
prisma generate

# Watch mode for development
prisma generate --watch

# Generate specific generator only
prisma generate --generator client

Bun Runtime

When using Bun, always add the --bun flag so Prisma runs with the Bun runtime (otherwise it falls back to Node.js because of the CLI shebang):

bunx --bun prisma init
bunx --bun prisma generate

Local Development Database

# Start local Prisma Postgres
prisma dev

# Start with specific name
prisma dev --name myproject

# Start in background (detached)
prisma dev --detach

# List all local instances
prisma dev ls

# Stop instance
prisma dev stop myproject

# Remove instance data
prisma dev rm myproject

Database Operations

# Pull schema from existing database
prisma db pull

# Push schema to database (no migrations)
prisma db push

# Seed database
prisma db seed

# Execute raw SQL
prisma db execute --file ./script.sql

Migrations (Development)

# Create and apply migration
prisma migrate dev

# Create migration with name
prisma migrate dev --name add_users_table

# Create migration without applying
prisma migrate dev --create-only

# Reset database and apply all migrations
prisma migrate reset

Migrations (Production)

# Apply pending migrations (CI/CD)
prisma migrate deploy

# Check migration status
prisma migrate status

# Compare schemas and generate diff
prisma migrate diff --from-config-datasource --to-schema schema.prisma --script

Utility Commands

# Open Prisma Studio (database GUI)
prisma studio

# Start Prisma's MCP server for AI tools
prisma mcp

# Show version info
prisma version
prisma -v

# Debug information
prisma debug

# Validate schema
prisma validate

# Format schema
prisma format

Current Prisma CLI Setup

New Configuration File

Use prisma.config.ts for CLI configuration:

import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: {
    path: 'prisma/migrations',
    seed: 'tsx prisma/seed.ts',
  },
  datasource: {
    url: env('DATABASE_URL'),
  },
})

Current Command Behavior

  • Run prisma generate explicitly after migrate dev, db push, or other schema syncs when you need fresh client output
  • Run prisma db seed explicitly after migrate dev or migrate reset when you need seed data
  • Use prisma db execute --file ... for raw SQL scripts

Environment Variables

Load environment variables explicitly in prisma.config.ts, commonly with dotenv:

// prisma.config.ts
import 'dotenv/config'

Rule Files

See individual rule files for detailed command documentation:

references/init.md           - Project initialization
references/generate.md       - Client generation
references/dev.md            - Local development database
references/db-pull.md        - Database introspection
references/db-push.md        - Schema push
references/db-seed.md        - Database seeding
references/db-execute.md     - Raw SQL execution
references/migrate-dev.md    - Development migrations
references/migrate-deploy.md - Production migrations
references/migrate-reset.md  - Database reset
references/migrate-status.md - Migration status
references/migrate-resolve.md - Migration resolution
references/migrate-diff.md   - Schema diffing
references/studio.md         - Database GUI
references/mcp.md            - Prisma MCP server
references/validate.md       - Schema validation
references/format.md         - Schema formatting
references/debug.md          - Debug info

How to Use

Use the command categories above for navigation, then open the specific command reference file you need.

提供 Prisma Client API 参考,涵盖模型查询、CRUD 操作、过滤、关系处理及事务配置。用于编写数据库查询、数据筛选和客户端方法调用。
prisma query findMany create update delete $transaction
.agents/skills/prisma-client-api/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-client-api -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-client-api",
    "license": "MIT",
    "metadata": {
        "author": "prisma",
        "version": "7.6.0"
    },
    "description": "Prisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on \"prisma query\", \"findMany\", \"create\", \"update\", \"delete\", \"$transaction\"."
}

Prisma Client API Reference

Complete API reference for Prisma Client. This skill provides guidance on model queries, filtering, relations, and client methods for current Prisma projects.

When to Apply

Reference this skill when:

  • Writing database queries with Prisma Client
  • Performing CRUD operations (create, read, update, delete)
  • Filtering and sorting data
  • Working with relations
  • Using transactions
  • Configuring client options

Rule Categories by Priority

Priority Category Impact Prefix
1 Client Construction HIGH constructor
2 Model Queries CRITICAL model-queries
3 Query Shape HIGH query-options
4 Filtering HIGH filters
5 Relations HIGH relations
6 Transactions CRITICAL transactions
7 Raw SQL CRITICAL raw-queries
8 Client Methods MEDIUM client-methods

Quick Reference

  • constructor - PrismaClient setup, adapter wiring, logging, and SQL commenter plugins
  • model-queries - CRUD operations and bulk operations
  • query-options - select, include, omit, sort, pagination
  • filters - scalar and logical filter operators
  • relations - relation reads and nested writes
  • transactions - array and interactive transaction patterns
  • raw-queries - $queryRaw and $executeRaw safety
  • client-methods - lifecycle methods, extensions, and satisfies patterns for prisma-client

Client Instantiation

import { PrismaClient } from '../generated/client'
import { PrismaPg } from '@prisma/adapter-pg'

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL
})

const prisma = new PrismaClient({ adapter })

Model Query Methods

Method Description
findUnique() Find one record by unique field
findUniqueOrThrow() Find one or throw error
findFirst() Find first matching record
findFirstOrThrow() Find first or throw error
findMany() Find multiple records
create() Create a new record
createMany() Create multiple records
createManyAndReturn() Create multiple and return them
update() Update one record
updateMany() Update multiple records
updateManyAndReturn() Update multiple and return them
upsert() Update or create record
delete() Delete one record
deleteMany() Delete multiple records
count() Count matching records
aggregate() Aggregate values (sum, avg, etc.)
groupBy() Group and aggregate

Query Options

Option Description
where Filter conditions
select Fields to include
include Relations to load
omit Fields to exclude
orderBy Sort order
take Limit results
skip Skip results (pagination)
cursor Cursor-based pagination
distinct Unique values only

Client Methods

Method Description
$connect() Explicitly connect to database
$disconnect() Disconnect from database
$transaction() Execute transaction
$queryRaw() Execute raw SQL query
$executeRaw() Execute raw SQL command
$on() Subscribe to events
$extends() Add extensions

Quick Examples

Find records

// Find by unique field
const user = await prisma.user.findUnique({
  where: { email: 'alice@prisma.io' }
})

// Find with filter
const users = await prisma.user.findMany({
  where: { role: 'ADMIN' },
  orderBy: { createdAt: 'desc' },
  take: 10
})

Create records

const user = await prisma.user.create({
  data: {
    email: 'alice@prisma.io',
    name: 'Alice',
    posts: {
      create: { title: 'Hello World' }
    }
  },
  include: { posts: true }
})

Update records

const user = await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Alice Smith' }
})

Delete records

await prisma.user.delete({
  where: { id: 1 }
})

Transactions

const [user, post] = await prisma.$transaction([
  prisma.user.create({ data: { email: 'alice@prisma.io' } }),
  prisma.post.create({ data: { title: 'Hello', authorId: 1 } })
])

Rule Files

Detailed API documentation:

references/constructor.md        - PrismaClient constructor options
references/model-queries.md      - CRUD operations
references/query-options.md      - select, include, omit, where, orderBy
references/filters.md            - Filter conditions and operators
references/relations.md          - Relation queries and nested operations
references/transactions.md       - Transaction API
references/raw-queries.md        - $queryRaw, $executeRaw
references/client-methods.md     - $connect, $disconnect, $on, $extends

Filter Operators

Operator Description
equals Exact match
not Not equal
in In array
notIn Not in array
lt, lte Less than
gt, gte Greater than
contains String contains
startsWith String starts with
endsWith String ends with
mode Case sensitivity

Relation Filters

Operator Description
some At least one related record matches
every All related records match
none No related records match
is Related record matches (1-to-1)
isNot Related record doesn't match

Resources

How to Use

Pick the category from the table above, then open the matching reference file for implementation details and examples.

提供 Prisma ORM 与 PostgreSQL、MySQL、SQLite、MongoDB 等数据库的配置指南。涵盖新项目初始化、数据库切换、连接字符串配置、驱动适配器选择及客户端实例化,支持多环境调试。
configure postgres connect to mysql setup mongodb sqlite setup
.agents/skills/prisma-database-setup/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-database-setup -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-database-setup",
    "license": "MIT",
    "metadata": {
        "author": "prisma",
        "version": "7.6.0"
    },
    "description": "Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.). Use when setting up a new project, changing databases, or troubleshooting connection issues. Triggers on \"configure postgres\", \"connect to mysql\", \"setup mongodb\", \"sqlite setup\"."
}

Prisma Database Setup

Comprehensive guides for configuring Prisma ORM with various database providers.

When to Apply

Reference this skill when:

  • Initializing a new Prisma project
  • Switching database providers
  • Configuring connection strings and environment variables
  • Troubleshooting database connection issues
  • Setting up database-specific features
  • Generating and instantiating Prisma Client

Rule Categories by Priority

Priority Category Impact Prefix
1 Provider Guides CRITICAL provider names
2 Prisma Postgres HIGH prisma-postgres
3 Client Setup CRITICAL prisma-client-setup

System Prerequisites

  • Node.js 20.19.0+
  • TypeScript 5.4.0+

Bun Runtime

If you're using Bun, run Prisma CLI commands with bunx --bun prisma ... so Prisma uses the Bun runtime instead of falling back to Node.js.

Supported Databases

Database Provider String Notes
PostgreSQL postgresql Default, full feature support
MySQL mysql Widespread support, some JSON diffs
SQLite sqlite Local file-based, no enum/scalar lists
MongoDB mongodb Mongo-specific workflow; do not apply SQL driver-adapter guidance
SQL Server sqlserver Microsoft ecosystem
CockroachDB cockroachdb Distributed SQL, Postgres-compatible
Prisma Postgres postgresql Managed serverless database

Configuration Files

Your configuration shape depends on the provider and Prisma major version:

  1. All providers use prisma/schema.prisma.
  2. Prisma 7 SQL setups typically use prisma.config.ts for datasource URLs.
  3. MongoDB projects should stay on Prisma 6.x, keep url = env("DATABASE_URL") in the schema, and continue using the classic MongoDB setup.

Driver Adapters

The standard SQL workflow uses a driver adapter. Choose the adapter and driver for your database and pass the adapter to PrismaClient.

Database Adapter JS Driver
PostgreSQL @prisma/adapter-pg pg
CockroachDB @prisma/adapter-pg pg
Prisma Postgres (Node.js) @prisma/adapter-pg pg
Prisma Postgres (edge/serverless) @prisma/adapter-ppg @prisma/ppg
MySQL / MariaDB @prisma/adapter-mariadb mariadb
SQLite @prisma/adapter-better-sqlite3 better-sqlite3
SQLite (Turso/LibSQL) @prisma/adapter-libsql @libsql/client
SQL Server @prisma/adapter-mssql node-mssql

MongoDB should not follow the Prisma 7 SQL adapter workflow. Use the latest Prisma 6.x release for MongoDB projects and do not install a SQL @prisma/adapter-* package for it.

Example (PostgreSQL):

import 'dotenv/config'
import { PrismaClient } from '../generated/client'
import { PrismaPg } from '@prisma/adapter-pg'

const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
const prisma = new PrismaClient({ adapter })

Prisma Client Setup (Required)

Prisma Client must be installed and generated for any database.

  1. Install Prisma CLI and Prisma Client:

    npm install prisma --save-dev
    npm install @prisma/client
    
  2. Add a generator block (prisma-client requires an explicit output path):

    generator client {
      provider = "prisma-client"
      output   = "../generated"
    }
    
  3. Generate Prisma Client:

    npx prisma generate
    
  4. For SQL providers, instantiate Prisma Client with the database-specific driver adapter:

    import { PrismaClient } from '../generated/client'
    import { PrismaPg } from '@prisma/adapter-pg'
    
    const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
    const prisma = new PrismaClient({ adapter })
    
  5. Re-run prisma generate after every schema change.

Quick Reference

PostgreSQL

datasource db {
  provider = "postgresql"
}

generator client {
  provider = "prisma-client"
  output   = "../generated"
}

MySQL

datasource db {
  provider = "mysql"
}

generator client {
  provider = "prisma-client"
  output   = "../generated"
}

SQLite

datasource db {
  provider = "sqlite"
}

generator client {
  provider = "prisma-client"
  output   = "../generated"
}

MongoDB

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

For MongoDB, stay on the latest Prisma 6.x line and keep the connection URL in schema.prisma. Do not move a MongoDB project to the Prisma 7 SQL adapter setup.

Rule Files

See individual rule files for detailed setup instructions:

references/postgresql.md
references/mysql.md
references/sqlite.md
references/mongodb.md
references/sqlserver.md
references/cockroachdb.md
references/prisma-postgres.md
references/prisma-client-setup.md

How to Use

Choose the provider reference file for your database, then apply references/prisma-client-setup.md to complete client generation and adapter setup. For MongoDB, use references/mongodb.md instead of copying the SQL adapter examples or Prisma 7 config pattern.

提供Prisma v7驱动适配器实现指南,涵盖架构、核心接口及事务生命周期协议。用于指导开发或修改数据库驱动适配器,确保符合官方契约规范。
实现新的数据库驱动适配器 修改SqlDriverAdapter接口 添加新数据库支持 处理事务生命周期相关逻辑
.agents/skills/prisma-driver-adapter-implementation/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-driver-adapter-implementation -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-driver-adapter-implementation",
    "license": "MIT",
    "metadata": {
        "author": "Tyler Benfield",
        "version": "7.6.0"
    },
    "description": "Required reference for Prisma v7 driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter\/Transaction interfaces. Contains critical contract details not inferable from code examples — including the transaction lifecycle protocol, error mapping requirements, and verification checklist. Existing implementations do not replace this skill."
}

Prisma 7 Driver Adapter Implementation Guide

This skill provides everything needed to implement a Prisma ORM v7 driver adapter for any database.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                         PrismaClient                            │
│                    (requires adapter factory)                   │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│            SqlMigrationAwareDriverAdapterFactory                │
│   ┌─────────────────────┐    ┌─────────────────────────────┐    │
│   │ connect()           │    │ connectToShadowDb()         │    │
│   │ → SqlDriverAdapter  │    │ → SqlDriverAdapter          │    │
│   └─────────────────────┘    └─────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                      SqlDriverAdapter                           │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│  │ queryRaw()   │ │ executeRaw() │ │ startTransaction()       │ │
│  │ → ResultSet  │ │ → number     │ │ → Transaction            │ │
│  └──────────────┘ └──────────────┘ └──────────────────────────┘ │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│  │executeScript │ │ dispose()    │ │ getConnectionInfo()      │ │
│  └──────────────┘ └──────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                        Transaction                              │
│  Extends SqlQueryable + commit() + rollback() + options         │
│  (lifecycle hooks only — Prisma sends SQL via executeRaw)       │
└─────────────────────────────────────────────────────────────────┘

Required Interfaces

Import from @prisma/driver-adapter-utils:

import type {
  ColumnType,
  IsolationLevel,
  SqlDriverAdapter,
  SqlMigrationAwareDriverAdapterFactory,
  SqlQuery,
  SqlQueryable,
  SqlResultSet,
  Transaction,
  TransactionOptions,
  ArgType,
  ConnectionInfo,
  MappedError,
} from "@prisma/driver-adapter-utils";
import {
  ColumnTypeEnum,
  DriverAdapterError,
} from "@prisma/driver-adapter-utils";

Interface Definitions

SqlQuery (input to queryRaw/executeRaw)

type SqlQuery = {
  sql: string; // Parameterized SQL with placeholders
  args: Array<unknown>; // Bound parameter values
  argTypes: Array<ArgType>; // Type hints for each argument
};

type ArgType = {
  scalarType: ArgScalarType; // 'string' | 'int' | 'bigint' | 'float' | 'decimal' | 'boolean' | 'enum' | 'uuid' | 'json' | 'datetime' | 'bytes' | 'unknown'
  dbType?: string;
  arity: "scalar" | "list";
};

SqlResultSet (output from queryRaw)

interface SqlResultSet {
  columnNames: Array<string>; // Column names in order
  columnTypes: Array<ColumnType>; // Column types matching columnNames
  rows: Array<Array<unknown>>; // Row data as arrays
  lastInsertId?: string; // For INSERT without RETURNING
}

ColumnTypeEnum values

const ColumnTypeEnum = {
  Int32: 0,
  Int64: 1,
  Float: 2,
  Double: 3,
  Numeric: 4,
  Boolean: 5,
  Character: 6,
  Text: 7,
  Date: 8,
  Time: 9,
  DateTime: 10,
  Json: 11,
  Enum: 12,
  Bytes: 13,
  Set: 14,
  Uuid: 15,
  Int32Array: 64,
  Int64Array: 65,
  FloatArray: 66,
  DoubleArray: 67,
  NumericArray: 68,
  BooleanArray: 69,
  CharacterArray: 70,
  TextArray: 71,
  DateArray: 72,
  TimeArray: 73,
  DateTimeArray: 74,
  JsonArray: 75,
  EnumArray: 76,
  BytesArray: 77,
  UuidArray: 78,
  UnknownNumber: 128,
} as const;

SqlDriverAdapter

interface SqlDriverAdapter extends SqlQueryable {
  executeScript(script: string): Promise<void>;
  startTransaction(isolationLevel?: IsolationLevel): Promise<Transaction>;
  getConnectionInfo?(): ConnectionInfo;
  dispose(): Promise<void>;
}

Transaction

interface Transaction extends SqlQueryable {
  readonly options: TransactionOptions;
  commit(): Promise<void>;
  rollback(): Promise<void>;
}

type TransactionOptions = { usePhantomQuery: boolean };

SqlMigrationAwareDriverAdapterFactory

interface SqlMigrationAwareDriverAdapterFactory {
  readonly provider: "mysql" | "postgres" | "sqlite" | "sqlserver";
  readonly adapterName: string;
  connect(): Promise<SqlDriverAdapter>;
  connectToShadowDb(): Promise<SqlDriverAdapter>;
}

Implementation Steps

Step 1: Create the Queryable base class

class MyQueryable<TClient> implements SqlQueryable {
  readonly provider = "postgres" as const; // or 'sqlite' | 'mysql' | 'sqlserver'
  readonly adapterName = "@my-org/adapter-mydb" as const;

  constructor(protected readonly client: TClient) {}

  async queryRaw(query: SqlQuery): Promise<SqlResultSet> {
    try {
      const args = query.args.map((arg, i) =>
        mapArg(arg, query.argTypes[i] ?? { scalarType: "unknown", arity: "scalar" })
      );

      // Execute query with your driver
      const result = await this.client.query(query.sql, args);

      // Extract column metadata
      const columnNames = /* get from result */;
      const columnTypes = /* map to ColumnTypeEnum */;

      // Map rows to ResultValue arrays
      const rows = result.map(row => mapRow(row, columnTypes));

      return { columnNames, columnTypes, rows };
    } catch (e) {
      this.onError(e);
    }
  }

  async executeRaw(query: SqlQuery): Promise<number> {
    try {
      const args = query.args.map((arg, i) =>
        mapArg(arg, query.argTypes[i] ?? { scalarType: "unknown", arity: "scalar" })
      );
      const result = await this.client.query(query.sql, args);
      return result.affectedRows ?? 0;
    } catch (e) {
      this.onError(e);
    }
  }

  protected onError(error: unknown): never {
    throw new DriverAdapterError(convertDriverError(error));
  }
}

Step 2: Create the Transaction class

Critical: commit() and rollback() are lifecycle hooks only. They must NOT issue SQL. Prisma sends COMMIT/ROLLBACK via executeRaw on the transaction object.

class MyTransaction extends MyQueryable<TClient> implements Transaction {
  readonly options: TransactionOptions;
  readonly #release: () => void;

  constructor(
    client: TClient,
    options: TransactionOptions,
    release: () => void,
  ) {
    super(client);
    this.options = options;
    this.#release = release;
  }

  commit(): Promise<void> {
    // DO NOT issue COMMIT SQL here — Prisma does it via executeRaw
    this.#release(); // Release connection/resources
    return Promise.resolve();
  }

  rollback(): Promise<void> {
    // DO NOT issue ROLLBACK SQL here — Prisma does it via executeRaw
    this.#release();
    return Promise.resolve();
  }
}

Step 3: Create the Adapter class

class MyAdapter extends MyQueryable<TClient> implements SqlDriverAdapter {
  #transactionDepth = 0;

  constructor(client: TClient) {
    super(client);
  }

  async executeScript(script: string): Promise<void> {
    // For SQLite: split on ';' and run each statement
    // For Postgres: use multi-statement execution
    try {
      // Implementation depends on driver capabilities
    } catch (e) {
      this.onError(e);
    }
  }

  async startTransaction(
    isolationLevel?: IsolationLevel,
  ): Promise<Transaction> {
    // Validate isolation level for your database
    const validLevels = new Set<IsolationLevel>([
      "READ UNCOMMITTED",
      "READ COMMITTED",
      "REPEATABLE READ",
      "SERIALIZABLE",
    ]);

    if (isolationLevel !== undefined && !validLevels.has(isolationLevel)) {
      throw new DriverAdapterError({
        kind: "InvalidIsolationLevel",
        level: isolationLevel,
      });
    }

    const options: TransactionOptions = { usePhantomQuery: false };

    this.#transactionDepth += 1;
    const depth = this.#transactionDepth;

    try {
      if (depth === 1) {
        // Issue BEGIN (with isolation level if specified)
        const beginSql = isolationLevel
          ? `BEGIN ISOLATION LEVEL ${isolationLevel}`
          : "BEGIN";
        await this.client.query(beginSql);
      } else {
        // Nested: use savepoints
        await this.client.query(`SAVEPOINT sp_${depth}`);
      }
    } catch (e) {
      this.#transactionDepth -= 1;
      this.onError(e);
    }

    const release = () => {
      this.#transactionDepth -= 1;
    };
    return new MyTransaction(this.client, options, release);
  }

  getConnectionInfo(): ConnectionInfo {
    return { supportsRelationJoins: true };
  }

  async dispose(): Promise<void> {
    await this.client.close();
  }
}

Step 4: Create the Factory class

export type MyAdapterConfig = {
  url: string;
};

export type MyAdapterOptions = {
  shadowDatabaseUrl?: string;
};

export class MyAdapterFactory implements SqlMigrationAwareDriverAdapterFactory {
  readonly provider = "postgres" as const;
  readonly adapterName = "@my-org/adapter-mydb" as const;

  constructor(
    private readonly config: MyAdapterConfig,
    private readonly options?: MyAdapterOptions,
  ) {}

  connect(): Promise<SqlDriverAdapter> {
    return Promise.resolve(new MyAdapter(openConnection(this.config.url)));
  }

  connectToShadowDb(): Promise<SqlDriverAdapter> {
    const url = this.options?.shadowDatabaseUrl ?? this.config.url;
    return Promise.resolve(new MyAdapter(openConnection(url)));
  }
}

Conversion Helpers

Argument Mapping (input)

Convert Prisma argument values to driver-native types:

function mapArg(arg: unknown, argType: ArgType): unknown {
  if (arg === null || arg === undefined) return null;

  // String → number for int columns
  if (typeof arg === "string" && argType.scalarType === "int")
    return Number.parseInt(arg, 10);

  // String → number for float columns
  if (typeof arg === "string" && argType.scalarType === "float")
    return Number.parseFloat(arg);

  // String → BigInt for bigint columns
  if (typeof arg === "string" && argType.scalarType === "bigint")
    return BigInt(arg);

  // Base64 string → Buffer for bytes columns
  if (typeof arg === "string" && argType.scalarType === "bytes")
    return Buffer.from(arg, "base64");

  // Boolean → 0/1 for SQLite
  if (typeof arg === "boolean" && /* SQLite */)
    return arg ? 1 : 0;

  return arg;
}

Row Mapping (output)

Convert driver result values to Prisma-expected types:

function mapRow(row: unknown[], columnTypes: ColumnType[]): ResultValue[] {
  const result: ResultValue[] = [];

  for (let i = 0; i < row.length; i++) {
    const value = row[i] ?? null;
    const colType = columnTypes[i];

    if (value === null) {
      result.push(null);
      continue;
    }

    // bigint → string for Int64 (JSON-safe)
    if (typeof value === "bigint") {
      result.push(value.toString());
      continue;
    }

    // Date → ISO 8601 string for DateTime
    if (value instanceof Date) {
      result.push(value.toISOString());
      continue;
    }

    // JSON objects → stringified
    if (colType === ColumnTypeEnum.Json && typeof value === "object") {
      result.push(JSON.stringify(value));
      continue;
    }

    result.push(value as ResultValue);
  }

  return result;
}

Column Type Inference

When the driver doesn't provide type metadata, infer from JS values:

function inferColumnType(value: NonNullable<unknown>): ColumnType {
  if (typeof value === "boolean") return ColumnTypeEnum.Boolean;
  if (typeof value === "bigint") return ColumnTypeEnum.Int64;
  if (value instanceof Uint8Array) return ColumnTypeEnum.Bytes;
  if (value instanceof Date) return ColumnTypeEnum.DateTime;
  if (Array.isArray(value)) return ColumnTypeEnum.Text; // fallback
  if (typeof value === "object") return ColumnTypeEnum.Json;
  if (typeof value === "number") return ColumnTypeEnum.UnknownNumber;
  return ColumnTypeEnum.Text;
}

Error Handling

Map driver errors to MappedError for Prisma to handle correctly:

function convertDriverError(error: unknown): MappedError {
  if (error instanceof Error) {
    // Database-specific error mapping
    const dbError = error as Error & { code?: string; errno?: number };

    // PostgreSQL example
    if (dbError.code === "23505") {
      return { kind: "UniqueConstraintViolation" };
    }
    if (dbError.code === "23502") {
      return { kind: "NullConstraintViolation" };
    }
    if (dbError.code === "23503") {
      return { kind: "ForeignKeyConstraintViolation" };
    }
    if (dbError.code === "42P01") {
      return { kind: "TableDoesNotExist" };
    }

    // SQLite example
    if (error.name === "SQLiteError") {
      return {
        kind: "sqlite",
        extendedCode: dbError.errno ?? 1,
        message: error.message,
      };
    }

    // PostgreSQL raw error
    if (dbError.code) {
      return {
        kind: "postgres",
        code: dbError.code,
        severity: "ERROR",
        message: error.message,
        detail: undefined,
        column: undefined,
        hint: undefined,
      };
    }
  }

  return { kind: "GenericJs", id: 0 };
}

Database-Specific Notes

SQLite

  • Set safeIntegers: true when opening the database to get bigint for large integers
  • Only SERIALIZABLE isolation level is valid
  • executeScript: split on ; and run each statement individually
  • Boolean values: store as 0/1, return as boolean

PostgreSQL

  • All standard isolation levels are valid
  • For connection pooling (PgBouncer), use prepare: false
  • Transactions require a dedicated connection (reserve() pattern)
  • executeScript: use multi-statement execution (.simple() in some drivers)
  • int8 columns may return as string (already stringified by driver)
  • numeric columns return as string to preserve precision

MySQL/MariaDB

  • Supports READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
  • Use ? placeholders for parameters
  • Handle BIGINT as string for large values

Testing Strategy

Unit Tests (no PrismaClient)

Test the adapter directly with the raw database driver:

describe("queryRaw", () => {
  test("returns column names and types", async () => {
    const adapter = new MyAdapter(createTestConnection());
    const result = await adapter.queryRaw({
      sql: "SELECT id, name FROM users",
      args: [],
      argTypes: [],
    });
    expect(result.columnNames).toEqual(["id", "name"]);
    expect(result.columnTypes[0]).toBe(ColumnTypeEnum.Int32);
  });
});

describe("startTransaction", () => {
  test("commit persists changes", async () => {
    const adapter = new MyAdapter(createTestConnection());
    const tx = await adapter.startTransaction();
    await tx.executeRaw({
      sql: "INSERT INTO users (name) VALUES (?)",
      args: ["Alice"],
      argTypes: [],
    });
    // Prisma sends COMMIT via executeRaw
    await tx.executeRaw({ sql: "COMMIT", args: [], argTypes: [] });
    await tx.commit(); // lifecycle hook only
    // Verify data persisted
  });
});

E2E Tests (with PrismaClient)

Test the full integration:

describe("E2E", () => {
  let prisma: PrismaClient;

  beforeEach(async () => {
    const factory = new MyAdapterFactory({ url: TEST_DB_URL });
    prisma = new PrismaClient({ adapter: factory });
  });

  test("CRUD operations", async () => {
    const user = await prisma.user.create({ data: { name: "Alice" } });
    expect(user.id).toBeGreaterThan(0);

    const found = await prisma.user.findUnique({ where: { id: user.id } });
    expect(found?.name).toBe("Alice");
  });

  test("transactions roll back on error", async () => {
    await expect(
      prisma.$transaction(async (tx) => {
        await tx.user.create({ data: { name: "Bob" } });
        throw new Error("Rollback!");
      }),
    ).rejects.toThrow();

    expect(await prisma.user.count()).toBe(0);
  });
});

Usage Example

import { PrismaClient } from "./generated/prisma/client";
import { MyAdapterFactory } from "@my-org/adapter-mydb";

const factory = new MyAdapterFactory({
  url: process.env.DATABASE_URL!,
});

const prisma = new PrismaClient({ adapter: factory });

// Use prisma normally
const users = await prisma.user.findMany();

Checklist

Before considering the adapter complete:

  • SqlMigrationAwareDriverAdapterFactory implemented with connect() and connectToShadowDb()
  • SqlDriverAdapter implements queryRaw, executeRaw, executeScript, startTransaction, dispose
  • Transaction implements queryRaw, executeRaw, commit, rollback with options: { usePhantomQuery: false }
  • commit() and rollback() are lifecycle hooks only (no SQL issued)
  • startTransaction issues BEGIN (depth 1) or SAVEPOINT sp_N (nested)
  • Argument mapping handles: string→int, string→bigint, string→float, base64→bytes
  • Row mapping handles: bigint→string, Date→ISO string, JSON→string
  • Column types correctly mapped to ColumnTypeEnum
  • Errors wrapped in DriverAdapterError with proper MappedError kind
  • Isolation level validation for the target database
  • Unit tests pass for queryRaw, executeRaw, executeScript, transactions
  • E2E tests pass with real PrismaClient
通过 Management API 引导用户完成 Prisma Postgres 数据库的创建、区域选择及本地连接配置。适用于新建数据库项目或获取连接字符串,不支持 CI/CD 或多租户集成场景。
设置数据库 创建 Prisma Postgres 项目 获取连接字符串 将应用连接到 Prisma Postgres 配置数据库
.agents/skills/prisma-postgres-setup/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-postgres-setup -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-postgres-setup",
    "license": "MIT",
    "metadata": {
        "author": "prisma",
        "version": "1.1.0"
    },
    "description": "Set up a new Prisma Postgres database and connect it to a local project using the Management API. Use when asked to \"set up a database\", \"create a Prisma Postgres project\", \"get a connection string\", \"connect my app to Prisma Postgres\", or \"provision a database\"."
}

Prisma Postgres Setup

Procedural skill that guides you through provisioning a new Prisma Postgres database via the Management API and connecting it to a local project.

When to Apply

Use this skill when:

  • Setting up a new Prisma Postgres database for a project
  • Creating a Prisma Postgres project and connecting it locally
  • Obtaining a connection string for Prisma Postgres
  • Provisioning a database via the Management API (not the Console UI)

Do not use this skill when:

  • Setting up CI/CD preview databases — use prisma-postgres-cicd
  • Building multi-tenant database provisioning into an app — use prisma-postgres-integrator
  • Working with a database that already exists and is connected (schema/migration tasks are standard Prisma CLI)

Prerequisites

  • Node.js 18+
  • A Prisma Postgres workspace (create one at https://console.prisma.io if needed)
  • A workspace service token (see references/auth.md)

UX Guidelines

When presenting choices to the user (region selection, project deletion, etc.), use your platform's interactive selection mechanism (e.g., ask tool in Claude Code, structured prompts in other agents). Do not print static tables and ask the user to type a value — present selectable options so the user can pick with minimal effort.

Workflow

Follow these steps in order. Each step includes the API call to make and how to handle the response.

Step 1: Authenticate

You need a service token. Try these methods in order:

1a. Token in the user's prompt

Check if the user included a service token in their initial message (e.g., "Set up Prisma Postgres with token eyJ..."). If so, use it exactly as provided — do not truncate, re-encode, or round-trip it through a file. Store it in a shell variable for subsequent calls.

1b. Token in the environment

Check for PRISMA_SERVICE_TOKEN in the environment or .env file.

1c. Ask the user to create one

If no token is available, instruct the user:

Create a service token in Prisma Console → Workspace Settings → Service Tokens. Copy the token and paste it here.

Read references/auth.md for details on service token creation.

Once you have a token, store it in a shell variable (PRISMA_SERVICE_TOKEN) and use it for all subsequent API calls.

Step 2: List available regions

Fetch the list of available Prisma Postgres regions to let the user choose where to deploy.

curl -s -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \
  https://api.prisma.io/v1/regions/postgres

The response contains an array of regions with id, name, and status. Only present regions where status is available.

Present the regions as an interactive menu — let the user pick from options rather than typing a region ID manually.

Read references/endpoints.md for the full response shape.

Step 3: Create a project with a database

curl -s -X POST https://api.prisma.io/v1/projects \
  -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "<project-name>",
    "region": "<region-id>",
    "createDatabase": true
  }'

Use the current directory name as the project name by default.

The response is wrapped in { "data": { ... } }. Extract:

  • data.id — the project ID (prefixed with proj_)
  • data.database.id — the database ID (prefixed with db_)
  • data.database.connections[0].endpoints.direct.connectionString — the direct PostgreSQL connection string

Use the direct connection string (endpoints.direct.connectionString). Do not use the pooled or accelerate endpoints — those are for legacy Accelerate setups and not needed for new projects.

If the response status is provisioning, wait a few seconds and poll GET /v1/databases/<database-id> until status is ready.

If creation fails due to a database limit, list the user's existing projects and present them as an interactive menu for deletion. After the user picks one, delete it and retry.

Read references/endpoints.md for the full request/response shapes.

Step 4: Create a named connection (optional)

If you need a dedicated connection (e.g., per-developer or per-environment), create one:

curl -s -X POST https://api.prisma.io/v1/databases/<database-id>/connections \
  -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "dev" }'

Extract the direct connection string from data.endpoints.direct.connectionString.

Step 5: Configure the local project

  1. Install dependencies:
npm install prisma @prisma/client @prisma/adapter-pg pg dotenv

All five packages are required:

  • prisma — CLI for migrations, schema push, client generation
  • @prisma/client — the generated query client
  • @prisma/adapter-pg — Prisma 7 driver adapter for direct PostgreSQL connections
  • pg — Node.js PostgreSQL driver (used by the adapter)
  • dotenv — loads .env variables for prisma.config.ts
  1. Write the direct connection string to .env. Append to the file if it already exists — do not overwrite existing entries:
DATABASE_URL="<direct-connection-string>"
  1. Verify .gitignore includes .env. Create .gitignore if it does not exist. Warn the user if .env is not gitignored.

  2. Ensure package.json has "type": "module" set (Prisma 7 generates ESM output).

  3. If prisma/schema.prisma does not exist, run npx prisma init to scaffold the project. This creates both prisma/schema.prisma and prisma.config.ts.

  4. Ensure schema.prisma has the postgresql provider and no url or directUrl in the datasource block (Prisma 7 manages connection URLs in prisma.config.ts, not in the schema):

datasource db {
  provider = "postgresql"
}
  1. Ensure prisma.config.ts loads the connection URL from the environment:
import path from 'node:path'
import { defineConfig } from 'prisma/config'
import 'dotenv/config'

export default defineConfig({
  earlyAccess: true,
  schema: path.join(import.meta.dirname, 'prisma', 'schema.prisma'),
  datasource: {
    url: process.env.DATABASE_URL!,
  },
})

Important Prisma 7 notes:

  • Connection URLs go in prisma.config.ts, never in schema.prisma
  • The provider in schema.prisma must be "postgresql" (not "prismaPostgres")
  • dotenv/config must be imported in prisma.config.ts to load .env variables

Step 6: Define schema and push

If the schema already has models, skip to pushing. Otherwise, present these options as an interactive menu:

  1. "I'll define my schema manually" — Tell the user to edit prisma/schema.prisma and come back when ready. Wait for them before proceeding.
  2. "Give me a starter schema" — Add a Blog starter schema (User, Post, Comment with relations) to prisma/schema.prisma. Show the user what was added and ask if they want to adjust it before pushing.
  3. "I'll describe what I need" — Ask the user to describe their data model in natural language (e.g., "I'm building a task manager with projects, tasks, and team members"). Generate a schema from the description, show it, and ask for confirmation before pushing.

Once the schema has models and the user is ready, create a migration and generate the client:

npx prisma migrate dev --name init

This creates migration files in prisma/migrations/ and generates the client in one step. Migration history is essential for CI/CD workflows (prisma migrate deploy) and production deployments.

Only use npx prisma db push if the user explicitly asks for prototyping-only mode (no migration history). In that case, follow it with npx prisma generate.

Step 7: Verify the connection

After generating the client, create and run a quick verification script to confirm everything works end-to-end. This is critical — do not skip this step.

Create a file named test-connection.ts:

import 'dotenv/config'
import pg from 'pg'
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from './generated/prisma/client.js'

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })
const adapter = new PrismaPg(pool)
const prisma = new PrismaClient({ adapter })

const result = await prisma.$queryRawUnsafe('SELECT 1 as connected')
console.log('Connected to Prisma Postgres:', result)

await prisma.$disconnect()
await pool.end()

Run it:

npx tsx test-connection.ts

Prisma 7 client instantiation rules:

  • Import from ./generated/prisma/client.js (not ./generated/prisma)
  • Create a pg.Pool with the DATABASE_URL connection string
  • Wrap it in a PrismaPg adapter
  • Pass { adapter } to the PrismaClient constructor
  • Do not use datasourceUrl — that option does not exist in Prisma 7
  • Do not use new PrismaClient() with no arguments — it will throw

After verification succeeds, delete test-connection.ts.

Then share links for the user to explore their database:

  • Prisma Studio (CLI): npx prisma studio — opens a visual data browser locally
  • Console: https://console.prisma.io/<workspaceId>/<projectId>/<databaseId>/dashboard — strip the prefixes (wksp_, proj_, db_) from the IDs returned in Step 3 to build this URL

Read references/prisma7-client.md for the full client instantiation reference.

Error Handling

Read references/api-basics.md for the full error reference. Key self-correction patterns:

HTTP Status Error Code Action
401 authentication-failed Service token is invalid or expired. Ask the user to create a new one in Console → Workspace Settings → Service Tokens.
404 resource-not-found Check that the resource ID includes the correct prefix (proj_, db_, con_).
422 validation-error Check request body against the endpoint schema. Common: missing name, invalid region.
429 rate-limit-exceeded Back off and retry after a few seconds.

Reference Files

Detailed API and usage information is in:

references/auth.md             — Service token creation and usage
references/api-basics.md       — Base URL, envelope, IDs, errors, pagination
references/endpoints.md        — Endpoint details for projects, databases, connections, regions
references/prisma7-client.md   — Prisma 7 client instantiation and usage patterns
提供 Prisma Postgres 的全方位操作指南,涵盖控制台管理、CLI 快速创建临时数据库、项目关联及通过 Management API 或 SDK 进行程序化集成与配置。
使用 Prisma Console 设置数据库 通过 create-db CLI 快速创建临时数据库 执行 prisma postgres link 关联本地项目 调用 Management API 或 SDK 进行资源管理和自动化集成
.agents/skills/prisma-postgres/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-postgres -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-postgres",
    "license": "MIT",
    "metadata": {
        "author": "prisma",
        "version": "7.6.0"
    },
    "description": "Prisma Postgres setup and operations guidance across Console, create-db CLI, Management API, and Management API SDK. Use when creating Prisma Postgres databases, working in Prisma Console, provisioning with create-db\/create-pg\/create-postgres, or integrating programmatic provisioning with service tokens or OAuth."
}

Prisma Postgres

Guidance for creating, managing, and integrating Prisma Postgres across interactive and programmatic workflows.

When to Apply

Reference this skill when:

  • Setting up Prisma Postgres from Prisma Console
  • Provisioning instant temporary databases with create-db
  • Linking an existing local project with prisma postgres link
  • Managing Prisma Postgres resources via Management API
  • Using @prisma/management-api-sdk in TypeScript/JavaScript
  • Handling claim URLs, connection strings, regions, and auth flows

Rule Categories by Priority

Priority Category Impact Prefix
1 CLI Provisioning CRITICAL create-db-cli
2 Management API CRITICAL management-api
3 Management API SDK HIGH management-api-sdk
4 Console and Connections HIGH console-and-connections

Quick Reference

  • create-db-cli - instant databases and current CLI flags (--ttl, --copy, --quiet, --open)
  • management-api - service token and OAuth API workflows
  • management-api-sdk - typed SDK usage with token storage
  • console-and-connections - Console operations, prisma postgres link, direct TCP connections, and serverless-driver choices

Core Workflows

1. Console-first workflow

Use Prisma Console for manual setup and operations:

  • Open https://console.prisma.io
  • Create/select workspace and project
  • Use Studio in the project sidebar to view/edit data
  • Retrieve direct connection details from the project UI

2. Quick provisioning with create-db

Use create-db when you need a database immediately:

npx create-db@latest

Aliases:

npx create-pg@latest
npx create-postgres@latest

For app integrations, you can also use the programmatic API (create() / regions()) from the create-db npm package.

Temporary databases auto-delete after ~24 hours unless claimed.

3. Link an existing local project

Use prisma postgres link when the database already exists and you want to wire a local project to it:

prisma postgres link

For CI or other non-interactive environments:

prisma postgres link --api-key "<your-api-key>" --database "db_..."

This flow updates your local .env with DATABASE_URL, then you can run prisma generate and prisma migrate dev.

4. Programmatic provisioning with Management API

Use API endpoints on:

https://api.prisma.io/v1

Explore the schema and endpoints using:

  • OpenAPI docs: https://api.prisma.io/v1/doc
  • Swagger Editor: https://api.prisma.io/v1/swagger-editor

Auth options:

  • Service token (workspace server-to-server)
  • OAuth 2.0 (act on behalf of users)

5. Type-safe integration with Management API SDK

Install and use:

npm install @prisma/management-api-sdk

Use createManagementApiClient for existing tokens, or createManagementApiSdk for OAuth + token refresh.

Rule Files

Detailed guidance lives in:

references/console-and-connections.md
references/create-db-cli.md
references/management-api.md
references/management-api-sdk.md

How to Use

Start with references/create-db-cli.md for fast setup, then switch to references/management-api.md or references/management-api-sdk.md when you need programmatic provisioning.

提供 Prisma ORM v6 至 v7 的完整迁移指南,涵盖破坏性变更、prisma-client 生成器、驱动适配器及配置更新。适用于版本升级、解决 v7 错误或项目迁移场景。
upgrade to prisma 7 prisma 7 migration prisma-client generator driver adapter required
.agents/skills/prisma-upgrade-v7/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill prisma-upgrade-v7 -g -y
SKILL.md
Frontmatter
{
    "name": "prisma-upgrade-v7",
    "license": "MIT",
    "metadata": {
        "author": "prisma",
        "version": "7.6.0"
    },
    "description": "Complete migration guide from Prisma ORM v6 to v7 covering all breaking changes. Use when upgrading Prisma versions, encountering v7 errors, or migrating existing projects. Triggers on \"upgrade to prisma 7\", \"prisma 7 migration\", \"prisma-client generator\", \"driver adapter required\"."
}

Upgrade to Prisma ORM 7

Complete guide for migrating from Prisma ORM v6 to v7. This upgrade introduces significant breaking changes around the new prisma-client generator, driver adapters, prisma.config.ts, explicit environment loading, and generated client entrypoints.

When to Apply

Reference this skill when:

  • Upgrading from Prisma v6 to v7
  • Updating to the prisma-client generator
  • Setting up driver adapters
  • Configuring prisma.config.ts
  • Fixing import errors after upgrade

Rule Categories by Priority

Priority Category Impact Prefix
1 Schema Migration CRITICAL schema-changes
2 Database Connectivity CRITICAL driver-adapters
3 Module System CRITICAL esm-support
4 Config and Env HIGH prisma-config, env-variables
5 Removed Features HIGH removed-features
6 Accelerate HIGH accelerate-users

Quick Reference

  • schema-changes - generator migration, required output paths, generated entrypoints, and Prisma.validator replacement
  • driver-adapters - required adapter installation for SQL providers, pool differences, and Prisma Postgres adapter choices
  • esm-support - ESM-first setup plus CommonJS fallback with moduleFormat = "cjs"
  • prisma-config - creating and using prisma.config.ts
  • env-variables - explicit environment loading
  • removed-features - removed middleware, metrics, and legacy CLI behavior
  • accelerate-users - migration notes for Accelerate users

Important Notes

  • MongoDB projects should stay on Prisma 6.x - do not migrate MongoDB apps to Prisma 7's SQL client path
  • Node.js 20.19.0+ required
  • TypeScript 5.4.0+ required
  • Latest stable Prisma ORM version: 7.6.0

Upgrade Steps Overview

  1. Update packages to v7
  2. Choose your module format (esm by default, cjs if needed)
  3. Update TypeScript configuration
  4. Update the schema generator block
  5. Create prisma.config.ts
  6. Install and configure a driver adapter for SQL providers
  7. Update Prisma Client imports
  8. Update client instantiation
  9. Replace deprecated helper patterns like Prisma.validator
  10. Run prisma generate and test

Quick Upgrade Commands

# Update packages
npm install @prisma/client@7
npm install -D prisma@7

# Install a driver adapter (PostgreSQL or Prisma Postgres via direct TCP)
npm install @prisma/adapter-pg pg

# Install dotenv for env loading
npm install dotenv

# Regenerate client
npx prisma generate

Breaking Changes Summary

Change v6 v7
Module format Implicit / mixed ESM-first, moduleFormat = "cjs" supported
Generator provider prisma-client-js prisma-client is the default, while prisma-client-js still exists for legacy setups
Output path Auto (node_modules) Required explicit
Driver adapters Optional Required for SQL providers
Config file .env + schema prisma.config.ts
Env loading Automatic Manual (dotenv)
Generated entrypoints Single package export client, browser, models, enums entrypoints
Type-safe query fragments Prisma.validator() TypeScript satisfies
Middleware $use() Client Extensions
Metrics Preview feature Removed

Rule Files

Detailed migration guides for each breaking change:

references/esm-support.md        - ESM and CommonJS configuration
references/schema-changes.md     - Generator, output, imports, and generated entrypoints
references/driver-adapters.md    - Required driver adapter setup
references/prisma-config.md      - New configuration file
references/env-variables.md      - Environment variable loading
references/removed-features.md   - Middleware, metrics, and CLI flags
references/accelerate-users.md   - Special handling for Accelerate

Step-by-Step Migration

1. Update package.json for ESM-first projects

{
  "type": "module"
}

If you need to stay on CommonJS, keep your app as CJS and set moduleFormat = "cjs" in the generator block instead of forcing ESM.

2. Update tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "target": "ES2023",
    "strict": true,
    "esModuleInterop": true
  }
}

3. Update schema.prisma

// Before (v6)
generator client {
  provider = "prisma-client-js"
}

// After (v7)
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
  // Optional if you need CommonJS:
  // moduleFormat = "cjs"
}

4. Create prisma.config.ts

import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: {
    path: 'prisma/migrations',
  },
  datasource: {
    url: env('DATABASE_URL'),
  },
})

5. Install a driver adapter (SQL providers only)

# PostgreSQL
npm install @prisma/adapter-pg pg

# MySQL
npm install @prisma/adapter-mariadb mariadb

# SQLite
npm install @prisma/adapter-better-sqlite3 better-sqlite3

# Prisma Postgres in standard Node.js apps (recommended)
npm install @prisma/adapter-pg pg

# Prisma Postgres serverless driver (edge/serverless)
npm install @prisma/adapter-ppg @prisma/ppg

# Neon
npm install @prisma/adapter-neon

MongoDB does not have a SQL @prisma/adapter-* package in the published Prisma 7.6.0 packages. If you're upgrading a MongoDB project, stop and keep that project on the latest Prisma 6.x release instead of following the standard Prisma 7 migration path.

6. Update client instantiation

// Before (v6)
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

// After (v7)
import { PrismaClient } from '../generated/prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL
})

const prisma = new PrismaClient({ adapter })

7. Replace Prisma.validator with satisfies

import { Prisma } from '../generated/prisma/client'

const userSelect = {
  id: true,
  email: true,
  name: true,
} satisfies Prisma.UserSelect

8. Run migrations and generate

npx prisma generate
npx prisma migrate dev  # if needed

Troubleshooting

"Cannot find module" errors

  • Check that the generator output path matches your import path
  • Ensure prisma generate ran successfully

SSL certificate errors

  • Add ssl: { rejectUnauthorized: false } to the adapter config if you need to preserve old behavior
  • Or configure your certificates properly with NODE_EXTRA_CA_CERTS / OpenSSL CA settings

Connection timeout issues

  • Driver adapters use the underlying driver's defaults, which differ from v6
  • Configure pool settings explicitly on the adapter if needed

Resources

How to Use

Follow references/schema-changes.md and references/driver-adapters.md first, then apply the remaining reference files based on your project setup.

基于Trigger.dev的AI Agent模式指南,涵盖并行化、路由、提示链、编排器、评估优化及人机协作。适用于构建需多步骤工作流、审批门控或工具调用的LLM任务。
需要并行处理多个项目时 需要根据分类将请求路由到不同模型时 需要构建带验证门的链式LLM调用时 需要协调多个专用任务时 需要暂停等待人类审批时
.agents/skills/trigger-agents/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill trigger-agents -g -y
SKILL.md
Frontmatter
{
    "name": "trigger-agents",
    "description": "AI agent patterns with Trigger.dev - orchestration, parallelization, routing, evaluator-optimizer, and human-in-the-loop. Use when building LLM-powered tasks that need parallel workers, approval gates, tool calling, or multi-step agent workflows."
}

AI Agent Patterns with Trigger.dev

Build production-ready AI agents using Trigger.dev's durable execution.

Pattern Selection

Need to...                              → Use
─────────────────────────────────────────────────────
Process items in parallel               → Parallelization
Route to different models/handlers      → Routing
Chain steps with validation gates       → Prompt Chaining
Coordinate multiple specialized tasks   → Orchestrator-Workers
Self-improve until quality threshold    → Evaluator-Optimizer
Pause for human approval                → Human-in-the-Loop (waitpoints.md)
Stream progress to frontend             → Realtime Streams (streaming.md)
Let LLM call your tasks as tools        → ai.tool (ai-tool.md)

Core Patterns

1. Prompt Chaining (Sequential with Gates)

Chain LLM calls with validation between steps. Fail early if intermediate output is bad.

import { task } from "@trigger.dev/sdk";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

export const translateCopy = task({
  id: "translate-copy",
  run: async ({ text, targetLanguage, maxWords }) => {
    // Step 1: Generate
    const draft = await generateText({
      model: openai("gpt-4o"),
      prompt: `Write marketing copy about: ${text}`,
    });

    // Gate: Validate before continuing
    const wordCount = draft.text.split(/\s+/).length;
    if (wordCount > maxWords) {
      throw new Error(`Draft too long: ${wordCount} > ${maxWords}`);
    }

    // Step 2: Translate (only if gate passed)
    const translated = await generateText({
      model: openai("gpt-4o"),
      prompt: `Translate to ${targetLanguage}: ${draft.text}`,
    });

    return { draft: draft.text, translated: translated.text };
  },
});

2. Routing (Classify → Dispatch)

Use a cheap model to classify, then route to appropriate handler.

import { task } from "@trigger.dev/sdk";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const routingSchema = z.object({
  model: z.enum(["gpt-4o", "o1-mini"]),
  reason: z.string(),
});

export const routeQuestion = task({
  id: "route-question",
  run: async ({ question }) => {
    // Cheap classification call
    const routing = await generateText({
      model: openai("gpt-4o-mini"),
      messages: [
        {
          role: "system",
          content: `Classify question complexity. Return JSON: {"model": "gpt-4o" | "o1-mini", "reason": "..."}
          - gpt-4o: simple factual questions
          - o1-mini: complex reasoning, math, code`,
        },
        { role: "user", content: question },
      ],
    });

    const { model } = routingSchema.parse(JSON.parse(routing.text));

    // Route to selected model
    const answer = await generateText({
      model: openai(model),
      prompt: question,
    });

    return { answer: answer.text, routedTo: model };
  },
});

3. Parallelization

Run independent LLM calls simultaneously with batch.triggerByTaskAndWait.

import { batch, task } from "@trigger.dev/sdk";

export const analyzeContent = task({
  id: "analyze-content",
  run: async ({ text }) => {
    // All three run in parallel
    const { runs: [sentiment, summary, moderation] } = await batch.triggerByTaskAndWait([
      { task: analyzeSentiment, payload: { text } },
      { task: summarizeText, payload: { text } },
      { task: moderateContent, payload: { text } },
    ]);

    // Check moderation first
    if (moderation.ok && moderation.output.flagged) {
      return { error: "Content flagged", reason: moderation.output.reason };
    }

    return {
      sentiment: sentiment.ok ? sentiment.output : null,
      summary: summary.ok ? summary.output : null,
    };
  },
});

See: references/orchestration.md for advanced patterns


4. Orchestrator-Workers (Fan-out/Fan-in)

Orchestrator extracts work items, fans out to workers, aggregates results.

import { batch, task } from "@trigger.dev/sdk";

export const factChecker = task({
  id: "fact-checker",
  run: async ({ article }) => {
    // Step 1: Extract claims (sequential - need output first)
    const { runs: [extractResult] } = await batch.triggerByTaskAndWait([
      { task: extractClaims, payload: { article } },
    ]);

    if (!extractResult.ok) throw new Error("Failed to extract claims");
    const claims = extractResult.output;

    // Step 2: Fan-out - verify all claims in parallel
    const { runs } = await batch.triggerByTaskAndWait(
      claims.map(claim => ({ task: verifyClaim, payload: claim }))
    );

    // Step 3: Fan-in - aggregate results
    const verified = runs
      .filter((r): r is typeof r & { ok: true } => r.ok)
      .map(r => r.output);

    return { claims, verifications: verified };
  },
});

5. Evaluator-Optimizer (Self-Refining Loop)

Generate → Evaluate → Retry with feedback until approved.

import { task } from "@trigger.dev/sdk";

export const refineTranslation = task({
  id: "refine-translation",
  run: async ({ text, targetLanguage, feedback, attempt = 0 }) => {
    // Bail condition
    if (attempt >= 5) {
      return { text, status: "MAX_ATTEMPTS", attempts: attempt };
    }

    // Generate (with feedback if retrying)
    const prompt = feedback
      ? `Improve this translation based on feedback:\n${feedback}\n\nOriginal: ${text}`
      : `Translate to ${targetLanguage}: ${text}`;

    const translation = await generateText({
      model: openai("gpt-4o"),
      prompt,
    });

    // Evaluate
    const evaluation = await generateText({
      model: openai("gpt-4o"),
      prompt: `Evaluate translation quality. Reply APPROVED or provide specific feedback:\n${translation.text}`,
    });

    if (evaluation.text.includes("APPROVED")) {
      return { text: translation.text, status: "APPROVED", attempts: attempt + 1 };
    }

    // Recursive self-call with feedback
    return refineTranslation.triggerAndWait({
      text,
      targetLanguage,
      feedback: evaluation.text,
      attempt: attempt + 1,
    }).unwrap();
  },
});

Trigger-Specific Features

Feature What it enables Reference
Waitpoints Human approval gates, external callbacks references/waitpoints.md
Streams Real-time progress to frontend references/streaming.md
ai.tool Let LLMs call your tasks as tools references/ai-tool.md
batch.triggerByTaskAndWait Typed parallel execution references/orchestration.md

Error Handling

const { runs } = await batch.triggerByTaskAndWait([...]);

// Check individual results
for (const run of runs) {
  if (run.ok) {
    console.log(run.output);  // Typed output
  } else {
    console.error(run.error);  // Error details
    console.log(run.taskIdentifier);  // Which task failed
  }
}

// Or filter by task type
const verifications = runs
  .filter((r): r is typeof r & { ok: true } =>
    r.ok && r.taskIdentifier === "verify-claim"
  )
  .map(r => r.output);

Quick Reference

// Trigger and wait for result
const result = await myTask.triggerAndWait(payload);
if (result.ok) console.log(result.output);

// Batch trigger same task
const results = await myTask.batchTriggerAndWait([
  { payload: item1 },
  { payload: item2 },
]);

// Batch trigger different tasks (typed)
const { runs } = await batch.triggerByTaskAndWait([
  { task: taskA, payload: { foo: 1 } },
  { task: taskB, payload: { bar: "x" } },
]);

// Self-recursion with unwrap
return myTask.triggerAndWait(newPayload).unwrap();
用于配置 Trigger.dev 项目的 trigger.config.ts,支持设置构建扩展(如 Prisma、Playwright、FFmpeg、Python)、重试策略、运行时环境及环境变量同步等部署相关设置。
配置 Trigger.dev 项目 添加数据库或浏览器自动化支持 自定义部署和构建设置
.agents/skills/trigger-config/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill trigger-config -g -y
SKILL.md
Frontmatter
{
    "name": "trigger-config",
    "description": "Configure Trigger.dev projects with trigger.config.ts. Use when setting up build extensions for Prisma, Playwright, FFmpeg, Python, or customizing deployment settings."
}

Trigger.dev Configuration

Configure your Trigger.dev project with trigger.config.ts and build extensions.

When to Use

  • Setting up a new Trigger.dev project
  • Adding database support (Prisma, TypeORM)
  • Configuring browser automation (Playwright, Puppeteer)
  • Adding media processing (FFmpeg)
  • Running Python scripts from tasks
  • Syncing environment variables
  • Installing system packages

Basic Configuration

// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "<project-ref>",
  dirs: ["./trigger"],
  runtime: "node", // "node", "node-22", or "bun"
  logLevel: "info",

  retries: {
    enabledInDev: false,
    default: {
      maxAttempts: 3,
      minTimeoutInMs: 1000,
      maxTimeoutInMs: 10000,
      factor: 2,
    },
  },

  build: {
    extensions: [], // Add extensions here
  },
});

Common Build Extensions

Prisma

import { prismaExtension } from "@trigger.dev/build/extensions/prisma";

export default defineConfig({
  // ...
  build: {
    extensions: [
      prismaExtension({
        schema: "prisma/schema.prisma",
        migrate: true,
        directUrlEnvVarName: "DIRECT_DATABASE_URL",
      }),
    ],
  },
});

Playwright (Browser Automation)

import { playwright } from "@trigger.dev/build/extensions/playwright";

extensions: [
  playwright({
    browsers: ["chromium"], // or ["chromium", "firefox", "webkit"]
  }),
]

Puppeteer

import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";

extensions: [puppeteer()]

// Set env var: PUPPETEER_EXECUTABLE_PATH="/usr/bin/google-chrome-stable"

FFmpeg (Media Processing)

import { ffmpeg } from "@trigger.dev/build/extensions/core";

extensions: [
  ffmpeg({ version: "7" }),
]
// Automatically sets FFMPEG_PATH and FFPROBE_PATH

Python

import { pythonExtension } from "@trigger.dev/build/extensions/python";

extensions: [
  pythonExtension({
    scripts: ["./python/**/*.py"],
    requirementsFile: "./requirements.txt",
    devPythonBinaryPath: ".venv/bin/python",
  }),
]

// Usage in tasks:
const result = await python.runScript("./python/process.py", ["arg1"]);

System Packages (apt-get)

import { aptGet } from "@trigger.dev/build/extensions/core";

extensions: [
  aptGet({
    packages: ["imagemagick", "curl"],
  }),
]

Additional Files

import { additionalFiles } from "@trigger.dev/build/extensions/core";

extensions: [
  additionalFiles({
    files: ["./assets/**", "./templates/**"],
  }),
]

Environment Variable Sync

import { syncEnvVars } from "@trigger.dev/build/extensions/core";

extensions: [
  syncEnvVars(async (ctx) => {
    return [
      { name: "API_KEY", value: await getSecret(ctx.environment) },
      { name: "ENV", value: ctx.environment },
    ];
  }),
]

Common Extension Combinations

Full-Stack Web App

extensions: [
  prismaExtension({ schema: "prisma/schema.prisma", migrate: true }),
  additionalFiles({ files: ["./assets/**"] }),
  syncEnvVars(async (ctx) => [...envVars]),
]

AI/ML Processing

extensions: [
  pythonExtension({
    scripts: ["./ai/**/*.py"],
    requirementsFile: "./requirements.txt",
  }),
  ffmpeg({ version: "7" }),
]

Web Scraping

extensions: [
  playwright({ browsers: ["chromium"] }),
  additionalFiles({ files: ["./selectors.json"] }),
]

Global Lifecycle Hooks

export default defineConfig({
  // ...
  onStartAttempt: async ({ payload, ctx }) => {
    console.log("Task starting:", ctx.task.id);
  },
  onSuccess: async ({ payload, output, ctx }) => {
    console.log("Task succeeded");
  },
  onFailure: async ({ payload, error, ctx }) => {
    console.error("Task failed:", error);
  },
});

Machine Defaults

export default defineConfig({
  // ...
  defaultMachine: "medium-1x",
  maxDuration: 300, // seconds
});

Telemetry Integration

import { PrismaInstrumentation } from "@prisma/instrumentation";

export default defineConfig({
  // ...
  telemetry: {
    instrumentations: [new PrismaInstrumentation()],
  },
});

Best Practices

  1. Pin versions for reproducible builds
  2. Use syncEnvVars for dynamic secrets
  3. Add native modules to build.external array
  4. Debug with --log-level debug --dry-run

Extensions only affect deployment, not local development.

See references/config.md for complete documentation.

分析Trigger.dev任务配置与运行数据,识别机器过大、重试浪费等成本优化点。需MCP工具支持动态分析,结合静态代码扫描提供省钱建议。
减少支出 优化成本 审计使用情况 调整机器规格 审查任务效率
.agents/skills/trigger-cost-savings/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill trigger-cost-savings -g -y
SKILL.md
Frontmatter
{
    "name": "trigger-cost-savings",
    "description": "Analyze Trigger.dev tasks, schedules, and runs for cost optimization opportunities. Use when asked to reduce spend, optimize costs, audit usage, right-size machines, or review task efficiency. Requires Trigger.dev MCP tools for run analysis."
}

Trigger.dev Cost Savings Analysis

Analyze task runs and configurations to find cost reduction opportunities.

Prerequisites: MCP Tools

This skill requires the Trigger.dev MCP server to analyze live run data.

Check MCP availability

Before analysis, verify these MCP tools are available:

  • list_runs — list runs with filters (status, task, time period, machine size)
  • get_run_details — get run logs, duration, and status
  • get_current_worker — get registered tasks and their configurations

If these tools are not available, instruct the user:

To analyze your runs, you need the Trigger.dev MCP server installed.

Run this command to install it:

  npx trigger.dev@latest install-mcp

This launches an interactive wizard that configures the MCP server for your AI client.

Do NOT proceed with run analysis without MCP tools. You can still review source code for static issues (see Static Analysis below).

Load latest cost reduction documentation

Before giving recommendations, fetch the latest guidance:

WebFetch: https://trigger.dev/docs/how-to-reduce-your-spend

Use the fetched content to ensure recommendations are current. If the fetch fails, fall back to the reference documentation in references/cost-reduction.md.

Analysis Workflow

Step 1: Static Analysis (source code)

Scan task files in the project for these issues:

  1. Oversized machines — tasks using large-1x or large-2x without clear need
  2. Missing maxDuration — tasks without execution time limits (runaway cost risk)
  3. Excessive retriesmaxAttempts > 5 without AbortTaskRunError for known failures
  4. Missing debounce — high-frequency triggers without debounce configuration
  5. Missing idempotency — payment/critical tasks without idempotency keys
  6. Polling instead of waitssetTimeout/setInterval/sleep loops instead of wait.for()
  7. Short waitswait.for() with < 5 seconds (not checkpointed, wastes compute)
  8. Sequential instead of batch — multiple triggerAndWait() calls that could use batchTriggerAndWait()
  9. Over-scheduled crons — schedules running more frequently than necessary

Step 2: Run Analysis (requires MCP tools)

Use MCP tools to analyze actual usage patterns:

2a. Identify expensive tasks

list_runs with filters:
- period: "30d" or "7d"
- Sort by duration or cost
- Check across different task IDs

Look for:

  • Tasks with high total compute time (duration x run count)
  • Tasks with high failure rates (wasted retries)
  • Tasks running on large machines with short durations (over-provisioned)

2b. Analyze failure patterns

list_runs with status: "FAILED" or "CRASHED"

For high-failure tasks:

  • Check if failures are retryable (transient) vs permanent
  • Suggest AbortTaskRunError for known non-retryable errors
  • Calculate wasted compute from failed retries

2c. Check machine utilization

get_run_details for sample runs of each task

Compare actual resource usage against machine preset:

  • If a task on large-2x consistently runs in < 1 second, it's over-provisioned
  • If tasks are I/O-bound (API calls, DB queries), they likely don't need large machines

2d. Review schedule frequency

get_current_worker to list scheduled tasks and their cron patterns

Flag schedules that may be too frequent for their purpose.

Step 3: Generate Recommendations

Present findings as a prioritized list with estimated impact:

## Cost Optimization Report

### High Impact
1. **Right-size `process-images` machine** — Currently `large-2x`, average run 2s.
   Switching to `small-2x` could reduce this task's cost by ~16x.
   ```ts
   machine: { preset: "small-2x" }  // was "large-2x"

Medium Impact

  1. Add debounce to sync-user-data — 847 runs/day, often triggered in bursts.
    debounce: { key: `user-${userId}`, delay: "5s" }
    

Low Impact / Best Practices

  1. Add maxDuration to generate-report — No timeout configured.
    maxDuration: 300  // 5 minutes
    

## Machine Preset Costs (relative)

Larger machines cost proportionally more per second of compute:

| Preset | vCPU | RAM | Relative Cost |
|--------|------|-----|---------------|
| micro | 0.25 | 0.25 GB | 0.25x |
| small-1x | 0.5 | 0.5 GB | 1x (baseline) |
| small-2x | 1 | 1 GB | 2x |
| medium-1x | 1 | 2 GB | 2x |
| medium-2x | 2 | 4 GB | 4x |
| large-1x | 4 | 8 GB | 8x |
| large-2x | 8 | 16 GB | 16x |

## Key Principles

- **Waits > 5 seconds are free** — checkpointed, no compute charge
- **Start small, scale up** — default `small-1x` is right for most tasks
- **I/O-bound tasks don't need big machines** — API calls, DB queries wait on network
- **Debounce saves the most on high-frequency tasks** — consolidates bursts into single runs
- **Idempotency prevents duplicate work** — especially important for expensive operations
- **`AbortTaskRunError` stops wasteful retries** — don't retry permanent failures

See `references/cost-reduction.md` for detailed strategies with code examples.
提供Trigger.dev任务实时订阅能力,支持前后端。用于构建进度指示器、实时仪表盘、流式AI响应及React组件,通过WebSockets实时获取任务状态与输出。
需要实时监控后台任务执行进度 构建展示任务状态的实时数据看板 在React前端触发任务并即时反馈结果 实现AI/LLM生成的流式响应展示
.agents/skills/trigger-realtime/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill trigger-realtime -g -y
SKILL.md
Frontmatter
{
    "name": "trigger-realtime",
    "description": "Subscribe to Trigger.dev task runs in real-time from frontend and backend. Use when building progress indicators, live dashboards, streaming AI\/LLM responses, or React components that display task status."
}

Trigger.dev Realtime

Subscribe to task runs and stream data in real-time from frontend and backend.

When to Use

  • Building progress indicators for long-running tasks
  • Creating live dashboards showing task status
  • Streaming AI/LLM responses to the UI
  • React components that trigger and monitor tasks
  • Waiting for user approval in tasks

Authentication

Create Public Access Token (Backend)

import { auth } from "@trigger.dev/sdk";

// Read-only token for specific runs
const publicToken = await auth.createPublicToken({
  scopes: {
    read: {
      runs: ["run_123"],
      tasks: ["my-task"],
    },
  },
  expirationTime: "1h",
});

// Pass this token to your frontend

Create Trigger Token (for frontend triggering)

const triggerToken = await auth.createTriggerPublicToken("my-task", {
  expirationTime: "30m",
});

Backend Subscriptions

import { runs, tasks } from "@trigger.dev/sdk";

// Trigger and subscribe
const handle = await tasks.trigger("my-task", { data: "value" });

for await (const run of runs.subscribeToRun(handle.id)) {
  console.log(`Status: ${run.status}`);
  console.log(`Progress: ${run.metadata?.progress}`);
  
  if (run.status === "COMPLETED") {
    console.log("Output:", run.output);
    break;
  }
}

// Subscribe to tagged runs
for await (const run of runs.subscribeToRunsWithTag("user-123")) {
  console.log(`Run ${run.id}: ${run.status}`);
}

// Subscribe to batch
for await (const run of runs.subscribeToBatch(batchId)) {
  console.log(`Batch run ${run.id}: ${run.status}`);
}

React Hooks

Installation

npm add @trigger.dev/react-hooks

Trigger Task from React

"use client";
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function TaskTrigger({ accessToken }: { accessToken: string }) {
  const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof myTask>(
    "my-task",
    { accessToken }
  );

  return (
    <div>
      <button 
        onClick={() => submit({ data: "value" })} 
        disabled={isLoading}
      >
        Start Task
      </button>
      
      {run && (
        <div>
          <p>Status: {run.status}</p>
          <p>Progress: {run.metadata?.progress}%</p>
          {run.output && <p>Result: {JSON.stringify(run.output)}</p>}
        </div>
      )}
    </div>
  );
}

Subscribe to Existing Run

"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function RunStatus({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, error } = useRealtimeRun<typeof myTask>(runId, {
    accessToken,
    onComplete: (run) => {
      console.log("Completed:", run.output);
    },
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;

  return (
    <div>
      <p>Status: {run.status}</p>
      <p>Progress: {run.metadata?.progress || 0}%</p>
    </div>
  );
}

Subscribe to Tagged Runs

"use client";
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";

function UserTasks({ userId, accessToken }: { userId: string; accessToken: string }) {
  const { runs } = useRealtimeRunsWithTag(`user-${userId}`, { accessToken });

  return (
    <ul>
      {runs.map((run) => (
        <li key={run.id}>{run.id}: {run.status}</li>
      ))}
    </ul>
  );
}

Realtime Streams (AI/LLM)

Define Stream (shared location)

// trigger/streams.ts
import { streams } from "@trigger.dev/sdk";

export const aiStream = streams.define<string>({
  id: "ai-output",
});

Pipe Stream in Task

import { task } from "@trigger.dev/sdk";
import { aiStream } from "./streams";

export const streamingTask = task({
  id: "streaming-task",
  run: async (payload: { prompt: string }) => {
    const completion = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: payload.prompt }],
      stream: true,
    });

    const { waitUntilComplete } = aiStream.pipe(completion);
    await waitUntilComplete();
  },
});

Read Stream in React

"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import { aiStream } from "../trigger/streams";

function AIResponse({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { parts, error } = useRealtimeStream(aiStream, runId, {
    accessToken,
    throttleInMs: 50,
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!parts) return <div>Waiting for response...</div>;

  return <div>{parts.join("")}</div>;
}

Wait Tokens (Human-in-the-loop)

In Task

import { task, wait } from "@trigger.dev/sdk";

export const approvalTask = task({
  id: "approval-task",
  run: async (payload) => {
    // Process initial data
    const processed = await processData(payload);

    // Wait for human approval
    const approval = await wait.forToken<{ approved: boolean }>({
      token: `approval-${payload.id}`,
      timeoutInSeconds: 86400, // 24 hours
    });

    if (approval.approved) {
      return await finalizeData(processed);
    }
    
    throw new Error("Not approved");
  },
});

Complete Token from React

"use client";
import { useWaitToken } from "@trigger.dev/react-hooks";

function ApprovalButton({ tokenId, accessToken }: { tokenId: string; accessToken: string }) {
  const { complete } = useWaitToken(tokenId, { accessToken });

  return (
    <div>
      <button onClick={() => complete({ approved: true })}>
        Approve
      </button>
      <button onClick={() => complete({ approved: false })}>
        Reject
      </button>
    </div>
  );
}

Run Object Properties

Property Description
id Unique run identifier
status QUEUED, EXECUTING, COMPLETED, FAILED, CANCELED
payload Task input (typed)
output Task result (typed, when completed)
metadata Real-time updatable data
createdAt Start timestamp
costInCents Execution cost

Best Practices

  1. Scope tokens narrowly — only grant necessary permissions
  2. Set expiration times — don't use long-lived tokens
  3. Use typed hooks — pass task types for proper inference
  4. Handle errors — always check for errors in hooks
  5. Throttle streams — use throttleInMs to control re-renders

See references/realtime.md for complete documentation.

用于在项目中初始化 Trigger.dev。涵盖安装 SDK、运行 npx trigger init 生成配置文件和目录、配置 trigger.config.ts 及设置环境变量,帮助开发者快速搭建异步任务处理环境并启动开发服务器。
首次添加 Trigger.dev 到项目 创建或初始化 trigger.config.ts 初始化 trigger 目录结构
.agents/skills/trigger-setup/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill trigger-setup -g -y
SKILL.md
Frontmatter
{
    "name": "trigger-setup",
    "description": "Set up Trigger.dev in your project. Use when adding Trigger.dev for the first time, creating trigger.config.ts, or initializing the trigger directory."
}

Trigger.dev Setup

Get Trigger.dev running in your project in minutes.

When to Use

  • Adding Trigger.dev to an existing project
  • Creating your first task
  • Setting up trigger.config.ts
  • Connecting to Trigger.dev cloud

Prerequisites

Quick Start

1. Install the SDK

npm install @trigger.dev/sdk

2. Initialize Your Project

npx trigger init

This creates:

  • trigger.config.ts - project configuration
  • trigger/ directory - where your tasks live
  • trigger/example.ts - a sample task

3. Configure trigger.config.ts

import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "proj_xxxxx", // From dashboard
  dirs: ["./trigger"],
});

4. Create Your First Task

// trigger/my-task.ts
import { task } from "@trigger.dev/sdk";

export const myFirstTask = task({
  id: "my-first-task",
  run: async (payload: { name: string }) => {
    console.log(`Hello, ${payload.name}!`);
    return { message: `Processed ${payload.name}` };
  },
});

5. Start Development Server

npx trigger dev

6. Trigger Your Task

From your app code:

import { tasks } from "@trigger.dev/sdk";
import type { myFirstTask } from "./trigger/my-task";

await tasks.trigger<typeof myFirstTask>("my-first-task", {
  name: "World",
});

Or from the Trigger.dev dashboard "Test" tab.

Project Structure

your-project/
├── trigger.config.ts    # Required - project config
├── trigger/             # Required - task files
│   ├── my-task.ts
│   └── another-task.ts
├── package.json
└── ...

Environment Variables

Create .env or set in your environment:

TRIGGER_SECRET_KEY=tr_dev_xxxxx  # From dashboard > API Keys

Common Issues

"No tasks found"

  • Ensure tasks are exported from files in dirs folders
  • Check trigger.config.ts points to correct directories

"Project not found"

  • Verify project in config matches dashboard
  • Check TRIGGER_SECRET_KEY is set

"Task not registered"

  • Restart npx trigger dev after adding new tasks
  • Tasks must use task() or schemaTask() from @trigger.dev/sdk

Next Steps

  • Add retry logic → see trigger-tasks skill
  • Configure build extensions → see trigger-config skill
  • Build AI workflows → see trigger-agents skill
  • Add real-time UI → see trigger-realtime skill
用于构建基于Trigger.dev的可靠后台任务、AI工作流及定时作业。涵盖任务定义、触发、重试、队列管理及并发控制,强调使用SDK规范与错误处理。
创建后台异步任务或工作流 实现AI代理的长运行逻辑 处理Webhook、邮件或文件上传 设置定时Cron任务 需要非阻塞执行的业务场景
.agents/skills/trigger-tasks/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill trigger-tasks -g -y
SKILL.md
Frontmatter
{
    "name": "trigger-tasks",
    "description": "Build AI agents, workflows and durable background tasks with Trigger.dev. Use when creating tasks, triggering jobs, handling retries, scheduling cron jobs, or implementing queues and concurrency control."
}

Trigger.dev Tasks

Build durable background tasks that run reliably with automatic retries, queuing, and observability.

When to Use

  • Creating background jobs or async workflows
  • Building AI agents that need long-running execution
  • Processing webhooks, emails, or file uploads
  • Scheduling recurring tasks (cron)
  • Any work that shouldn't block your main application

Critical Rules

  1. Always use @trigger.dev/sdk — never use deprecated client.defineJob
  2. Check result.ok before accessing result.output from triggerAndWait()
  3. Never use Promise.all with triggerAndWait() or wait.* calls
  4. Export tasks from files in your trigger/ directory

Basic Task

import { task } from "@trigger.dev/sdk";

export const processData = task({
  id: "process-data",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
  },
  run: async (payload: { userId: string; data: any[] }) => {
    console.log(`Processing ${payload.data.length} items`);
    return { processed: payload.data.length };
  },
});

Schema Task (Validated Input)

import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";

export const validatedTask = schemaTask({
  id: "validated-task",
  schema: z.object({
    name: z.string(),
    email: z.string().email(),
  }),
  run: async (payload) => {
    // payload is typed and validated
    return { message: `Hello ${payload.name}` };
  },
});

Triggering Tasks

From Backend Code

import { tasks } from "@trigger.dev/sdk";
import type { processData } from "./trigger/tasks";

// Single trigger (fire and forget)
const handle = await tasks.trigger<typeof processData>("process-data", {
  userId: "123",
  data: [{ id: 1 }],
});

// Batch trigger (up to 1,000 items, 3MB per payload)
const batchHandle = await tasks.batchTrigger<typeof processData>("process-data", [
  { payload: { userId: "123", data: [] } },
  { payload: { userId: "456", data: [] } },
]);

From Inside Tasks

export const parentTask = task({
  id: "parent-task",
  run: async (payload) => {
    // Fire and forget
    const handle = await childTask.trigger({ data: "value" });

    // Wait for result - returns Result object, NOT direct output
    const result = await childTask.triggerAndWait({ data: "value" });
    if (result.ok) {
      console.log("Output:", result.output);
    } else {
      console.error("Failed:", result.error);
    }

    // Quick unwrap (throws on error)
    const output = await childTask.triggerAndWait({ data: "value" }).unwrap();

    // Batch with wait
    const results = await childTask.batchTriggerAndWait([
      { payload: { data: "item1" } },
      { payload: { data: "item2" } },
    ]);
  },
});

Waits

import { task, wait } from "@trigger.dev/sdk";

export const taskWithWaits = task({
  id: "task-with-waits",
  run: async (payload) => {
    await wait.for({ seconds: 30 });
    await wait.for({ minutes: 5 });
    await wait.until({ date: new Date("2024-12-25") });

    // Wait for external approval
    await wait.forToken({
      token: "user-approval-token",
      timeoutInSeconds: 3600,
    });
  },
});

Waits > 5 seconds are checkpointed and don't count toward compute.

Concurrency & Queues

import { task, queue } from "@trigger.dev/sdk";

// Shared queue
const emailQueue = queue({
  name: "email-processing",
  concurrencyLimit: 5,
});

// Task-level concurrency
export const oneAtATime = task({
  id: "sequential-task",
  queue: { concurrencyLimit: 1 },
  run: async (payload) => {
    // Only one instance runs at a time
  },
});

// Use shared queue
export const emailTask = task({
  id: "send-email",
  queue: emailQueue,
  run: async (payload) => {},
});

// Per-tenant concurrency (at trigger time)
await childTask.trigger(payload, {
  queue: {
    name: `user-${userId}`,
    concurrencyLimit: 2,
  },
});

Debouncing

Consolidate rapid triggers into a single execution:

await myTask.trigger(
  { userId: "123" },
  {
    debounce: {
      key: "user-123-update",
      delay: "5s",
      mode: "trailing", // Use latest payload (default: "leading")
    },
  }
);

Idempotency

import { task, idempotencyKeys } from "@trigger.dev/sdk";

export const paymentTask = task({
  id: "process-payment",
  run: async (payload: { orderId: string }) => {
    const key = await idempotencyKeys.create(`payment-${payload.orderId}`);

    await chargeCustomer.trigger(payload, {
      idempotencyKey: key,
      idempotencyKeyTTL: "24h",
    });
  },
});

Error Handling & Retries

import { task, retry, AbortTaskRunError } from "@trigger.dev/sdk";

export const resilientTask = task({
  id: "resilient-task",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
  },
  catchError: async ({ error, ctx }) => {
    if (error.code === "FATAL_ERROR") {
      throw new AbortTaskRunError("Cannot retry");
    }
    return { retryAt: new Date(Date.now() + 60000) };
  },
  run: async (payload) => {
    // Retry specific operations
    const result = await retry.onThrow(
      async () => unstableApiCall(payload),
      { maxAttempts: 3 }
    );

    // HTTP retries with conditions
    const response = await retry.fetch("https://api.example.com", {
      retry: {
        maxAttempts: 5,
        condition: (res, err) => res?.status === 429 || res?.status >= 500,
      },
    });
  },
});

Scheduled Tasks (Cron)

import { schedules } from "@trigger.dev/sdk";

// Declarative schedule
export const dailyTask = schedules.task({
  id: "daily-cleanup",
  cron: "0 0 * * *", // Midnight UTC
  run: async (payload) => {
    // payload.timestamp - scheduled time
    // payload.timezone - IANA timezone
    // payload.scheduleId - schedule identifier
  },
});

// With timezone
export const tokyoTask = schedules.task({
  id: "tokyo-morning",
  cron: { pattern: "0 9 * * *", timezone: "Asia/Tokyo" },
  run: async () => {},
});

// Dynamic/multi-tenant schedules
await schedules.create({
  task: "reminder-task",
  cron: "0 8 * * *",
  timezone: "America/New_York",
  externalId: userId,
  deduplicationKey: `${userId}-daily`,
});

Metadata & Progress

import { task, metadata } from "@trigger.dev/sdk";

export const batchProcessor = task({
  id: "batch-processor",
  run: async (payload: { items: any[] }) => {
    metadata.set("progress", 0).set("total", payload.items.length);

    for (let i = 0; i < payload.items.length; i++) {
      await processItem(payload.items[i]);
      metadata.set("progress", ((i + 1) / payload.items.length) * 100);
    }

    metadata.set("status", "completed");
  },
});

Tags

import { task, tags } from "@trigger.dev/sdk";

export const processUser = task({
  id: "process-user",
  run: async (payload: { userId: string }) => {
    await tags.add(`user_${payload.userId}`);
  },
});

// Trigger with tags
await processUser.trigger(
  { userId: "123" },
  { tags: ["priority", "user_123"] }
);

Machine Presets

export const heavyTask = task({
  id: "heavy-computation",
  machine: { preset: "large-2x" }, // 8 vCPU, 16 GB RAM
  maxDuration: 1800, // 30 minutes
  run: async (payload) => {},
});
Preset vCPU RAM
micro 0.25 0.25 GB
small-1x 0.5 0.5 GB (default)
small-2x 1 1 GB
medium-1x 1 2 GB
medium-2x 2 4 GB
large-1x 4 8 GB
large-2x 8 16 GB

Best Practices

  1. Make tasks idempotent — safe to retry without side effects
  2. Use queues to prevent overwhelming external services
  3. Configure appropriate retries with exponential backoff
  4. Track progress with metadata for long-running tasks
  5. Use debouncing for user activity and webhook bursts
  6. Match machine size to computational requirements

See references/ for detailed documentation on each feature.

提供Liveblocks实时协作、AI集成及存储开发的最佳实践。涵盖React/Node SDK使用、身份验证(ID/Access令牌)、房间管理、权限控制及自定义组件(如编辑器、评论、聊天)的实现与调试指南。
开发或调试基于Liveblocks的实时协作功能 询问Liveblocks身份验证或权限配置问题 实现AI协作者或自定义实时多人游戏逻辑 集成Tiptap/Lexical等编辑器并处理冲突解决
.agents/skills/liveblocks-best-practices/SKILL.md
npx skills add adrianhajdin/ghost-ai --skill liveblocks-best-practices -g -y
SKILL.md
Frontmatter
{
    "name": "liveblocks-best-practices",
    "license": "Apache License 2.0",
    "metadata": {
        "author": "liveblocks",
        "version": "1.0.0"
    },
    "description": "Use this skill when building, debugging, or answering questions about Liveblocks. Liveblocks gives you the building blocks and infrastructure to enable people and AI to work together inside your app, powering realtime collaboration.\nLiveblocks features include collaboration, rooms, organizations, workspaces, comments, composer, threads, notifications, multiplayer, conflict resolution, realtime presence, avatar stacks, AI collaborators, AI agents, text editors, Tiptap, BlockNote, Lexical, React Flow, Chat SDK.\nCommon components include AiChat, Thread, InboxNotification, Composer, Toolbar (for Lexical Tiptap), FloatingToolbar, FloatingComposer, FloatingThreads, AnchoredThreads.\nCommon hooks include useThreads, useStorage, useMutation, useOthers, useInboxNotifications, useAiChats.\nCommon issues are related to authentication (ID tokens vs access tokens), permissions, room limits, connection errors, user info."
}

Liveblocks best practices

When to use this skill

Use this skill when implementing features using any Liveblocks packages, for example @liveblocks/react or @liveblocks/node. This could be related to features like realtime multiplayer, commenting, notifications, cursors, avatar stacks, AI, and more. This also includes technologies like Liveblocks Storage, Yjs, Tiptap, BlockNote, Lexical, React Flow, tldraw, and more.

Quick reference

A number of reference files are available in the references/ directory. You must read the list below, and load relevant files as needed. For example, add-user-information is available in the references/add-user-information.md file.

References

  • add-user-information: Add user information to your application, such as name, avatar, color, and more. Users will no longer be anonymous. Useful for displaying user info in comments, comments mention suggestions, notifications, cursors, avatar stacks.
  • ai-as-a-collaborator: Use agentic workflows to allow AI to collaborate like a human inside your Liveblocks application.
  • ai-chats-with-tools-knowledge-components: Set up and troubleshoot chats with RAG, knowledge, custom components, tool calling, custom models. Different to AI as a collaborator, as it is NOT workflow based.
  • auth-endpoint-callback: Use a callback function to authenticate users, instead of passing a string to authEndpoint. Useful for passing custom headers to your back end, and for preventing automatic reconnection when the token is invalid.
  • authenticating-with-access-tokens: Alternative method for authenticating users with their ID and info, best for very simple permissions.
  • authenticating-with-id-tokens: Recommended method for authenticating users with their ID and info, best for complex permissions.
  • avoid-hitting-user-limit-in-rooms: How to avoid rooms filling up with users.
  • compartmentalize-resources-with-organizations: Use organizations to create workspaces/tenants and compartmentalize resources, such as rooms, notifications, comment threads, and more. You can filter by organization when listing rooms and more.
  • create-custom-comment-composer: Build your own commenting composer (advanced use cases only).
  • create-custom-realtime-multiplayer: Use Liveblocks Storage to build fully custom conflict-resolved multiplayer apps, like Figma, Pitch, Spline. Helpful for one-off realtime features too, like simple properties in a document.
  • create-custom-text-editor-toolbar: Set up a styled toolbar, static or floating next to your selection, in Tiptap and Lexical.
  • create-rooms-manually: Always create rooms yourself in production applications, setting permissions, organization, metadata.
  • customize-thread-components: Use slots to customize comments inside of threads, and their different parts.
  • dark-mode-styles: You can import CSS styling for dark mode themes.
  • develop-and-test-locally: Set up a local dev server, and use it for Continuous Integration (CI) and End-to-End (E2E) testing. Connect to your app not online.
  • devtools-extension: Inspect Liveblocks Storage, Yjs, presence, events, and connected users with DevTools extensions for Chrome, Firefox, Edge.
  • edit-component-text-strings: Override strings in default components, such as button values, tooltip text, error text, more. Also helpful for setting other languages.
  • exhaustive-deps-with-usemutation: Prevent and fix stale-closure bugs with useMutation by configuring the react-hooks/exhaustive-deps ESLint rule.
  • handling-connection-errors: Handle problems joining rooms because of permissions, authentication, changed room IDs.
  • handling-full-rooms: Handle problems caused by joining full rooms.
  • handling-hook-and-component-errors: Handle errors caused by hooks and pre-built components.
  • handling-unstable-connections: Implement fallbacks and error messages when users have poor network conditions.
  • log-out-of-liveblocks: Rarely useful, but helpful in specific SPA situations where you cannot navigate the page to log out.
  • multiplayer-react-flow: Use the Liveblocks React Flow package to create multiplayer diagrams with cursors and more.
  • multiple-text-editors: Add multiple Tiptap or BlockNote editors to the same page. Optionally use Storage to hold their field IDs.
  • offline-support-in-text-editors: Give your text editors the illusion of a quicker load time.
  • override-css-variables: Add custom styles to the default components by overriding Liveblocks CSS variables.
  • performant-others-and-presence: Prevent unnecessary presence renders by using useOtherConnectionIds, useOthersMapped, useOther, and shallow to update components only when necessary.
  • performant-storage-with-selectors: Prevent unnecessary storage renders by using useStorage selectors and shallow.
  • prevent-unsaved-changes-being-lost: Stop losing unsynched or unsaved changes.
  • primitive-component-parts: Use primitives to create custom components or to merge components from your design system into Liveblocks UI.
  • remove-liveblocks-branding: A Liveblocks logo badge appears in the bottom right of the screen, this is how to remove it.
  • rendering-error-components: Use ErrorBoundary to structure your app with error fallbacks.
  • rendering-loading-components: Use ClientSideSuspense to performantly structure your app with loading skeletons and spinners.
  • send-comment-notification-emails: Send email notifications to users when they have unread comments. These comments are sent via webhooks, and are triggered when a user is mentioned, or has participated in a thread.
  • smoother-realtime-updates: Make presence and storage run at a higher frame rate, appearing more smooth. Using this option, updates can be received more frequently.
  • suspense-vs-regular-hooks: You must read this when using any Liveblocks hooks. React suspense uses ErrorBoundary and ClientSideSuspense components. Regular hooks use { error, isLoading } values.
  • tiptap-best-practices: Server-side rendering, starter kit extensions, initial content, unsaved changes, more.
  • trigger-custom-notifications: Trigger any kind of notification in your inbox, even those unrelated to commenting.
  • type-liveblocks-correctly: Always use TypeScript to type Liveblocks where available. Presence, others, user info, storage, metadata, room info, notifications activities, can all be automatically typed.
  • url-params-in-room-id: Use URL params in room IDs to create rooms, and incorporate document titles in the URL.
  • utility-components: Import a ready-made emoji picker, or human-readable timestamps, durations, file sizes.
  • yjs-best-practices: YKeyValue, subdocuments, V2 encoding, double imports, getYjsProviderForRoom. NOT relevant if you're using Tiptap, BlockNote, or Lexical.
  • z-index-issues: Fix z-index problems by targeting portaled elements.

Note

Some files link to markdown files in the Liveblocks docs, for example:

https://liveblocks.io/docs/concepts.md

When linking users to these pages, remove .md from the link, so they can view the full content:

https://liveblocks.io/docs/concepts

Liveblocks packages

Always check if we provide a package for your technology. For example, if you're using React Flow, you should use @liveblocks/react-flow.

  • @liveblocks/client: JavaScript client. Can use with any framework, e.g. Vue, Svelte, vanilla JS.
  • @liveblocks/react: React client. Contains hooks and components..
  • @liveblocks/react-ui: Pre-built React UI components and primitives. @liveblocks/react for data and hooks.
  • @liveblocks/react-tiptap: Collaborative Tiptap integration for React.
  • @liveblocks/react-blocknote: Collaborative BlockNote integration for React.
  • @liveblocks/react-lexical: Collaborative Lexical integration for React.
  • @liveblocks/node-lexical: Server-side Lexical utilities for Node.js.
  • @liveblocks/node-prosemirror: Server-side ProseMirror utilities For Node.js. Also works with Tiptap and BlockNote.
  • @liveblocks/react-flow: Multiplayer React Flow.
  • @liveblocks/yjs: Yjs provider backed by Liveblocks rooms.
  • @liveblocks/redux: Sync Liveblocks room state with a Redux store.
  • @liveblocks/zustand: Sync Liveblocks room state with Zustand.
  • @liveblocks/node: Node.js server SDK. Use for auth and lots more.
  • @liveblocks/emails: Build notification emails more easily.
  • @liveblocks/chat-sdk-adapter: Make multi-platform (Liveblocks, Slack, Discord, etc.) chat bots.
  • Python SDK: Python server SDK. Use for auth and lots more.
  • REST API: HTTP API. Use for auth and lots more.

If a technology is not listed here

If the users want to set up a new technology, first check the following resources to see if a package exists:

Learn more

Learn more in Liveblocks docs.

Главная - Вики-сайт
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-08 20:25
浙ICP备14020137号-1 $Гость$