PocketBase SDK

SkillSearch

PocketBase SDK is a skill that helps an agent write JavaScript or TypeScript code for PocketBase client applications. It covers calling PocketBase from a frontend or Node.js, authenticating users, subscribing to realtime events, uploading files, and working with the PocketBase JS/TS SDK. The skill includes CRUD operations, auth flows, authStore, realtime SSE, file handling, and batch operations.

Use PocketBase SDK in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add PocketBase SDK and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the PocketBase SDK skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Have a PocketBase instance available to connect to.

PocketBase SDKStart free

What your AI can do with it

  • Write JavaScript or TypeScript code that calls PocketBase from a frontend or Node.js
  • Authenticate users and manage auth flows with authStore
  • Subscribe to realtime events using PocketBase realtime SSE
  • Upload and handle files through the PocketBase JS/TS SDK
  • Perform CRUD and batch operations against PocketBase

Getting started

  1. Have a PocketBase instance available to connect to.
  2. Add the PocketBase JavaScript SDK to your project.
  3. Configure the SDK with the URL of your PocketBase instance.
  4. Use the skill to write code for authentication, CRUD, realtime subscriptions, or file uploads.

What this skill tells your AI

The instructions your AI receives, as published by davila7/claude-code-templates in cli-tool/components/skills/pocketbase/pb-sdk/SKILL.md and read by ahel’s review.

Installation & Setup

npm install pocketbase
# or
yarn add pocketbase
# or
<script src="https://cdn.jsdelivr.net/npm/pocketbase@0.36.6/dist/pocketbase.umd.js"></script>
import PocketBase from 'pocketbase'

const pb = new PocketBase('http://127.0.0.1:8090')

CRUD Operations

List records

const records = await pb.collection('posts').getList(1, 20, {
    filter: 'status = "active" && created > "2024-01-01"',
    sort: '-created,title',
    expand: 'author,tags',
    fields: 'id,title,author,created',  // partial response
    skipTotal: true,  // skip COUNT query for better performance
})

// records.page, records.perPage, records.totalItems, records.totalPages, records.items

Get full list (auto-paginate)

const allRecords = await pb.collection('posts').getFullList({
    filter: 'status = "active"',
    sort: '-created',
    batch: 200,  // records per request (default: 200)
})

View single record

const record = await pb.collection('posts').getOne('RECORD_ID', {
    expand: 'author',
})

Get first matching record

const record = await pb.collection('posts').getFirstListItem('slug = "my-post"', {
    expand: 'author',
})

Create record

const record = await pb.collection('posts').create({
    title: 'My Post',
    body: 'Content here',
    author: 'USER_ID',
    status: 'draft',
})

Update record

const record = await pb.collection('posts').update('RECORD_ID', {
    title: 'Updated Title',
    status: 'published',
})

Delete record

await pb.collection('posts').delete('RECORD_ID')

Query Parameters

Filter syntax

Same as API rules filter syntax. Common patterns:

// Equality
filter: 'status = "active"'

// Contains (LIKE)
filter: 'title ~ "hello"'

// Multi-relation contains
filter: 'tags ?= "TAG_ID"'

// Date comparison
filter: 'created > "2024-01-01 00:00:00"'

// Relative dates
filter: 'created > @now - 7d'

// Logical operators
filter: 'status = "active" && author = "USER_ID"'
filter: '(type = "a" || type = "b") && active = true'

// Null check
filter: 'parent = null'
filter: 'parent != null'

Sort syntax

sort: '-created'          // descending by created
sort: 'title'             // ascending by title
sort: '-created,title'    // multi-field sort
sort: '@random'           // random order

Expand relations

expand: 'author'                    // single relation
expand: 'author,tags'               // multiple relations
expand: 'author.team'               // nested expand (author's team)
expand: 'comments_via_post'         // back-relation (comments that reference this post)
expand: 'comments_via_post.author'  // nested back-relation expand

