Skip to main content
โšก Calmops

PocketBase: The Open-Source Backend-in-a-Box

PocketBase is an open-source backend that combines a SQLite database, auth system, realtime subscriptions, and file storage in a single executable. This comprehensive guide covers everything you need to know.

What is PocketBase?

PocketBase is a backend solution written in Go that provides everything you need to build modern web and mobile applications.

# Download and run
./pocketbase serve

Features include:

  • SQLite Database - Embedded, portable database
  • User Authentication - Email, OAuth, and anonymous auth
    • Realtime Subscriptions - Server-Sent Events
  • File Storage - Local and S3-compatible
    • Admin Dashboard - Built-in UI
    • REST API - Full CRUD operations

Installation and Setup

Running PocketBase

# Download
curl -L -o pocketbase https://github.com/pocketbase/pocketbase/releases/latest/download/pocketbase_*.zip

# Extract
unzip pocketbase_*.zip

# Run
./pocketbase serve

# Or run in background
./pocketbase serve --http=127.0.0.1:8090

First Run

# Visit admin UI
# http://127.0.0.1:8090/_/

# Create your first admin account
# Email: [email protected]
# Password: your-secure-password

Database Schema

Collections

Create collections via the Admin UI or API:

# Create collection via API (as admin)
curl -X POST http://127.0.0.1:8090/api/collections \
  -H "Authorization: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "articles",
    "type": "base",
    "system": false,
    "schema": [
      {
        "system": "id",
        "name": "title",
        "type": "text",
        "required": true
      },
      {
        "name": "content",
        "type": "text",
        "required": true
      },
      {
        "name": "slug",
        "type": "text",
        "required": true,
        "options": {
          "pattern": "^[a-z0-9-]+$"
        }
      },
      {
        "name": "published",
        "type": "bool",
        "required": false
      },
      {
        "name": "author",
        "type": "relation",
        "required": true,
        "options": {
          "collectionId": "_users",
          "cascadeDelete": false,
          "maxSelect": 1
        }
      }
    ],
    "listRule": "published = true",
    "viewRule": "published = true",
    "createRule": "@request.auth.id != ''",
    "updateRule": "@request.auth.id = author.id",
    "deleteRule": "@request.auth.id = author.id"
  }'

Field Types

PocketBase supports various field types:

  • Text - Single line, multi-line
  • Editor - Rich text (Markdown)
  • Number - Integer, Float
  • Bool - Boolean
  • Email - Validated email
  • URL - Validated URL
  • File - Single/Multiple files
  • Relation - One-to-one, one-to-many
  • JSON - JSON object/array
  • Date - Date and datetime
  • Select - Single/Multi select
  • User - Relation to users collection

Authentication

User Registration

// Register new user
const result = await pb.collection('users').create({
  email: '[email protected]',
  password: 'secure-password',
  passwordConfirm: 'secure-password',
  name: 'John Doe'
});

console.log(result);

User Login

// Email/Password login
const authData = await pb.collection('users').authWithPassword(
  '[email protected]',
  'secure-password'
);

console.log(authData);
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);

OAuth2 Login

// Google OAuth
await pb.collection('users').authWithOAuth2({
  provider: 'google'
});

// With redirect
await pb.collection('users').authWithOAuth2({
  provider: 'google',
  createData: {
    name: profile.name,
    avatarUrl: profile.avatarUrl
  }
});

Auth Store

// Check authentication state
pb.authStore.isValid; // true/false

// Get current user
pb.authStore.model; // User object

// Refresh token
await pb.collection('users').authRefresh();

// Logout
pb.authStore.clear();

JavaScript SDK

Installation

# Via npm
npm install pocketbase

# Via CDN
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pocketbase.umd.js"></script>

Basic Usage

import PocketBase from 'pocketbase';

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

// Create record
const record = await pb.collection('articles').create({
  title: 'My First Article',
  content: 'Content goes here...',
  slug: 'my-first-article',
  published: true,
  author: pb.authStore.model.id
});

// Get records
const records = await pb.collection('articles').getList(1, 20, {
  sort: '-created',
  filter: 'published = true'
});

// Get single record
const article = await pb.collection('articles').getOne('RECORD_ID');

// Update record
await pb.collection('articles').update('RECORD_ID', {
  title: 'Updated Title'
});

// Delete record
await pb.collection('articles').delete('RECORD_ID');

Querying

// Filter
const records = await pb.collection('articles').getList(1, 10, {
  filter: 'published = true && created >= "2024-01-01"',
  sort: '-created,title'
});

// Full-text search
const search = await pb.collection('articles').getList(1, 10, {
  filter: 'title ?~ "javascript"'
});

// Relations
const records = await pb.collection('articles').getList(1, 10, {
  expand: 'author',
  filter: 'author.name ?~ "John"'
});

// Select specific fields
const records = await pb.collection('articles').getList(1, 10, {
  fields: 'id,title,slug,author.id,author.name'
});

Realtime Subscriptions

// Subscribe to changes
pb.collection('articles').subscribe('*', function (e) {
  console.log(e.action);   // "create" | "update" | "delete"
  console.log(e.record);   // The changed record
});

