PocketBase is a portable open-source backend that ships as a single executable file. It bundles an embedded SQLite database, realtime subscriptions, built-in authentication (email/password and OAuth2), file storage, and a full admin dashboard UI. Install by downloading one binary, run it, and you have a complete backend with REST API, SDK support, and an admin panel at /_/. Extend PocketBase with JavaScript hooks or as a Go framework. Deploy anywhere: Docker, fly.io, Railway, or a plain VPS. Ideal for MVPs, side projects, and small-to-medium applications.
- PocketBase is a single-file executable backend with zero external dependencies
- Built-in SQLite database, REST API, realtime subscriptions, and admin dashboard
- Email/password and OAuth2 authentication work out of the box
- Extend via JavaScript hooks or use as a Go framework
- Deploy to Docker, fly.io, Railway, or any VPS
- Ideal for MVPs, side projects, and small-to-medium apps
What Is PocketBase?
PocketBase is an open-source backend application packaged in a single executable file. Written in Go, it bundles an embedded SQLite database, realtime subscriptions, user authentication, file storage, and a complete admin dashboard. Download one binary, run it, and you have a fully functional backend service ready to use.
The core philosophy of PocketBase is simplicity. No complex dependency management, no database server configuration, no microservices architecture. One file is your entire backend.
Installation & Setup
Installing PocketBase is straightforward. Download the binary for your operating system from the GitHub Releases page, extract it, and run it.
# Download PocketBase (Linux/macOS)
wget https://github.com/pocketbase/pocketbase/releases/download/v0.25.0/pocketbase_0.25.0_linux_amd64.zip
unzip pocketbase_0.25.0_linux_amd64.zip
# Run PocketBase
./pocketbase serve
# Output:
# Server started at http://127.0.0.1:8090
# - REST API: http://127.0.0.1:8090/api/
# - Admin UI: http://127.0.0.1:8090/_/
# Specify a custom host and port
./pocketbase serve --http="0.0.0.0:8080"
# Enable HTTPS with auto TLS (Let's Encrypt)
./pocketbase serve --https="yourdomain.com"The first time you visit the admin panel, you will be prompted to create an admin account. PocketBase stores all data in the pb_data directory.
Collections & Schema
PocketBase uses Collections to organize data, similar to database tables. There are three collection types: Base (regular data), Auth (user authentication data), and View (read-only views). You can create and manage collections through the admin dashboard or the API.
Each collection automatically includes id, created, and updated fields. Supported field types include: text, number, bool, email, url, date, select, relation, file, json, and editor.
// Collection schema example (as defined in admin UI or via API)
// Collection: "posts"
{
"name": "posts",
"type": "base",
"schema": [
{ "name": "title", "type": "text", "required": true },
{ "name": "content", "type": "editor", "required": true },
{ "name": "slug", "type": "text", "unique": true },
{ "name": "tags", "type": "select", "options": {
"values": ["tech", "tutorial", "news"],
"maxSelect": 5
}},
{ "name": "author", "type": "relation", "options": {
"collectionId": "users",
"maxSelect": 1
}},
{ "name": "cover", "type": "file", "options": {
"maxSelect": 1,
"maxSize": 5242880,
"mimeTypes": ["image/jpeg", "image/png", "image/webp"]
}},
{ "name": "published","type": "bool", "default": false },
{ "name": "views", "type": "number", "default": 0 }
]
}CRUD Operations via REST API
PocketBase automatically generates a complete REST API for every collection. You can use standard HTTP methods to create, read, update, and delete records.
# List records with filtering, sorting, and pagination
curl "http://127.0.0.1:8090/api/collections/posts/records?\
filter=(published=true)&\
sort=-created&\
page=1&\
perPage=20&\
expand=author"
# Get a single record by ID
curl "http://127.0.0.1:8090/api/collections/posts/records/RECORD_ID"
# Create a record
curl -X POST "http://127.0.0.1:8090/api/collections/posts/records" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer USER_TOKEN" \
-d '{"title":"My Post","content":"Hello world","published":true}'
# Update a record
curl -X PATCH "http://127.0.0.1:8090/api/collections/posts/records/RECORD_ID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer USER_TOKEN" \
-d '{"title":"Updated Title"}'
# Delete a record
curl -X DELETE "http://127.0.0.1:8090/api/collections/posts/records/RECORD_ID" \
-H "Authorization: Bearer USER_TOKEN"The PocketBase filter syntax supports various operators: =, !=, >, >=, <, <=, ~ (like), !~ (not like). Combine conditions with && and ||.
JavaScript SDK
PocketBase provides an official JavaScript SDK that works in both browser and Node.js environments. The SDK wraps the REST API with a type-safe interface.
// Install the SDK
// npm install pocketbase
import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
// --- List records with filtering ---
const posts = await pb.collection('posts').getList(1, 20, {
filter: 'published = true',
sort: '-created',
expand: 'author',
});
console.log(posts.items);
console.log(posts.totalItems);
// --- Get a single record ---
const post = await pb.collection('posts').getOne('RECORD_ID', {
expand: 'author',
});
// --- Get first matching record ---
const featured = await pb.collection('posts').getFirstListItem(
'slug = "hello-world"'
);
// --- Create a record ---
const newPost = await pb.collection('posts').create({
title: 'My First Post',
content: '<p>Hello PocketBase!</p>',
published: true,
author: pb.authStore.record?.id,
});
// --- Update a record ---
await pb.collection('posts').update('RECORD_ID', {
title: 'Updated Title',
views: post.views + 1,
});
// --- Delete a record ---
await pb.collection('posts').delete('RECORD_ID');Realtime Subscriptions
PocketBase has built-in realtime functionality based on Server-Sent Events (SSE). You can subscribe to collection changes and receive create, update, and delete events in real time.
import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
// Subscribe to all changes in the "posts" collection
pb.collection('posts').subscribe('*', (e) => {
console.log(e.action); // "create", "update", or "delete"
console.log(e.record); // the changed record
});
// Subscribe to changes on a specific record
pb.collection('posts').subscribe('RECORD_ID', (e) => {
console.log('Record changed:', e.record);
});
// Unsubscribe from a specific collection
pb.collection('posts').unsubscribe('*');
// Unsubscribe from all subscriptions
pb.collection('posts').unsubscribe();
// React example: realtime chat messages
// useEffect(() => {
// pb.collection('messages').subscribe('*', (e) => {
// if (e.action === 'create') {
// setMessages(prev => [...prev, e.record]);
// }
// });
// return () => pb.collection('messages').unsubscribe();
// }, []);Authentication
PocketBase has a built-in authentication system supporting email/password login and OAuth2 social login. Auth collections automatically handle password hashing, token generation, and session management.
Email/Password Authentication
import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
// --- Register a new user ---
const user = await pb.collection('users').create({
email: 'user@example.com',
password: 'securepassword123',
passwordConfirm: 'securepassword123',
name: 'John Doe',
});
// --- Login with email/password ---
const authData = await pb.collection('users').authWithPassword(
'user@example.com',
'securepassword123'
);
console.log(authData.token);
console.log(authData.record.id);
// --- Check auth state ---
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record);
// --- Refresh auth token ---
await pb.collection('users').authRefresh();
// --- Logout ---
pb.authStore.clear();
// --- Request password reset ---
await pb.collection('users').requestPasswordReset('user@example.com');
// --- Request email verification ---
await pb.collection('users').requestVerification('user@example.com');OAuth2 Social Login
// OAuth2 login (Google, GitHub, Discord, etc.)
// First, configure OAuth2 providers in Admin UI > Auth collection > Options
// Browser-based OAuth2 login
const authData = await pb.collection('users').authWithOAuth2({
provider: 'google',
});
console.log(authData.token);
console.log(authData.record);
console.log(authData.meta?.avatarURL);
// Supported OAuth2 providers:
// Google, GitHub, GitLab, Discord, Microsoft, Apple,
// Facebook, Twitter, Spotify, Twitch, Kakao, and moreFile Storage
PocketBase has built-in file storage. Add file-type fields to collections to handle file uploads. Files are stored in the pb_data/storage directory with automatic thumbnail generation.
// Upload a file with a record
const formData = new FormData();
formData.append('title', 'My Photo');
formData.append('image', fileInput.files[0]);
const record = await pb.collection('photos').create(formData);
// Get the file URL
const url = pb.files.getURL(record, record.image);
// => http://127.0.0.1:8090/api/files/photos/RECORD_ID/filename.jpg
// Get a thumbnail (auto-generated)
const thumb = pb.files.getURL(record, record.image, {
thumb: '100x100',
});
// Upload multiple files
const multi = new FormData();
multi.append('title', 'Gallery');
multi.append('images', file1);
multi.append('images', file2);
multi.append('images', file3);
const gallery = await pb.collection('galleries').create(multi);
// Delete a file by setting it to empty
await pb.collection('photos').update(record.id, {
image: null,
});Hooks & Event Handlers
PocketBase supports JavaScript hooks. Place .pb.js files in the pb_hooks directory to intercept events and add custom logic. Hooks fire before and after record creation, update, and deletion.
// pb_hooks/main.pb.js
// Before creating a record - validate and modify data
onRecordCreate((e) => {
// Auto-generate a slug from the title
if (e.record.get('title')) {
const slug = e.record.get('title')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
e.record.set('slug', slug);
}
e.next();
}, 'posts');
// After creating a record - send notification
onRecordAfterCreateSuccess((e) => {
console.log('New post created:', e.record.get('title'));
// Send email, webhook, etc.
e.next();
}, 'posts');
// Add a custom API route
routerAdd('GET', '/api/stats', (e) => {
const posts = arrayOf(new Record());
$app.recordQuery('posts').all(posts);
return e.json(200, {
totalPosts: posts.length,
timestamp: new Date().toISOString(),
});
});
// Cron job: run cleanup daily at midnight
cronAdd('daily-cleanup', '0 0 * * *', () => {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 30);
$app.dao().deleteRecordsByFilter(
'temp_files',
'created < "' + cutoff.toISOString() + '"'
);
});Extending with Go
PocketBase can be used as a Go framework. Import PocketBase into your own Go project to access all internal APIs, register custom routes, middleware, and event handlers.
// main.go
package main
import (
"log"
"net/http"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// Add a custom route
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/api/hello", func(e *core.RequestEvent) error {
return e.JSON(http.StatusOK, map[string]string{
"message": "Hello from custom Go route!",
})
})
return se.Next()
})
// Hook into record events
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
log.Println("Creating post:", e.Record.GetString("title"))
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}# Initialize Go module and run
go mod init myapp
go mod tidy
go run main.go serveAdmin Dashboard
PocketBase includes a beautiful admin dashboard accessible at the /_/ path. The dashboard lets you visually manage collections, browse records, configure authentication, set API rules, and more.
- Create, edit, and delete collections and fields
- Browse, search, and filter records
- Configure API rules (list, view, create, update, delete)
- Manage OAuth2 providers and email templates
- View logs and system information
- Import and export collection schemas
- Manage admin accounts
API Rules & Access Control
PocketBase uses a rule-based access control system. Each API operation on every collection can have its own rule. An empty rule means access is denied. Rules starting with @request reference the current request context.
// API Rules examples (set in Admin UI for each collection)
// List/View: anyone can read published posts
// Rule: published = true
// List/View: only authenticated users
// Rule: @request.auth.id != ""
// Create: only authenticated users
// Rule: @request.auth.id != ""
// Update: only the author can update
// Rule: author = @request.auth.id
// Delete: only admins (leave blank to deny, or use admin-only)
// Rule: @request.auth.role = "admin"
// Combined: author or admin can update
// Rule: author = @request.auth.id || @request.auth.role = "admin"
// Access related records
// Rule: @request.auth.id = team.members.idDeployment
PocketBase can be deployed to any environment that can run a single binary. Here are common deployment options.
Docker Deployment
# Dockerfile
FROM alpine:latest
ARG PB_VERSION=0.25.0
RUN apk add --no-cache \
unzip \
ca-certificates
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
RUN unzip /tmp/pb.zip -d /pb/
EXPOSE 8090
CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090"]# docker-compose.yml
version: "3.8"
services:
pocketbase:
build: .
ports:
- "8090:8090"
volumes:
- pb_data:/pb/pb_data
- ./pb_hooks:/pb/pb_hooks
restart: unless-stopped
volumes:
pb_data:fly.io Deployment
# fly.toml
app = "my-pocketbase-app"
primary_region = "ord"
[build]
dockerfile = "Dockerfile"
[mounts]
source = "pb_data"
destination = "/pb/pb_data"
[http_service]
internal_port = 8090
force_https = true
# Deploy commands:
# fly launch
# fly volumes create pb_data --size 1 --region ord
# fly deployRailway Deployment
Railway supports deploying directly from a GitHub repository with a Dockerfile. Connect your repo and Railway will automatically build and deploy. Make sure to add a persistent volume mounted to the pb_data directory.
VPS + systemd Deployment
# /etc/systemd/system/pocketbase.service
[Unit]
Description=PocketBase
After=network.target
[Service]
Type=simple
User=pocketbase
Group=pocketbase
WorkingDirectory=/opt/pocketbase
ExecStart=/opt/pocketbase/pocketbase serve --http=127.0.0.1:8090
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
# Enable and start
# sudo systemctl enable pocketbase
# sudo systemctl start pocketbaseBackup & Migration
PocketBase stores all data in the pb_data directory. Backing up is as simple as copying that directory. You can also use the API or SQLite tools for backups.
# Manual backup: copy pb_data directory
cp -r /opt/pocketbase/pb_data /backup/pb_data_$(date +%Y%m%d)
# SQLite online backup (safe while running)
sqlite3 /opt/pocketbase/pb_data/data.db ".backup /backup/data_backup.db"
# Automated backup via cron (daily at 2 AM)
# crontab -e
0 2 * * * cp -r /opt/pocketbase/pb_data /backup/pb_data_$(date +\%Y\%m\%d)
# Use the admin API for backup
curl -X POST "http://127.0.0.1:8090/api/backups" \
-H "Authorization: Bearer ADMIN_TOKEN"
# List existing backups
curl "http://127.0.0.1:8090/api/backups" \
-H "Authorization: Bearer ADMIN_TOKEN"
# Migrate: copy pb_data to new server and start PocketBase
scp -r /opt/pocketbase/pb_data user@newserver:/opt/pocketbase/
ssh user@newserver "/opt/pocketbase/pocketbase serve"PocketBase vs Supabase vs Firebase
Here is a comparison of PocketBase with other popular BaaS (Backend-as-a-Service) platforms.
| Feature | PocketBase | Supabase | Firebase |
|---|---|---|---|
| Database | SQLite (embedded) | PostgreSQL | Firestore (NoSQL) |
| Hosting | Self-hosted | Cloud / Self-hosted | Cloud only |
| Pricing | Free (open-source) | Free tier + paid | Free tier + pay-as-you-go |
| Realtime | SSE | WebSocket (Postgres Changes) | WebSocket |
| Auth | Built-in (email + OAuth2) | Built-in (email + OAuth2 + more) | Built-in (email + OAuth2 + phone) |
| File Storage | Local / S3 | S3 compatible | Cloud Storage |
| Scalability | Single server (vertical) | Horizontal scaling | Auto-scaling |
| Language | Go | TypeScript / Elixir | Proprietary |
| Best For | MVPs, side projects, small-medium apps | Production apps, team collaboration | Mobile apps, rapid prototyping |
Security Best Practices
- Use HTTPS: Always use TLS in production. PocketBase supports automatic Let's Encrypt certificates, or place it behind an Nginx or Caddy reverse proxy.
- Configure API Rules: Never leave collection API rules unset without intention. Default empty rules mean deny access, but explicitly set rules for every operation.
- Restrict Admin Panel Access: In production, bind the admin panel to an internal network or use a firewall to restrict access to the /_/ path.
- Regular Backups: Set up automated backup schedules and regularly back up the pb_data directory.
- Use Strong Passwords: Set strong passwords for admin accounts and configure minimum password length requirements.
- Keep Updated: Regularly update PocketBase to the latest version to get security patches.
- Rate Limiting: Use a reverse proxy (Nginx, Caddy) to add rate limiting and prevent brute-force attacks.
- S3 Storage: For important file storage, configure S3-compatible external storage (AWS S3, Backblaze B2) instead of local disk.
# Nginx reverse proxy with rate limiting
upstream pocketbase {
server 127.0.0.1:8090;
}
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Rate limiting
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://pocketbase;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Restrict admin panel to specific IPs
location /_/ {
allow 10.0.0.0/8;
deny all;
proxy_pass http://pocketbase;
proxy_set_header Host $host;
}
# SSE for realtime (long-lived connections)
location /api/realtime {
proxy_pass http://pocketbase;
proxy_set_header Host $host;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
}
}S3-Compatible Storage Configuration
PocketBase supports storing files in S3-compatible services. Configure this in the Admin panel under Settings > Files storage.
// S3 configuration in Admin UI > Settings > Files storage
// Endpoint: s3.us-east-1.amazonaws.com (AWS)
// s3.us-west-004.backblazeb2.com (Backblaze B2)
// Bucket: my-pocketbase-files
// Region: us-east-1
// Access Key: your-access-key
// Secret: your-secret-key
// Force path style: true (for non-AWS providers)Full-Stack Example: React + PocketBase
Here is a complete CRUD application example built with React and PocketBase.
// src/lib/pocketbase.ts
import PocketBase from 'pocketbase';
export const pb = new PocketBase('http://127.0.0.1:8090');
// src/hooks/usePosts.ts
import { useState, useEffect } from 'react';
import { pb } from '../lib/pocketbase';
export function usePosts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch initial data
pb.collection('posts')
.getList(1, 50, { sort: '-created', expand: 'author' })
.then((result) => {
setPosts(result.items);
setLoading(false);
});
// Subscribe to realtime changes
pb.collection('posts').subscribe('*', (e) => {
if (e.action === 'create') {
setPosts((prev) => [e.record, ...prev]);
} else if (e.action === 'update') {
setPosts((prev) =>
prev.map((p) => (p.id === e.record.id ? e.record : p))
);
} else if (e.action === 'delete') {
setPosts((prev) => prev.filter((p) => p.id !== e.record.id));
}
});
return () => {
pb.collection('posts').unsubscribe();
};
}, []);
const createPost = async (data) => {
return pb.collection('posts').create(data);
};
const deletePost = async (id) => {
return pb.collection('posts').delete(id);
};
return { posts, loading, createPost, deletePost };
}Frequently Asked Questions
What is PocketBase and what are its primary use cases?
PocketBase is an open-source backend that ships as a single executable file. It includes an embedded SQLite database, built-in authentication, file storage, realtime subscriptions, and an admin dashboard. Primary use cases include MVPs, side projects, internal tools, mobile app backends, and small-to-medium web applications. It is written in Go and can be extended as a Go framework or with JavaScript hooks.
How does PocketBase compare to Supabase and Firebase?
PocketBase is self-hosted and runs as a single binary with zero dependencies, while Supabase and Firebase are cloud-hosted platforms. PocketBase uses SQLite, Supabase uses PostgreSQL, and Firebase uses a proprietary NoSQL database. PocketBase is free and open-source with no usage limits. Supabase offers a generous free tier but charges for scaling. Firebase has pay-as-you-go pricing. PocketBase is ideal for smaller applications and full control, while Supabase and Firebase are better for teams needing managed infrastructure and global scaling.
Can PocketBase handle production workloads?
Yes, PocketBase can handle production workloads for small-to-medium applications. SQLite can handle thousands of concurrent readers and many writes per second. PocketBase supports WAL mode for better concurrency. For high-traffic applications, you can deploy PocketBase behind a reverse proxy like Nginx or Caddy, enable gzip compression, and use a CDN for static assets. However, PocketBase does not support horizontal scaling or multi-server clustering, so it is not suitable for applications requiring millions of concurrent users.
How do I set up authentication in PocketBase?
PocketBase provides built-in authentication for both email/password and OAuth2 providers. Create an auth collection in the admin dashboard, then use the JavaScript SDK to register and login users. For OAuth2, enable providers like Google, GitHub, or Discord in the admin panel and configure client IDs and secrets. PocketBase handles token generation, session management, and password hashing automatically. You can also customize auth rules per collection for fine-grained access control.
Does PocketBase support realtime subscriptions?
Yes, PocketBase has built-in realtime support via Server-Sent Events (SSE). You can subscribe to changes on any collection and receive create, update, and delete events in real time. The JavaScript SDK provides a simple subscribe method that handles reconnection automatically. Realtime subscriptions respect collection API rules, so users only receive events for records they have permission to access.
How do I deploy PocketBase to production?
PocketBase can be deployed anywhere that runs a single binary. Common deployment options include: Docker (using a minimal Dockerfile with the PocketBase binary), fly.io (using a Dockerfile and fly.toml with a persistent volume), Railway (connecting your GitHub repo), or a plain VPS with systemd for process management. Always mount a persistent volume for the pb_data directory, set up regular backups, and use a reverse proxy with TLS termination.
Can I extend PocketBase with custom logic?
Yes, PocketBase can be extended in two ways. First, you can use JavaScript hooks by placing .pb.js files in the pb_hooks directory. These hooks intercept events like record creation, update, and deletion, and can add custom API routes. Second, you can use PocketBase as a Go framework by importing it into your own Go project and registering custom routes, middleware, and event handlers. The Go approach gives full access to the PocketBase internals and the Go standard library.
How do I back up and migrate a PocketBase database?
PocketBase stores all data in the pb_data directory, which contains the SQLite database, uploaded files, and configuration. To back up, copy the entire pb_data directory or use the built-in backup API endpoint. For automated backups, use a cron job with sqlite3 .backup command or the PocketBase admin API. To migrate, copy pb_data to a new server and start PocketBase. Schema migrations can be managed through the admin UI or by exporting and importing collection schemas as JSON.