Fields (partial response)

fields: 'id,title,created'
fields: 'id,expand.author.name'     // include expanded field
fields: '*,expand.author.name'      // all fields + specific expand

Authentication

Email/password

const authData = await pb.collection('users').authWithPassword('user@example.com', 'password123')
// authData.token, authData.record

OAuth2 (all-in-one)

// Opens popup/redirect for OAuth2 provider
const authData = await pb.collection('users').authWithOAuth2({ provider: 'google' })
// or with redirect
const authData = await pb.collection('users').authWithOAuth2({
    provider: 'google',
    urlCallback: (url) => { window.location.href = url }
})

OTP (one-time password)

// Step 1: Request OTP
const result = await pb.collection('users').requestOTP('user@example.com')
// result.otpId

// Step 2: Verify OTP
const authData = await pb.collection('users').authWithOTP(result.otpId, '123456')

MFA (multi-factor authentication)

MFA is triggered automatically when enabled. After primary auth returns a mfaId:

try {
    await pb.collection('users').authWithPassword('user@example.com', 'password')
} catch (err) {
    if (err.response?.mfaId) {
        // Need second factor — e.g., OTP
        const otpResult = await pb.collection('users').requestOTP('user@example.com')
        await pb.collection('users').authWithOTP(otpResult.otpId, '123456', {
            mfaId: err.response.mfaId
        })
    }
}

Auth store

pb.authStore.token       // current JWT token
pb.authStore.record      // current auth record
pb.authStore.isValid     // token not expired
pb.authStore.isAdmin     // deprecated — check record.collectionName === '_superusers'
pb.authStore.isSuperuser // check if superuser

// Listen for auth changes
pb.authStore.onChange((token, record) => {
    console.log('Auth changed:', record?.id)
})

// Clear auth
pb.authStore.clear()

// Refresh auth (get fresh token + record)
await pb.collection('users').authRefresh()

Password reset

// Request reset email
await pb.collection('users').requestPasswordReset('user@example.com')

// Confirm reset (usually from email link)
await pb.collection('users').confirmPasswordReset(token, newPassword, newPasswordConfirm)

Email verification

await pb.collection('users').requestVerification('user@example.com')
await pb.collection('users').confirmVerification(token)

Email change

await pb.collection('users').requestEmailChange('new@example.com')
await pb.collection('users').confirmEmailChange(token, password)

Realtime (SSE)

Subscribe to record changes

// Subscribe to all changes in a collection
pb.collection('posts').subscribe('*', function(e) {
    // e.action: 'create' | 'update' | 'delete'
    // e.record: the affected record
    console.log(e.action, e.record.id)
}, {
    expand: 'author',  // expand relations in realtime events
    filter: 'status = "active"',  // only receive matching records
})

// Subscribe to a specific record
pb.collection('posts').subscribe('RECORD_ID', function(e) {
    console.log('Record changed:', e.record)
})

// Unsubscribe
pb.collection('posts').unsubscribe('*')        // from specific topic
pb.collection('posts').unsubscribe('RECORD_ID')
pb.collection('posts').unsubscribe()            // from all collection topics
pb.realtime.unsubscribe()                       // from everything

Connection management

// The SDK auto-reconnects on disconnect
// You can listen for connect/disconnect:
pb.realtime.onConnect = function() {
    console.log('Connected')
}
pb.realtime.onDisconnect = function() {
    console.log('Disconnected')
}

File Upload & Download

Upload files

// Via FormData (browser)
const formData = new FormData()
formData.append('title', 'My Post')
formData.append('document', fileInput.files[0])
formData.append('images', fileInput1.files[0])  // multi-file
formData.append('images', fileInput2.files[0])

const record = await pb.collection('posts').create(formData)

// Via object (Node.js or when you have the file as a Blob/File)
const record = await pb.collection('posts').create({
    title: 'My Post',
    document: new File([blob], 'file.pdf'),
})

Delete a file