// Subscribe to specific record
pb.collection('articles').subscribe('RECORD_ID', function (e) {
  console.log(e.action);
  console.log(e.record);
});

// Unsubscribe
pb.collection('articles').unsubscribe();

// Unsubscribe all
pb.collection('articles').unsubscribe('*');

File Handling

Uploading Files

// Create with file
const formData = new FormData();
formData.append('title', 'Article with Image');
formData.append('image', fileInput.files[0]);

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

// Update with file
await pb.collection('articles').update('RECORD_ID', {
  'image': fileInput.files[0]
});

Serving Files

// Get file URL
const url = pb.files.getUrl(record, record.image, {
  thumb: '100x100'  // Generate thumbnail
});

// Different thumbnail presets
// '100x100' - fit
// '100x100!' - crop
// '100x' - width only
// 'x100' - height only

S3 Storage

Configure in pb_config.yaml:

filesystem:
  endpoint: https://s3.amazonaws.com
  bucket: my-bucket
  region: us-east-1
  accessKey: your-access-key
  secretKey: your-secret-key
  forcePathStyle: false

API Rules

Collection Rules

// Everyone can view
listRule: ""
viewRule: ""

// Authenticated users only
listRule: "@request.auth.id != ''"

// Owner only
listRule: "@request.auth.id = user.id"

// Field-level
{
  "name": "email",
  "system": false,
  "type": "email",
  "required": true,
  "presentable": false,
  "views": {},
  "createRule": null,
  "updateRule": "@request.auth.id = user.id",
  "deleteRule": null
}

Available Variables

  • @request.id - Request unique ID
  • @request.auth.id - Authenticated user ID
  • @request.auth.email - Authenticated user email
  • @collection.field - Field value from current record
  • @collection.expand.field - Expanded relation field

Admin API

Authentication

// As admin (using superuser API token)
pb.admins.authWithToken('ADMIN_TOKEN');

// Or with email/password
await pb.admins.authWithPassword('[email protected]', 'password');

Managing Collections

// List collections
const collections = await pb.collections.getList(1, 50);

// Create collection
await pb.collections.create({
  name: 'products',
  type: 'base',
  schema: [
    { name: 'name', type: 'text', required: true },
    { name: 'price', type: 'number', required: true }
  ]
});

// Delete collection
await pb.collections.delete('COLLECTION_ID');

Building a Full-Stack App

Frontend Example

<!DOCTYPE html>
<html>
<head>
  <title>PocketBase Todo</title>
  <script src="https://unpkg.com/[email protected]/dist/pocketbase.umd.js"></script>
</head>
<body>
  <h1>Todo App</h1>
  
  <div id="auth-section">
    <input type="email" id="email" placeholder="Email">
    <input type="password" id="password" placeholder="Password">
    <button onclick="login()">Login</button>
    <button onclick="register()">Register</button>
  </div>
  
  <div id="app-section" style="display:none">
    <input type="text" id="todo-input" placeholder="New todo">
    <button onclick="addTodo()">Add</button>
    <ul id="todo-list"></ul>
    <button onclick="logout()">Logout</button>
  </div>
  
  <script>
    const pb = new PocketBase('http://127.0.0.1:8090');
    
    async function register() {
      try {
        await pb.collection('users').create({
          email: document.getElementById('email').value,
          password: document.getElementById('password').value,
          passwordConfirm: document.getElementById('password').value
        });
        alert('Registered! Please login.');
      } catch (e) {
        alert(e.message);
      }
    }
    
    async function login() {
      try {
        await pb.collection('users').authWithPassword(
          document.getElementById('email').value,
          document.getElementById('password').value
        );
        showApp();
      } catch (e) {
        alert(e.message);
      }
    }
    
    async function addTodo() {
      const input = document.getElementById('todo-input');
      await pb.collection('todos').create({
        title: input.value,
        user: pb.authStore.model.id
      });
      input.value = '';
      loadTodos();
    }
    
    async function loadTodos() {
      const todos = await pb.collection('todos').getList(1, 50, {
        filter: `user = "${pb.authStore.model.id}"`,
        sort: '-created'
      });
      
      const list = document.getElementById('todo-list');
      list.innerHTML = todos.items.map(t => 
        `<li>${t.title} <button onclick="deleteTodo('${t.id}')">X</button></li>`
      ).join('');
    }
    
    async function deleteTodo(id) {
      await pb.collection('todos').delete(id);
      loadTodos();
    }
    
    function showApp() {
      document.getElementById('auth-section').style.display = 'none';
      document.getElementById('app-section').style.display = 'block';
      loadTodos();
    }
    
    function logout() {
      pb.authStore.clear();
      location.reload();
    }
    
    // Check auth
    if (pb.authStore.isValid) {
      showApp();
    }
  </script>
</body>
</html>

External Resources

Conclusion

PocketBase provides an excellent backend solution for small to medium applications. Key points:

  • Single executable - no complex setup
  • SQLite database with relational support
  • Built-in authentication with OAuth
  • Realtime subscriptions via SSE
  • File storage with S3 support
  • Admin UI for data management

For rapid prototyping and smaller applications, PocketBase offers excellent value with minimal infrastructure requirements.

Comments