Skip to content
Browse docs

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) — or a supporting file like templates/welcome.html
  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
pbc pocketbase deploy         # pushes every supported file in pb_hooks/

Or push a directory explicitly, without deploying anything else:

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

Only PocketBase’s own hook loader runs *.pb.js; other text files travel too (.js, .json, .html, .css, .md, and a handful more), so a hook that require()s a helper module or reads a template file finds it on the instance. A pb_hooks/ subdirectory uploads fine and keeps its relative path. The instance reloads hooks automatically after any push. 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.

pbc pocketbase 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