Skip to content
DocsPocketBaseExtending with Hooks

Extending with Hooks

PocketBase hooks let you run custom JavaScript on the server — right inside your instance. Add API routes, react to record changes, schedule cron jobs, or send emails, all without deploying a separate backend.

On PocketBase Cloud you can write hooks in the portal’s editor, or keep them as files in your repository and push them with the CLI.

Creating a hook

Using the portal

  1. Open your PocketBase instance in the portal
  2. Go to the Hooks tab
  3. Click New Hook, give the file a name ending in .pb.js (e.g., main.pb.js)
  4. Write your code and save — the instance reloads hooks automatically

Using the CLI

Keep hook files in your project’s pb_hooks/ directory and push them from there. In a directory linked to the instance, a deploy is enough:

cd db
pb cloud pb deploy         # pushes every pb_hooks/*.pb.js

Or push a directory explicitly, without deploying anything else:

pb cloud pb hooks push ./pb_hooks --name my-app-db
pb cloud pb hooks ls --name my-app-db
pb cloud pb hooks rm old.pb.js --name my-app-db

Only files ending in .pb.js are uploaded, and the instance reloads them automatically. This is the path to prefer if you want hooks reviewed in pull requests and testable locally — see Local Development, where ./pocketbase serve runs the very same pb_hooks/ directory on your machine.

pb cloud pb hooks ls lists hooks that were pushed through the CLI or written in the portal. Hooks shipped inside a creation archive run on the instance but aren’t recorded there, so the list can read empty while hooks are live. Push them once to manage them from the CLI.

Example: a custom API route

routerAdd("GET", "/api/hello/{name}", (e) => {
  const name = e.request.pathValue("name");
  return e.json(200, { message: `Hello ${name}!` });
});

Your route is immediately available at https://<instance-name>.pocketbasecloud.com/api/hello/world.

Example: react to record changes

Run logic whenever a record is created, updated, or deleted:

onRecordCreate((e) => {
  // runs before the record is persisted — you can still modify it
  e.record.set("slug", e.record.get("title").toLowerCase().replaceAll(" ", "-"));
  e.next();
}, "posts");

onRecordAfterCreateSuccess((e) => {
  // runs after the record is saved
  console.log("new post:", e.record.id);
  e.next();
}, "posts");

Example: scheduled cron jobs

cronAdd("cleanup", "0 3 * * *", () => {
  const records = $app.findRecordsByFilter(
    "sessions",
    "created < @yesterday",
    "-created",
    200,
    0
  );
  for (const record of records) {
    $app.delete(record);
  }
});

Example: send an email

onRecordAfterCreateSuccess((e) => {
  const message = new MailerMessage({
    from: { address: e.app.settings().meta.senderAddress },
    to: [{ address: e.record.email() }],
    subject: "Welcome!",
    html: "<p>Thanks for signing up.</p>",
  });
  e.app.newMailClient().send(message);
  e.next();
}, "users");

What hooks can access

Hooks run in PocketBase’s embedded JavaScript VM with access to:

  • $app — the full PocketBase app instance (query, create, update records)
  • routerAdd — register custom HTTP routes
  • onRecord* event handlers — before/after create, update, delete
  • cronAdd — cron-style scheduled jobs
  • $http.send() — make outbound HTTP requests to third-party APIs
  • $os, $filesystem, $security — utility namespaces

See the PocketBase JSVM documentation for the complete API.

When to use a backend instead

Hooks are perfect for lightweight logic tied to your data. For heavier workloads — long-running jobs, large dependencies, websockets, or code you want to develop and test locally as a normal project — deploy a dedicated backend alongside your instance.

Next steps