pocketbase-zod-schema
v1.0.2
Published
PocketBase migration generator using Zod schemas for type-safe database migrations
Maintainers
Readme
pocketbase-zod-schema
Define your PocketBase collections using Zod schemas and automatically generate migration files.
Features
- Type-safe schema definitions - Use Zod to define your PocketBase collections with full TypeScript support
- Automatic migrations - Generate PocketBase-compatible migration files from your schema changes
- Relation support - Easily define single and multiple relations between collections
- View collections - Read-only collections backed by a SQL query, declared alongside the rest
- Permission templates - Built-in templates for common permission patterns
- Index definitions - Declare indexes alongside your schema
- Execution engine - Current state is reconstructed by executing your migrations in a simulated PocketBase JSVM, so loops, helper functions and computed values are understood
- Verification & linting - Round-trip
up()/down()before writing a migration, and catch JavaScript that PocketBase's goja runtime cannot run - CLI & programmatic API - Use the CLI for quick generation or the API for custom workflows
Installation
npm install pocketbase-zod-schema
# or
yarn add pocketbase-zod-schema
# or
pnpm add pocketbase-zod-schemaQuick Start
1. Create a schema file
Create a schema file in your project (e.g., src/schema/post.ts):
import { z } from "zod";
import {
defineCollection,
TextField,
EditorField,
BoolField,
RelationField,
RelationsField,
} from "pocketbase-zod-schema";
// Define the Zod schema
export const PostSchema = z.object({
title: TextField({ min: 1, max: 200 }),
content: EditorField(),
published: BoolField(),
// Single relation to users collection
author: RelationField({ collection: "users" }),
// Multiple relations to tags collection
tags: RelationsField({ collection: "tags", maxSelect: 10 }),
});
// Define the collection with permissions
export const PostCollection = defineCollection({
collectionName: "posts",
schema: PostSchema,
permissions: {
listRule: '@request.auth.id != ""',
viewRule: "",
createRule: '@request.auth.id != ""',
updateRule: "author = @request.auth.id",
deleteRule: "author = @request.auth.id",
},
});2. Configure the CLI
Create pocketbase-migrate.config.js at your project root:
export default {
schema: {
directory: "./src/schema",
exclude: ["*.test.ts", "*.spec.ts"],
},
migrations: {
directory: "./pocketbase/pb_migrations",
},
};3. Generate migrations
npx pocketbase-migrate generateThis will create a migration file in your PocketBase migrations directory.
TypeScript Support: You can use TypeScript (.ts) schema files directly - no compilation needed! The tool automatically handles TypeScript files using tsx.
4. Generate TypeScript types
Generate type-safe TypeScript definitions from your schemas:
# Generate types to pocketbase-types.ts
npx pocketbase-migrate generate-types
# Or specify a custom output path
npx pocketbase-migrate generate-types --output ./src/types/pocketbase.tsThis creates a pocketbase-types.ts file with:
- Type-safe record interfaces for each collection (e.g.,
PostsRecord,UsersRecord) - Response types with expand support (e.g.,
PostsResponse,UsersResponse) - A
TypedPocketBaseinterface for type-safe PocketBase client usage
Usage in your application:
import PocketBase from "pocketbase";
import { TypedPocketBase } from "./pocketbase-types";
const pb = new PocketBase("http://localhost:8090") as TypedPocketBase;
// Full type safety with autocomplete!
const post = await pb.collection("posts").getOne("post-id");
// post is typed as PostsResponse with full autocomplete
// Expand relations with type safety
const postWithAuthor = await pb.collection("posts").getOne("post-id", {
expand: "author"
});
// postWithAuthor.expand?.author is typed as UsersResponseSchema Definition
High-Level Collection Definition
The recommended way to define collections is using defineCollection(), which provides a single entry point for collection name, schema, permissions, indexes, and future features:
import { z } from "zod";
import { defineCollection, TextField, EditorField, RelationField } from "pocketbase-zod-schema";
export const PostCollectionSchema = z.object({
title: TextField({ min: 1, max: 200 }),
content: EditorField(),
author: RelationField({ collection: "users" }),
});
export const PostCollection = defineCollection({
collectionName: "posts",
schema: PostCollectionSchema,
permissions: {
template: "owner-only",
ownerField: "author",
},
indexes: [
"CREATE INDEX idx_posts_author ON posts (author)",
],
});Benefits of defineCollection():
- Explicit collection name - No need to rely on filename conventions
- All metadata in one place - Schema, permissions, indexes together
- Future-proof - Easy to extend with new features
- Cleaner syntax - No nested function calls
Export Pattern: It's recommended to export both the schema and collection definition:
// Define the Zod schema (for type inference and validation)
export const PostSchema = z.object({
title: z.string(),
content: z.string(),
author: RelationField({ collection: "users" }),
});
// Define the collection (used by migration generator, includes metadata)
export const PostCollection = defineCollection({
collectionName: "posts",
schema: PostSchema,
permissions: { /* ... */ },
});This pattern allows:
PostSchema- Used for type inference (z.infer<typeof PostSchema>) and validationPostCollection- Used by the migration generator (has collection metadata)
Field Types
The library provides explicit field helper functions for all PocketBase field types. These helpers embed PocketBase-specific metadata and provide type-safe configuration options.
Field Helper Functions
| Field Helper | PocketBase Type | Description | Example |
|--------------|-----------------|-------------|---------|
| BoolField() | bool | Boolean field | active: BoolField() |
| NumberField(options?) | number | Number field with optional constraints | price: NumberField({ min: 0 }) |
| TextField(options?) | text | Text field with optional constraints | name: TextField({ min: 1, max: 200 }) |
| EmailField() | email | Email field with validation | email: EmailField() |
| URLField() | url | URL field with validation | website: URLField() |
| EditorField() | editor | Rich text editor field | content: EditorField() |
| DateField(options?) | date | Date field with optional constraints | birthdate: DateField() |
| AutodateField(options?) | autodate | Auto-managed timestamp field | createdAt: AutodateField({ onCreate: true }) |
| SelectField(values, options?) | select | Single or multiple select field | status: SelectField(["draft", "published"]) |
| FileField(options?) | file | Single file upload field | avatar: FileField({ mimeTypes: ["image/*"] }) |
| FilesField(options?) | file | Multiple file upload field | images: FilesField({ maxSelect: 5 }) |
| JSONField(schema?, options?) | json | JSON field with optional schema and size cap | metadata: JSONField({ maxSize: "5M" }) |
| GeoPointField() | geoPoint | Geographic coordinates field | location: GeoPointField() |
| RelationField(config) | relation | Single relation field | author: RelationField({ collection: "users" }) |
| RelationsField(config) | relation | Multiple relation field | tags: RelationsField({ collection: "tags" }) |
Field Options
BoolField()
- No options
- Returns:
z.ZodBoolean
NumberField(options?)
min?: number- Minimum value constraintmax?: number- Maximum value constraintnoDecimal?: boolean- Disallow decimal values (integers only)- Returns:
z.ZodNumber
TextField(options?)
min?: number- Minimum length constraintmax?: number- Maximum length constraintpattern?: RegExp | string- Pattern constraint (regex)autogeneratePattern?: string- Auto-generate pattern (e.g.,"[A-Z]{3}-[0-9]{6}")- Returns:
z.ZodString
EmailField()
- No options (includes email validation)
- Returns:
z.ZodString
URLField()
- No options (includes URL validation)
- Returns:
z.ZodString
EditorField()
- No options
- Returns:
z.ZodString
DateField(options?)
min?: Date | string- Minimum date constraintmax?: Date | string- Maximum date constraint- Returns:
z.ZodString
AutodateField(options?)
onCreate?: boolean- Set date automatically on record creationonUpdate?: boolean- Update date automatically on record update- Returns:
z.ZodString
SelectField(values, options?)
values: [string, ...string[]]- Array of allowed values (required)maxSelect?: number- Maximum selections (default: 1, >1 enables multiple selection)- Returns:
z.ZodEnum<T>orz.ZodArray<z.ZodEnum<T>>
FileField(options?)
mimeTypes?: string[]- Allowed MIME types (e.g.,["image/*", "application/pdf"])maxSize?: ByteSize- Maximum file size: a number of bytes, or a string with aK/M/Gsuffix ("5M","1G"; max"8G")thumbs?: string[]- Thumbnail sizes to generate (e.g.,["100x100", "200x200"])protected?: boolean- Whether file requires auth to access- Returns:
z.ZodType<string, File | string>— accepts aFileon input, parses to the stored filename string
FilesField(options?)
- All
FileFieldoptions plus: minSelect?: number- Minimum number of files requiredmaxSelect?: number- Maximum number of files allowed- Returns:
z.ZodType<string[], (File | string)[]>
JSONField(schema?, options?)
schema?: z.ZodTypeAny- Optional Zod schema for JSON structure validationoptions?.maxSize?: number | "5M"- Maximum serialized size, in bytes or with aK/M/Gsuffix. PocketBase applies a 1MB default when unset, so larger payloads must set it. Either argument may be passed alone:JSONField({ maxSize: "5M" })- Returns:
T | z.ZodRecord<z.ZodString, z.ZodAny>
GeoPointField()
- No options
- Returns:
z.ZodObject<{ lon: z.ZodNumber; lat: z.ZodNumber }>
Field Helper Examples
import { z } from "zod";
import {
defineCollection,
BoolField,
NumberField,
TextField,
EmailField,
URLField,
EditorField,
DateField,
AutodateField,
SelectField,
FileField,
FilesField,
JSONField,
GeoPointField,
RelationField,
RelationsField,
} from "pocketbase-zod-schema";
const ProductSchema = z.object({
// Text fields
name: TextField({ min: 1, max: 200 }),
sku: TextField({ autogeneratePattern: "[A-Z]{3}-[0-9]{6}" }),
description: EditorField(),
website: URLField().optional(),
// Number fields
price: NumberField({ min: 0 }),
quantity: NumberField({ min: 0, noDecimal: true }),
rating: NumberField({ min: 0, max: 5 }).optional(),
// Boolean field
active: BoolField(),
featured: BoolField().optional(),
// Date fields
releaseDate: DateField().optional(),
createdAt: AutodateField({ onCreate: true }),
updatedAt: AutodateField({ onUpdate: true }),
// Select fields
status: SelectField(["draft", "published", "archived"]),
categories: SelectField(["electronics", "clothing", "food"], { maxSelect: 3 }),
// File fields
thumbnail: FileField({
mimeTypes: ["image/*"],
maxSize: 5242880, // 5MB
thumbs: ["100x100", "200x200"],
}),
images: FilesField({
mimeTypes: ["image/*"],
maxSelect: 5,
}),
// JSON field
metadata: JSONField(),
settings: JSONField(z.object({
theme: z.string(),
notifications: z.boolean(),
})).optional(),
// GeoPoint field
location: GeoPointField().optional(),
// Relation fields
vendor: RelationField({ collection: "vendors" }),
tags: RelationsField({ collection: "tags", maxSelect: 10 }),
});
export const ProductCollection = defineCollection({
collectionName: "products",
schema: ProductSchema,
permissions: {
listRule: "",
viewRule: "",
createRule: '@request.auth.id != ""',
updateRule: "vendor.owner = @request.auth.id",
deleteRule: "vendor.owner = @request.auth.id",
},
});Backward Compatibility
The library still supports plain Zod types for backward compatibility. The migration generator will infer PocketBase field types from Zod types when field helpers are not used:
| Zod Type | PocketBase Type | Example |
|----------|-----------------|---------|
| z.string() | text | title: z.string() |
| z.string().email() | email | email: z.string().email() |
| z.string().url() | url | website: z.string().url() |
| z.number() | number | price: z.number() |
| z.boolean() | bool | active: z.boolean() |
| z.date() | date | birthdate: z.date() |
| z.enum([...]) | select | status: z.enum(["draft", "published"]) |
| z.instanceof(File) | file | avatar: z.instanceof(File) |
Recommendation: Use field helpers for new schemas to get explicit field type declarations and access to PocketBase-specific options.
Defining Relations
Use RelationField() for single relations and RelationsField() for multiple relations:
import { RelationField, RelationsField } from "pocketbase-zod-schema";
const ProjectSchema = z.object({
name: z.string(),
// Single relation (maxSelect: 1)
owner: RelationField({ collection: "users" }),
// Single relation with cascade delete
category: RelationField({
collection: "categories",
cascadeDelete: true,
}),
// Multiple relations (maxSelect: 999 by default)
collaborators: RelationsField({ collection: "users" }),
// Multiple relations with constraints
tags: RelationsField({
collection: "tags",
minSelect: 1,
maxSelect: 5,
}),
});Relation Options
RelationField(config) - Single relation
collection: string- Target collection name (required)cascadeDelete?: boolean- Delete related records when this record is deleted (default:false)displayFields?: string[] | null- Fields to display in the PocketBase admin UI
RelationsField(config) - Multiple relations
- All
RelationFieldoptions plus: minSelect?: number- Minimum number of relations required (default:0)maxSelect?: number- Maximum number of relations allowed (default:999)
Relations are explicit-only. There is no naming-convention fallback — a field is a relation only when it's built with
RelationField()/RelationsField(); a barez.array(z.string())maps to ajsonfield, not a relation.
Defining View Collections
Use defineView() for a read-only collection backed by a SQL query. PocketBase derives the
collection's fields by running the query, so the Zod schema describes the row shape for TypeScript
only:
import { z } from "zod";
import { baseSchema, defineView, sql } from "pocketbase-zod-schema";
export const ProductStatsSchema = z
.object({
vendor: z.string(),
productCount: z.number(),
})
.extend(baseSchema);
export default defineView({
collectionName: "ProductStats",
schema: ProductStatsSchema,
viewQuery: sql`
SELECT p.vendor AS id,
p.vendor AS vendor,
COUNT(*) AS productCount
FROM products p
GROUP BY p.vendor
`,
permissions: {
listRule: "vendor.owner = @request.auth.id",
viewRule: "vendor.owner = @request.auth.id",
},
});- Views are read-only:
defineView()accepts onlylistRuleandviewRule, and rejects indexes. - The outermost
SELECTmust expose a uniqueidcolumn. - The generated migration contains the query and no
fields/indexesarray. - Editing the SQL produces an in-place update that keeps the collection id stable; re-indenting it produces no migration at all.
- Deleting a view is not a destructive change — a view stores no data.
defineCollection({ type: "view", viewQuery }) is equivalent, but defineView() turns those
constraints into compile errors instead of apply-time failures.
Defining Permissions
Attach API rules to a collection through defineCollection({ permissions }):
import { defineCollection } from "pocketbase-zod-schema";
// Explicit rules
const PostCollection = defineCollection({
collectionName: "posts",
schema: z.object({ title: z.string() }),
permissions: {
listRule: '@request.auth.id != ""', // Authenticated users can list
viewRule: "", // Anyone can view (public)
createRule: '@request.auth.id != ""', // Authenticated users can create
updateRule: "author = @request.auth.id", // Only author can update
deleteRule: "author = @request.auth.id", // Only author can delete
},
});
// Using a template
const ProjectCollection = defineCollection({
collectionName: "projects",
schema: z.object({ title: z.string(), owner: RelationField({ collection: "users" }) }),
permissions: {
template: "owner-only",
ownerField: "owner",
},
});Permission Templates
| Template | Description |
|----------|-------------|
| "public" | All operations are public (no auth required) |
| "authenticated" | All operations require authentication |
| "owner-only" | Only the owner can perform operations (ownerField, default "User") |
| "admin-only" | Requires @request.auth.<roleField> = "admin" (roleField, default "role") |
| "read-public" | Public read, authenticated write |
| "custom" | No base rules — customRules supplies everything |
Two more have no template name; call them and pass the result as permissions:
import { PermissionTemplates } from "pocketbase-zod-schema";
PermissionTemplates.locked(); // every rule null (superusers only)
PermissionTemplates.readOnlyAuthenticated(); // authenticated read, writes lockedRule values: null = superusers only, "" = public, any other string = a PocketBase filter
expression. manageRule applies to auth collections only.
Template with Custom Overrides
const PostCollection = defineCollection({
collectionName: "posts",
schema: z.object({ title: z.string(), author: RelationField({ collection: "users" }) }),
permissions: {
template: "owner-only",
ownerField: "author",
customRules: {
listRule: '@request.auth.id != ""', // Override just the list rule
viewRule: "", // Make viewing public
},
},
});Defining Indexes
Declare database indexes through defineCollection({ indexes }):
import { defineCollection } from "pocketbase-zod-schema";
const UserCollection = defineCollection({
collectionName: "users",
schema: z.object({
email: z.string().email(),
username: z.string(),
}),
permissions: {
template: "authenticated",
},
indexes: [
'CREATE UNIQUE INDEX idx_users_email ON users (email)',
'CREATE INDEX idx_users_username ON users (username)',
],
});CLI Reference
Commands
# Generate migration from schema changes
npx pocketbase-migrate generate
# Show what would be generated without writing files
npx pocketbase-migrate status
# Compare the files on disk against PocketBase's _migrations table
npx pocketbase-migrate status --verify
# Check migrations against PocketBase's goja runtime (Node-only globals, async, ...)
npx pocketbase-migrate lint
# Generate TypeScript definitions from schemas
npx pocketbase-migrate generate-types
# Force generation even with destructive changes
npx pocketbase-migrate generate --force
# Round-trip up() and down() before writing; refuse a migration that doesn't roll back
npx pocketbase-migrate generate --verify
# Restrict the diff to matching collection or field names (regex supported)
npx pocketbase-migrate generate Posts Commentsgenerate Command
pocketbase-migrate generate [filters...] [options]
Options:
-o, --output <directory> Output directory for migration files
-f, --force Force generation even with destructive changes or duplicates
--dry-run Show what would be generated without writing files
--schema-dir <directory> Directory containing Zod schema files
--verify Execute up() and down() before writing
--no-verify Skip verification even when enabled in the config fileOne file is written per collection operation.
status Command
pocketbase-migrate status [options]
Options:
--schema-dir <directory> Directory containing Zod schema files
--json Output status as JSON
--verify Compare disk against PocketBase's _migrations table; exit non-zero on drift
--pb-data <path> PocketBase data directory or data.db file--verify needs Node >= 22.5 (node:sqlite).
lint Command
pocketbase-migrate lint [files...] [options]
Options:
-o, --output <directory> Directory containing migration files
--no-execute Static checks onlyCatches JavaScript that runs in Node but not in goja: require, process, fetch, setTimeout,
async/await, import/export, class fields. Exits non-zero on any error-severity finding.
Global Options
-c, --config <path> Configuration file path
-v, --version Print the version ← -v is version, not verbose
--verbose Enable verbose logging
--quiet Suppress non-essential output
--no-color Disable colored outputgenerate-types Command
Generate type-safe TypeScript definitions from your Zod schemas.
pocketbase-migrate generate-types [options]
Options:
-o, --output <path> Output file path (default: pocketbase-types.ts)
--schema-dir <directory> Directory containing Zod schema filesWhat it generates:
The generate-types command analyzes your Zod schemas and generates a TypeScript file (default: pocketbase-types.ts) containing:
Record interfaces - Type-safe interfaces for each collection's data structure
export interface PostsRecord { id: string; created: string; updated: string; collectionId: string; collectionName: "posts"; title: string; content: string; // ... other fields }Response types - Extended types with expand support for relations
export interface PostsResponse extends PostsRecord { expand?: { author?: UsersResponse; tags?: TagsResponse[]; }; }TypedPocketBase interface - Type-safe PocketBase client wrapper
export interface TypedPocketBase extends PocketBase { collection(idOrName: "posts"): RecordService<PostsResponse>; collection(idOrName: "users"): RecordService<UsersResponse>; // ... other collections }
Example usage:
import PocketBase from "pocketbase";
import { TypedPocketBase } from "./pocketbase-types";
const pb = new PocketBase("http://localhost:8090") as TypedPocketBase;
// Full type safety!
const post = await pb.collection("posts").getOne("post-id");
// Type: PostsResponse
// Expand relations with type safety
const postWithAuthor = await pb.collection("posts").getOne("post-id", {
expand: "author"
});
// postWithAuthor.expand?.author is typed as UsersResponse | undefinedConfiguration Options
// pocketbase-migrate.config.js
export default {
schema: {
// Directory containing your Zod schema files
directory: "./src/schema",
// Files to exclude from schema discovery (replaces the default list)
exclude: ["*.test.ts", "*.spec.ts", "base.ts", "index.ts"],
},
migrations: {
// Directory to output migration files, and to reconstruct state from
directory: "./pocketbase/pb_migrations",
// Round-trip up()/down() before writing a migration (default: false)
verify: false,
// PocketBase data directory, for reading the _migrations table.
// "" means the pb_data directory next to the migrations directory.
dataDirectory: "",
},
diff: {
// Warn when collections or fields would be deleted
warnOnDelete: true,
// Require --force flag for destructive changes
requireForceForDestructive: true,
},
typeGen: {
// Default output for `generate-types`
outPath: "pocketbase-types.ts",
},
};There is no snapshot file to configure. The current database state is reconstructed by executing
the migration files: the newest *_collections_snapshot.js plus everything after it.
Environment overrides: MIGRATION_SCHEMA_DIR, MIGRATION_SCHEMA_EXCLUDE, MIGRATION_OUTPUT_DIR,
MIGRATION_VERIFY, MIGRATION_DATA_DIR, MIGRATION_REQUIRE_FORCE. Precedence is
CLI > environment > config file > defaults.
Programmatic API
For custom workflows, use the programmatic API from pocketbase-zod-schema/server:
import {
parseSchemaFiles,
compare,
generate,
loadSnapshotWithMigrations,
} from "pocketbase-zod-schema/server";
async function generateMigration() {
const schemaDir = "./src/schema";
const migrationsDir = "./pocketbase/pb_migrations";
// Parse all schema files
const currentSchema = await parseSchemaFiles({ schemaDir });
// Reconstruct the current database state by executing the existing migrations.
// Returns null when there is nothing to replay; throws SnapshotError if a
// migration cannot be executed.
const previousSnapshot = loadSnapshotWithMigrations({
migrationsPath: migrationsDir,
});
// Compare schemas and detect changes
const diff = compare(currentSchema, previousSnapshot);
// Write one migration file per collection operation; returns the paths written
const paths = generate(diff, migrationsDir);
console.log(paths.length ? `Migrations created:\n${paths.join("\n")}` : "No changes detected");
}The execution engine, round-trip verification and the goja lint are also available directly from
pocketbase-zod-schema/server — see the
Execution Engine guide.
Complete Example
Here's a complete example of a blog schema with users, posts, and comments:
// src/schema/user.ts
import { z } from "zod";
import { baseSchema, defineCollection, TextField, EmailField } from "pocketbase-zod-schema";
// Input schema for forms (includes passwordConfirm for validation)
export const UserInputSchema = z.object({
name: TextField({ max: 100 }).optional(),
email: EmailField(),
password: TextField({ min: 8 }),
passwordConfirm: z.string(),
avatar: z.instanceof(File).optional(),
});
// Database schema (excludes passwordConfirm)
const UserCollectionSchema = z.object({
name: TextField({ max: 100 }).optional(),
email: EmailField(),
password: TextField({ min: 8 }),
avatar: z.instanceof(File).optional(),
});
// Full schema with base fields for type inference (includes id, collectionId, etc.)
export const UserSchema = UserCollectionSchema.extend(baseSchema);
// Collection definition with permissions and indexes
export const UserCollection = defineCollection({
collectionName: "Users",
type: "auth", // auth collections must opt in explicitly; it is never inferred
schema: UserSchema,
permissions: {
listRule: "id = @request.auth.id",
viewRule: "id = @request.auth.id",
createRule: "",
updateRule: "id = @request.auth.id",
deleteRule: "id = @request.auth.id",
},
indexes: [
'CREATE UNIQUE INDEX idx_users_email ON users (email)',
],
});// src/schema/post.ts
import { z } from "zod";
import {
defineCollection,
TextField,
EditorField,
BoolField,
DateField,
RelationField,
RelationsField,
} from "pocketbase-zod-schema";
// Define the Zod schema
export const PostSchema = z.object({
title: TextField({ min: 1, max: 200 }),
slug: TextField({ pattern: /^[a-z0-9-]+$/ }),
content: EditorField(),
excerpt: TextField({ max: 500 }).optional(),
published: BoolField(),
publishedAt: DateField().optional(),
// Relations
author: RelationField({ collection: "users" }),
category: RelationField({ collection: "categories" }),
tags: RelationsField({ collection: "tags", maxSelect: 10 }),
});
// Define the collection with permissions
export const PostCollection = defineCollection({
collectionName: "posts",
schema: PostSchema,
permissions: {
listRule: 'published = true || author = @request.auth.id',
viewRule: 'published = true || author = @request.auth.id',
createRule: '@request.auth.id != ""',
updateRule: "author = @request.auth.id",
deleteRule: "author = @request.auth.id",
},
});// src/schema/comment.ts
import { z } from "zod";
import { defineCollection, TextField, RelationField } from "pocketbase-zod-schema";
// Define the Zod schema
export const CommentSchema = z.object({
content: TextField({ min: 1 }),
// Relations with cascade delete
post: RelationField({ collection: "posts", cascadeDelete: true }),
author: RelationField({ collection: "users" }),
});
// Define the collection with permissions
export const CommentCollection = defineCollection({
collectionName: "comments",
schema: CommentSchema,
permissions: {
listRule: "",
viewRule: "",
createRule: '@request.auth.id != ""',
updateRule: "author = @request.auth.id",
deleteRule: "author = @request.auth.id || @request.auth.role = 'admin'",
},
});Documentation
Full guides live in the repository:
- API Reference
- Execution Engine
- Configuration
- Migration Guide
- Type Mapping
- View Collections
- Naming Conventions
Requirements
Node.js 20 or higher. status --verify, which reads PocketBase's _migrations table, additionally
requires Node 22.5+ for node:sqlite.
License
MIT
