Files
2026-07-12 10:17:17 -04:00

5.8 KiB

PocketBase SDK Path Construction

The double /api trap

When building a reverse proxy for a PocketBase-backed SPA, the PB JS SDK constructs its own API paths. Understanding this avoids the most common proxy bug.

How the PB SDK builds URLs

The PocketBase JS SDK's buildURL method concatenates baseURL + path:

// If you create: new PocketBase('/pb')
// Then calling pb.collection('users').authWithPassword(email, password)
// SDK builds: GET /pb/api/collections/users/auth-with-password
//            ^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         baseURL  SDK-added /api/ path

The SDK ALWAYS prepends /api/ to its paths. Your proxy must strip only the /pb prefix and forward the rest as-is — including the /api/ that the SDK already added.

The bug

# WRONG — this adds a second /api/, producing /api/api/collections/...
if self.path.startswith('/pb'):
    url = PB_BACKEND + '/api' + self.path[3:]  # DOUBLE /api!

PocketBase returns 404 {"message": "The requested resource wasn't found."} for the double-path URL, which is the same error message as genuinely missing resources — making this a silent, confusing failure.

The fix

# CORRECT — preserve the original path, which already includes /api/
if self.path.startswith('/pb'):
    url = PB_BACKEND + self.path[3:]  # /pb/X → /X on backend

Python http.server path behavior

BaseHTTPRequestHandler.path includes the query string. When forwarding, the query string is already part of the path. urllib.request.Request(url) handles query strings correctly, so no special parsing is needed.

PocketBase admin auth

PocketBase admin/superuser authentication is version-dependent:

  • v0.22+: POST /api/collections/_superusers/auth-with-password
  • Older: POST /api/admins/auth-with-password (may be a 404)
  • The admin dashboard is at /_/ (web UI only, not an API endpoint)

If neither endpoint works, check the PocketBase version. The admin user must be created via the ./pocketbase superuser CLI command or through the web UI on first run.

PocketBase Query Field Errors (400 "Something went wrong")

PocketBase returns 400 {"message": "Something went wrong while processing your request."} when a query references fields that don't exist in the collection schema. This is NOT a 404 — it's a 400, and the error message is generic, making it easy to misdiagnose as a permissions or setup issue.

The created / updated system field trap

Collections created via the PocketBase API (rather than the admin UI) may lack the created and updated system fields. Any query that sorts or filters on these fields will fail with 400.

Symptom: sort: '-created' succeeds via curl against a valid collection but returns 400 when tested against the same collection on a different PB instance. The collection EXISTS but the query fails because the field doesn't.

Detection: Test the exact query parameters one at a time:

# Isolate which parameter breaks
# Test: ?page=1 (should work)
# Test: ?page=1&sort=-created (fails → field missing)
# Test: ?page=1&fields=created (may fail or be silently ignored)

Workaround: Replace sort: '-created' with sort: '-id' — PocketBase record IDs are sortable and roughly chronological. For fields, remove any references to created/updated if the collection lacks them.

Root cause: Subagents building against one PB instance may use system fields that work there, but the target deployment's PB instance has collections created via API without auto-system-fields enabled.

Collection naming mismatches

PocketBase collection names are case-sensitive and can use either camelCase (repairOrders) or snake_case (repair_orders) depending on how they were created. The API silently returns 404 for the wrong case, and the error message is identical to a genuinely missing collection. Always verify the exact collection name by querying GET /api/collections before assuming a collection doesn't exist.

PocketBase Sign-Up Error Handling

When pb.collection('users').create() fails, the PocketBase SDK throws a ClientResponseError with a generic top-level .message (e.g., "Failed to create record.") and field-level details in .data:

{
  "data": {
    "email": {"code": "validation_not_unique", "message": "Value must be unique."}
  },
  "message": "Failed to create record.",
  "status": 400
}

Never use the raw .message directly — it's always "Failed to create record." for any validation failure. Instead, unwrap .data to show the user which field failed:

} catch (err: unknown) {
  let message = 'Failed to create account.';
  if (err && typeof err === 'object' && 'data' in err) {
    const pbErr = err as { data?: Record<string, { message: string }> };
    if (pbErr.data) {
      const fieldErrors = Object.entries(pbErr.data)
        .map(([field, info]) => `${field}: ${info.message}`)
        .join('; ');
      if (fieldErrors) message = fieldErrors;
    }
  }
  setError(message);
}

This produces user-friendly messages like email: Value must be unique. instead of the unhelpful Failed to create record.

Common PocketBase validation errors on user creation:

  • email not uniqueemail: Value must be unique.
  • password too shortpassword: Must be at least 8 characters.
  • missing required fieldemail: Cannot be blank.

PocketBase SMTP Configuration

PocketBase requires SMTP to be configured for email verification to work. Check the _params table in the PocketBase data directory:

SELECT value FROM _params WHERE id='settings'

If smtp.enabled is false, verification emails will never send. Configure SMTP via the PocketBase admin UI (/_/) or by updating the _params row directly with valid SMTP credentials (host, port, username, password, TLS).