// Set field to empty to delete
await pb.collection('posts').update('RECORD_ID', {
    document: null,  // deletes the file
})

// For multi-file: remove specific file
await pb.collection('posts').update('RECORD_ID', {
    'images-': ['filename_to_remove.jpg'],  // minus suffix removes
})

Get file URL

const url = pb.files.getURL(record, record.document)
// https://example.com/api/files/COLLECTION_ID/RECORD_ID/filename.pdf

// With thumbnail (for image fields)
const thumb = pb.files.getURL(record, record.cover, { thumb: '100x100' })
// Supported: WxH, WxHt (top), WxHb (bottom), WxHf (fit), 0xH, Wx0

Protected files

For files in collections with view rules, include the auth token:

const url = pb.files.getURL(record, record.document, { token: pb.authStore.token })

Batch Operations

Send multiple create/update/delete in one request (transactional):

const batch = pb.createBatch()

batch.collection('posts').create({ title: 'Post 1' })
batch.collection('posts').create({ title: 'Post 2' })
batch.collection('posts').update('RECORD_ID', { title: 'Updated' })
batch.collection('comments').delete('COMMENT_ID')

const results = await batch.send()
// results[0], results[1], ... correspond to each operation

Error Handling

try {
    const record = await pb.collection('posts').create(data)
} catch (err) {
    // err.status — HTTP status code
    // err.response — full error response
    // err.response.message — error message
    // err.response.data — field-level validation errors
    //   e.g., { title: { code: "validation_required", message: "Missing required value." } }
    // err.isAbort — true if request was cancelled

    if (err.status === 400) {
        // Validation error
        for (const [field, error] of Object.entries(err.response.data)) {
            console.log(`${field}: ${error.message}`)
        }
    }
}

Advanced

Auto-cancellation

By default, duplicate pending requests to the same endpoint are auto-cancelled. Disable per-request:

await pb.collection('posts').getList(1, 20, {
    requestKey: null,  // disable auto-cancel for this request
})

// Or use a custom key to group cancellations
await pb.collection('posts').getList(1, 20, {
    requestKey: 'my-custom-key',
})

Custom headers

// Per-request
await pb.collection('posts').getList(1, 20, {
    headers: { 'X-Custom': 'value' }
})

// Global (all requests)
pb.beforeSend = function(url, options) {
    options.headers['X-Custom'] = 'value'
    return { url, options }
}

// Intercept response
pb.afterSend = function(response, data) {
    // modify data if needed
    return data
}

SSR / Server-side

// Load auth from cookie (e.g., in Next.js/SvelteKit)
pb.authStore.loadFromCookie(request.headers.get('cookie') || '')

// Export auth to cookie
const cookie = pb.authStore.exportToCookie({ httpOnly: false })
response.headers.set('set-cookie', cookie)

Sending as superuser

const pb = new PocketBase('http://127.0.0.1:8090')
await pb.collection('_superusers').authWithPassword('admin@example.com', 'password')
// Now all requests are authenticated as superuser

Signals

GitHub stars
32k
Forks
4k
Last commit
Oct 2026

ahel review

  • K1binfo
    installs-packages

Automated review, not a security audit. Ruleset v1+k2.

Questions

What PocketBase features does this skill cover?
It covers CRUD, auth flows, authStore, realtime SSE, file handling, and batch operations when using the PocketBase JS/TS SDK from a frontend or Node.js.
Can it help with realtime updates?
Yes, it covers subscribing to realtime events through PocketBase realtime SSE.
Does it support file uploads?
Yes, file handling and uploading files are part of what the skill covers.
Can I use it with TypeScript?
Yes, the skill covers working with the PocketBase JS/TS SDK, so TypeScript usage is included.
Does it work outside the browser?
Yes, it covers calling PocketBase from Node.js as well as from a frontend.
Advanced
Item type
skill
Key
pocketbase-sdk
Source
github.com/davila7/claude-code-templates