Install any skill in seconds. Free to start, no credit card required.
Get Started Free →JavaScript SDK usage for PocketBase client applications. Use when calling PocketBase from frontend or Node.js, authenticating users, subscribing to realtime events, uploading files, or working with the PocketBase JS/TS SDK. Covers CRUD, auth flows, authStore, realtime SSE, file handling, batch operations, and query syntax.
.claude/skills/davila7-pocketbase-sdk/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 133% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 84% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 117% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 96% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 137% | 0% |
bashnpm install pocketbase # or yarn add pocketbase # or <script src="https://cdn.jsdelivr.net/npm/pocketbase@0.36.6/dist/pocketbase.umd.js"></script>
jsimport PocketBase from 'pocketbase' const pb = new PocketBase('http://127.0.0.1:8090')
jsconst 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
jsconst allRecords = await pb.collection('posts').getFullList({ filter: 'status = "active"', sort: '-created', batch: 200, // records per request (default: 200) })
jsconst record = await pb.collection('posts').getOne('RECORD_ID', { expand: 'author', })
jsconst record = await pb.collection('posts').getFirstListItem('slug = "my-post"', { expand: 'author', })
jsconst record = await pb.collection('posts').create({ title: 'My Post', body: 'Content here', author: 'USER_ID', status: 'draft', })
jsconst record = await pb.collection('posts').update('RECORD_ID', { title: 'Updated Title', status: 'published', })
jsawait pb.collection('posts').delete('RECORD_ID')
Same as API rules filter syntax. Common patterns:
js// 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'
jssort: '-created' // descending by created sort: 'title' // ascending by title sort: '-created,title' // multi-field sort sort: '@random' // random order
jsexpand: '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
jsfields: 'id,title,created' fields: 'id,expand.author.name' // include expanded field fields: '*,expand.author.name' // all fields + specific expand
jsconst authData = await pb.collection('users').authWithPassword('user@example.com', 'password123') // authData.token, authData.record
js// 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 } })
js// 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 is triggered automatically when enabled. After primary auth returns a mfaId:
jstry { 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 }) } }
jspb.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()
js// 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)
jsawait pb.collection('users').requestVerification('user@example.com') await pb.collection('users').confirmVerification(token)
jsawait pb.collection('users').requestEmailChange('new@example.com') await pb.collection('users').confirmEmailChange(token, password)
js// 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
js// 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') }
js// 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'), })
js// 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 })
jsconst 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
For files in collections with view rules, include the auth token:
jsconst url = pb.files.getURL(record, record.document, { token: pb.authStore.token })
Send multiple create/update/delete in one request (transactional):
jsconst 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
jstry { 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}`) } } }
By default, duplicate pending requests to the same endpoint are auto-cancelled. Disable per-request:
jsawait 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', })
js// 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 }
js// 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)
jsconst 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
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 11,025 | 5,346 | -52% | 1 | 1 | 0% | 2,153 | 4,217 | +96% | 0 | 0 | — |
case-02 | pass→pass | 12,611 | 12,727 | +1% | 1 | 1 | 0% | 2,271 | 5,385 | +137% | 0 | 0 | — |
case-03 | pass→pass | 7,118 | 5,771 | -19% | 1 | 1 | 0% | 1,277 | 3,929 | +208% | 0 | 0 | — |
case-04 | pass→pass | 6,868 | 4,355 | -37% | 1 | 1 | 0% | 1,417 | 3,915 | +176% | 0 | 0 | — |
case-05 | pass→pass | 9,401 | 3,851 | -59% | 1 | 1 | 0% | 1,649 | 3,677 | +123% | 0 | 0 | — |
case-06 | pass→pass | 13,491 | 4,856 | -64% | 1 | 1 | 0% | 2,289 | 3,837 | +68% | 0 | 0 | — |
case-07 | fail→pass | 9,743 | 6,221 | -36% | 1 | 1 | 0% | 1,769 | 4,120 | +133% | 0 | 0 | — |
case-08 | pass→pass | 6,058 | 2,311 | -62% | 1 | 1 | 0% | 1,061 | 3,332 | +214% | 0 | 0 | — |
case-09 | pass→pass | 6,118 | 2,839 | -54% | 1 | 1 | 0% | 1,076 | 3,498 | +225% | 0 | 0 | — |
case-10 | pass→pass | 6,843 | 4,272 | -38% | 1 | 1 | 0% | 1,316 | 3,803 | +189% | 0 | 0 | — |
case-11 | pass→pass | 5,667 | 3,852 | -32% | 1 | 1 | 0% | 1,137 | 3,730 | +228% | 0 | 0 | — |
case-12 | fail→pass | 12,230 | 6,816 | -44% | 1 | 1 | 0% | 2,352 | 4,334 | +84% | 0 | 0 | — |
case-13 | pass→pass | 5,731 | 2,506 | -56% | 1 | 1 | 0% | 1,180 | 3,478 | +195% | 0 | 0 | — |
case-14 | pass→pass | 14,802 | 13,072 | -12% | 1 | 1 | 0% | 3,277 | 5,844 | +78% | 0 | 0 | — |
case-15 | pass→pass | 5,773 | 3,362 | -42% | 1 | 1 | 0% | 1,216 | 3,646 | +200% | 0 | 0 | — |
case-16 | pass→pass | 5,717 | 2,930 | -49% | 1 | 1 | 0% | 1,010 | 3,576 | +254% | 0 | 0 | — |
case-17 | pass→pass | 10,065 | 7,097 | -29% | 1 | 1 | 0% | 2,138 | 4,507 | +111% | 0 | 0 | — |
case-18 | fail→pass | 8,029 | 2,227 | -72% | 1 | 1 | 0% | 1,587 | 3,448 | +117% | 0 | 0 | — |
case-19 | pass→pass | 8,374 | 4,776 | -43% | 1 | 1 | 0% | 1,704 | 4,057 | +138% | 0 | 0 | — |
case-20 | pass→pass | 6,979 | 3,629 | -48% | 1 | 1 | 0% | 1,669 | 3,770 | +126% | 0 | 0 | — |
case-21 | pass→pass | 7,222 | 4,412 | -39% | 1 | 1 | 0% | 1,405 | 3,918 | +179% | 0 | 0 | — |
case-22 | pass→pass | 4,336 | 2,473 | -43% | 1 | 1 | 0% | 854 | 3,425 | +301% | 0 | 0 | — |
case-23 | pass→pass | 6,570 | 4,941 | -25% | 1 | 1 | 0% | 1,429 | 3,978 | +178% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.