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
- Open your PocketBase instance in the portal
- Go to the Hooks tab
- Click New Hook, give the file a name ending in
.pb.js(e.g.,main.pb.js) — or a supporting file liketemplates/welcome.html - 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 lslists 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 routesonRecord*event handlers — before/after create, update, deletecronAdd— 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.