PocketBase Plugin Introduction Goal
Enable seamless CRUD operations and authentication with PocketBase collections through an elegant, minimal Python interface that requires zero configuration per collection. Context
This plugin provides a unified interface to interact with any PocketBase collection without pre-defining schemas or writing collection-specific code. It handles authentication automatically, supports batch operations, and adapts to any collection structure dynamically. Overview
The plugin exposes a single PocketBase class that can interact with any collection through method calls or dynamic attribute access. All operations are async and require proper environment variables to be set. Steps Step 1: Environment Setup
Why: The plugin reads PocketBase credentials from environment variables for security and portability.
Code Snippet:
Python
POCKETBASE_URL=https://your-instance.pocketbase.io POCKETBASE_EMAIL=admin@example.com POCKETBASE_PASSWORD=your-admin-password
Dos and Don'ts:
✅ DO use POCKETBASE_URL without trailing slash
✅ DO use admin credentials for POCKETBASE_EMAIL/PASSWORD
❌ DON'T commit .env to version control
❌ DON'T use user credentials for admin operations
Step 2: Basic Import and Initialization
Why: Two import patterns provide flexibility - singleton for quick use, class for explicit collection binding.
Code Snippet:
Python
from pocketbase_plugin import pb
from pocketbase_plugin import PocketBase users = PocketBase("users")
Dos and Don'ts:
✅ DO use singleton pb for multiple collections
✅ DO create instances for single collection operations
❌ DON'T mix patterns in the same function
❌ DON'T forget collections are accessed as attributes on singleton
Step 3: Listing/Searching Records
Why: Retrieve multiple records with optional filtering, sorting, and pagination.
Code Snippet:
Python
records = await pb.users.list()
records = await pb.users.list(filter="age>21 && verified=true") records = await pb.users.list(age=21, verified=True) # kwargs become filters
records = await pb.posts.list( sort="-created,title", # DESC by created, then ASC by title page=2, perPage=50 )
Dos and Don'ts:
✅ DO use filter strings for complex conditions
✅ DO use kwargs for simple equality filters
✅ DO prefix sort fields with - for DESC
❌ DON'T mix filter string with filter kwargs
❌ DON'T forget pagination returns metadata in response
Step 4: Retrieving Single Records
Why: Fetch a specific record by ID with optional relation expansion.
Code Snippet:
Python
user = await pb.users.get("record_id_xyz")
post = await pb.posts.get( "post_id_123", expand="author,comments.user" )
user = await pb.users.get( "user_id", fields="id,name,email" )
Dos and Don'ts:
✅ DO use expand for related data
✅ DO use fields to minimize payload
❌ DON'T assume record exists (handle exceptions)
❌ DON'T expand more than 6 levels deep
Step 5: Creating Records
Why: Insert new records into a collection with automatic schema adaptation.
Code Snippet:
Python
user = await pb.users.create( name="John Doe", email="john@example.com", age=30 )
post = await pb.posts.create( title="Hello World", content="Post content", author="user_id", _params={"fields": "id,title,created"} # _params for query parameters )
print(user["id"]) # "newly_created_id"
Dos and Don'ts:
✅ DO pass fields as kwargs matching collection schema
✅ DO use _params for query parameters
✅ DO handle the returned record data
❌ DON'T include id unless you want specific ID
❌ DON'T assume field types (let PocketBase validate)
Step 6: Updating Records
Why: Modify existing records with partial updates (only specified fields change).
Code Snippet:
Python
updated = await pb.users.update( "user_id_xyz", name="Jane Doe", age=31 )
updated = await pb.posts.update( "post_id", title="Updated Title", _params={"expand": "author"} )
Dos and Don'ts:
✅ DO pass only fields you want to update
✅ DO use the returned updated record
❌ DON'T pass unchanged fields
❌ DON'T forget the record ID as first argument
Step 7: Deleting Records
Why: Remove records from a collection permanently.
Code Snippet:
Python
await pb.users.delete("user_id_xyz")
try: user = await pb.users.get("user_id") if user["status"] == "inactive": await pb.users.delete(user["id"]) except Exception: pass # Record doesn't exist
Dos and Don'ts:
✅ DO verify record exists if needed
✅ DO handle deletion in try/except
❌ DON'T delete without considering relations
❌ DON'T expect return value (returns empty dict)
Step 8: Batch Operations
Why: Execute multiple operations in a single request for performance.
Code Snippet:
Python
new_users = await pb.users.create_many([ {"name": "User 1", "email": "user1@example.com"}, {"name": "User 2", "email": "user2@example.com"}, {"name": "User 3", "email": "user3@example.com"} ])
updates = { "user_id_1": {"status": "active"}, "user_id_2": {"status": "inactive"}, "user_id_3": {"name": "Updated Name"} } results = await pb.users.update_many(updates)
await pb.users.delete_many(["id1", "id2", "id3"])
results = await pb.batch([ {"method": "POST", "url": "/api/collections/users/records", "body": {...}}, {"method": "PATCH", "url": "/api/collections/posts/records/id", "body": {...}}, {"method": "DELETE", "url": "/api/collections/comments/records/id"} ])
Dos and Don'ts:
✅ DO use batch for multiple operations
✅ DO check individual operation results
✅ DO use create_many for bulk inserts
❌ DON'T exceed reasonable batch sizes (100-500 ops)
❌ DON'T mix unrelated operations in batch
Step 9: User Authentication
Why: Authenticate as a collection user (not admin) for user-specific operations.
Code Snippet:
Python
auth_data = await pb.users.auth( "user@example.com", # identity (email/username) "user_password" )
token = auth_data["token"] user_record = auth_data["record"] user_id = user_record["id"]
Dos and Don'ts:
✅ DO use for user-specific operations
✅ DO store token for subsequent user requests
❌ DON'T confuse with admin authentication
❌ DON'T use user auth for admin operations
Step 10: Working with Different Collections
Why: The same interface works for any collection regardless of schema.
Code Snippet:
Python
await pb.products.create(name="Mouse", price=29.99, stock=100) await pb.orders.create(user="user_id", items=["item1", "item2"], total=59.98) await pb.categories.create(name="Electronics", slug="electronics")
collection_name = "dynamic_collection" records = await getattr(pb, collection_name).list()
dynamic = PocketBase(collection_name) await dynamic.create(field1="value1", field2="value2")
Dos and Don'ts:
✅ DO use the same methods for all collections
✅ DO let PocketBase handle schema validation
✅ DO use getattr for dynamic collection names
❌ DON'T hardcode collection schemas in your code
❌ DON'T assume fields exist across collections
Step 11: Error Handling
Why: Properly handle network, authentication, and validation errors.
Code Snippet:
Python
import httpx
try: user = await pb.users.create(email="invalid-email") except httpx.HTTPStatusError as e: if e.response.status_code == 400: print("Validation error:", e.response.json()) elif e.response.status_code == 401: print("Authentication failed") elif e.response.status_code == 404: print("Collection or record not found") else: print(f"HTTP error {e.response.status_code}") except httpx.RequestError as e: print("Network error:", str(e))
Dos and Don'ts:
✅ DO wrap operations in try/except
✅ DO check status codes for specific errors
✅ DO log errors appropriately
❌ DON'T suppress all exceptions silently
❌ DON'T assume operations always succeed
Step 12: Advanced Filtering
Why: Build complex queries for precise data retrieval.
Code Snippet:
Python
results = await pb.products.list( filter="(price >= 10 && price <= 100) && (category = 'electronics' || featured = true)", sort="-featured,-created", expand="category,reviews" )
posts = await pb.posts.list( filter="created >= '2024-01-01 00:00:00' && status = 'published'" )
users = await pb.users.list( filter="email ~ 'gmail.com' && name !~ 'test'" # contains gmail, not test )
records = await pb.items.list( filter="deletedAt = null && parentId != null" )
Dos and Don'ts:
✅ DO use parentheses for complex logic
✅ DO use ~ for pattern matching
✅ DO use ISO format for dates
❌ DON'T forget to escape quotes in strings
❌ DON'T use invalid operators for field types