Lesedauer 6 Minuten

PocketBase nimmt dir mit seiner REST-API und dem Admin-Dashboard viel Arbeit ab. Früher oder später kommst du aber an einen Punkt, an dem Filter und Standard-Endpunkte nicht mehr reichen: Du willst Daten gruppieren, Summen bilden, mehrere Collections verknüpfen oder eine Statistik für dein Dashboard berechnen. Genau dann lohnt sich ein Blick unter die Haube. PocketBase speichert alles in einer SQLite-Datenbank, und du kannst mit echtem SQL direkt darauf zugreifen.

In diesem Beitrag zeige ich dir, welche Wege es gibt, SQL-Abfragen in PocketBase zu nutzen, wie du sie sicher schreibst und worauf du bei Performance und Datenstruktur achten solltest.

Warum SQL in PocketBase?

PocketBase ist ein Backend in einer einzigen Datei. Unter der Oberfläche steckt SQLite, und jede Collection ist eine ganz normale Tabelle. Die REST-API mit ihrer Filtersyntax wie status = "active" && created > "2026-01-01" deckt die meisten Alltagsfälle ab. Für folgende Aufgaben brauchst du aber SQL:

  • Aggregationen: COUNT, SUM, AVG und GROUP BY für Auswertungen und Reports.
  • Komplexe Joins: Daten aus mehreren Collections in einer einzigen Abfrage zusammenführen.
  • Massenänderungen: Viele Datensätze auf einmal aktualisieren, ohne jeden einzeln über die API anzufassen.
  • Eigene Endpunkte: Maßgeschneiderte API-Routen, die genau die Daten liefern, die dein Frontend braucht.

Die vier Wege zu SQL in PocketBase

Es gibt nicht nur einen Weg, SQL in PocketBase einzusetzen. Je nach Anwendungsfall wählst du eine der folgenden Varianten:

Vier Wege zu SQL in PocketBase

1. View Collections: SQL direkt im Dashboard

Der einfachste Einstieg sind View Collections. Du legst im Admin-Dashboard eine neue Collection vom Typ „View“ an und hinterlegst eine SELECT-Abfrage. PocketBase behandelt das Ergebnis wie eine schreibgeschützte Collection. Du kannst sie über die normale REST-API abfragen, filtern, sortieren und mit API-Regeln absichern.

Ein Beispiel: Du hast eine Collection orders mit einer Relation customer und einem Feld total. Diese View liefert dir den Umsatz pro Kunde:

Wichtig dabei:

  • Jede View braucht eine Spalte id. Die muss pro Zeile eindeutig sein. Bei Aggregationen ohne natürliche ID kannst du z. B. (ROW_NUMBER() OVER()) AS id verwenden.
  • Views sind nur lesend. Create, Update und Delete gibt es hier nicht.
  • Die Abfrage wird bei jedem Request ausgeführt. Bei großen Datenmengen spielen Indizes deshalb eine große Rolle.

Danach rufst du die View wie jede andere Collection ab:

2. SQL in JavaScript-Hooks

Wenn du PocketBase mit JavaScript erweiterst (Dateien im Ordner pb_hooks), bekommst du über $app.db() Zugriff auf den Query Builder. Damit kannst du beliebige SQL-Abfragen ausführen, lesend und schreibend.

So baust du einen eigenen Endpunkt, der Statistiken liefert:

Ein paar Details, die dir Zeit sparen:

  • DynamicModel beschreibt die Form des Ergebnisses. Die Standardwerte bestimmen den Typ: "" für Text, 0 für Zahlen, false für Booleans.
  • arrayOf() brauchst du für mehrere Zeilen mit .all(). Für genau eine Zeile nimmst du ein einzelnes Model und .one().
  • {:name} ist ein Platzhalter, den du über .bind() befüllst. Dazu gleich mehr.

Für schreibende Operationen verwendest du .execute():

3. SQL in Go

Nutzt du PocketBase als Go-Framework, funktioniert das ganz ähnlich über app.DB(). Du hast die Wahl zwischen rohem SQL und dem fluenten Query Builder der Bibliothek dbx:

Oder mit dem Builder, ganz ohne SQL-String:

4. Direkt mit sqlite3 auf die Datenbank

Die Datei pb_data/data.db ist eine ganz normale SQLite-Datenbank. Du kannst sie mit dem sqlite3-Kommandozeilenwerkzeug oder einem Tool wie DB Browser for SQLite öffnen. Das ist ideal, um schnell etwas nachzuschauen oder eine Abfrage zu testen, bevor du sie in eine View oder einen Hook übernimmst.

Schreibende Zugriffe solltest du auf diesem Weg vermeiden, solange PocketBase läuft. Du umgehst damit Hooks, Validierung und API-Regeln, und PocketBase bekommt von deinen Änderungen nichts mit. Mach vorher in jedem Fall ein Backup.

Sicherheit: Immer Parameter binden

Der wichtigste Grundsatz beim Arbeiten mit SQL-Abfragen: Baue niemals Benutzereingaben per String-Verkettung in deine Query ein. Das öffnet Tür und Tor für SQL-Injection.

Beim Binden kümmert sich die Datenbank um das korrekte Escaping. Denk außerdem daran: Eigene Routen und Hooks umgehen die API-Regeln deiner Collections. Prüfe selbst, ob der aktuelle Nutzer die Daten sehen darf, etwa mit $apis.requireAuth() oder einer Bedingung auf e.auth.id in deiner Abfrage.

Besonderheiten der PocketBase-Datenstruktur

Wenn du SQL schreibst, arbeitest du direkt mit der internen Struktur. Ein paar Dinge solltest du kennen:

  • IDs sind zufällige 15-stellige Strings, keine fortlaufenden Zahlen.
  • Datumsfelder wie created und updated werden als Text im Format 2026-11-01 10:30:00.000Z gespeichert. Du kannst sie trotzdem mit SQLite-Funktionen wie strftime() und date() auswerten und als String vergleichen.
  • Einfache Relationen speichern die ID des verknüpften Datensatzes als Text. Ein Join funktioniert ganz normal mit ON o.customer = c.id.
  • Mehrfach-Relationen und Select-Felder mit mehreren Werten werden als JSON-Array gespeichert. Hier hilft dir json_each().

Ein Beispiel für eine Mehrfach-Relation: Ein Artikel hat mehrere Tags im Feld tags. So zählst du, wie oft jeder Tag verwendet wird:

Auf dieselbe Weise liest du Werte aus JSON-Feldern mit json_extract(meta, '$.author') aus.

Welcher Weg passt zu dir?

Entscheidungshilfe für SQL in PocketBase

Performance: Indizes und EXPLAIN

SQLite ist erstaunlich schnell, aber nur, wenn deine Abfragen passende Indizes finden. Filterst oder joinst du regelmäßig über ein Feld wie customer oder status, lege dafür einen Index an. Das geht in den Einstellungen der Collection im Dashboard unter „Indexes“:

Ob SQLite deinen Index nutzt, verrät dir EXPLAIN QUERY PLAN:

Steht im Ergebnis SEARCH ... USING INDEX, ist alles gut. Siehst du dagegen SCAN orders, durchsucht SQLite die komplette Tabelle. Bei ein paar hundert Einträgen merkst du das kaum, bei hunderttausenden schon.

Weitere Tipps:

  • Wähle nur die Spalten aus, die du wirklich brauchst, statt SELECT *.
  • Nutze LIMIT bei großen Ergebnismengen.
  • Fasse mehrere schreibende Abfragen in einer Transaktion zusammen, z. B. mit $app.runInTransaction((txApp) => { ... }). Innerhalb der Transaktion verwendest du dann txApp.db().

Fazit

PocketBase macht dir den Einstieg leicht, sperrt dich aber nicht ein. Weil unter der Haube SQLite arbeitet, kannst du jederzeit zu echtem SQL greifen. Für Auswertungen, die über die API verfügbar sein sollen, sind View Collections die erste Wahl. Brauchst du volle Kontrolle oder willst du Daten ändern, gehst du über $app.db() in JavaScript-Hooks oder app.DB() in Go. Und für schnelle Analysen öffnest du die Datenbank einfach mit sqlite3. Wenn du dabei immer Parameter bindest und an deine Indizes denkst, holst du das Maximum aus deinem Backend heraus.

Alle Details zum Query Builder findest du in der offiziellen PocketBase-Dokumentation zur Datenbank. Die verfügbaren SQL-Funktionen, etwa für JSON und Datumswerte, sind in der SQLite-Dokumentation beschrieben.

FAQ: SQL-Abfragen in PocketBase

Welche Datenbank verwendet PocketBase?

PocketBase nutzt SQLite. Alle Daten liegen in der Datei pb_data/data.db. Jede Collection entspricht einer Tabelle in dieser Datenbank.

Kann ich über die REST-API direkt SQL senden?

Nein, und das ist auch gut so. Die REST-API akzeptiert nur die eigene Filtersyntax. Wenn du SQL-Ergebnisse über die API bereitstellen willst, nutzt du eine View Collection oder einen eigenen Endpunkt in einem Hook.

Kann ich in einer View Collection Daten ändern?

Nein. View Collections sind schreibgeschützt. Für Änderungen brauchst du JavaScript-Hooks, Go-Code oder die normalen Collection-Endpunkte.

Gelten die API-Regeln auch für meine eigenen SQL-Abfragen?

Für View Collections ja, dort legst du List- und View-Regeln wie gewohnt fest. Abfragen über $app.db() oder app.DB() umgehen die Regeln dagegen. Hier bist du selbst für die Zugriffsprüfung verantwortlich.

Wie schütze ich mich vor SQL-Injection?

Verwende immer Platzhalter wie {:name} und übergib die Werte über .bind() bzw. Bind(). Setze Benutzereingaben niemals per String-Verkettung in die Abfrage ein.

Wie frage ich Mehrfach-Relationen mit SQL ab?

Mehrfach-Relationen werden als JSON-Array gespeichert. Mit json_each() zerlegst du das Array in einzelne Zeilen und kannst sie anschließend ganz normal joinen.