Skip to content
Back to blog
pocketbasehooksclifeatures

Organize Your PocketBase Hooks With Folders and Real Templates

September 17, 2026·Tom
Organize Your PocketBase Hooks With Folders and Real Templates

Organize Your PocketBase Hooks With Folders and Real Templates

pb_hooks/ is where your PocketBase instance’s server-side JavaScript lives — custom routes, record hooks, cron jobs. Until now, it was also a flat pile. Every file sat directly in pb_hooks/, and the only extensions allowed were .pb.js, .js, and .json. A lib/ folder to keep a growing set of hooks readable was refused outright. An HTML email template, a CSS file for a generated PDF, a Markdown doc — none of it had anywhere to go.

That’s fixed. pb_hooks now takes a much longer list of text formats, and it takes subdirectories.

What’s different

More file types. Alongside .pb.js, .js, .mjs, .cjs, and .json, you can now push .html, .htm, .css, .txt, .md, .csv, .xml, .svg, .yml, and .yaml. PocketBase’s own hook loader still only executes *.pb.js — everything else is there for your hook code to read, the same way a plain .js helper module always was.

Subdirectories, up to five levels deep. pb_hooks/emails/welcome.html uploads and installs at exactly that path now, instead of being silently skipped or, if you tried it through a full archive deploy, failing the whole upload.

A higher file-count ceiling. The old limits — 30 files from the CLI, 50 from the platform — are both 500 now, and they’re the same number, so there’s no gap where the CLI refuses a push the portal would have accepted.

Using it

Nothing new to learn — the same three ways of managing hooks just stopped rejecting subdirectories and most extensions:

# push a whole directory, subfolders included
pbc pocketbase hooks push ./pb_hooks --name my-app-db

# a full deploy carries pb_hooks along with pb_migrations and pb_public
cd db
pbc pocketbase deploy

Or create emails/welcome.html directly in the portal’s Hooks editor — the editor picks HTML syntax highlighting automatically instead of assuming everything is JavaScript.

A hook can now read its own template instead of inlining a string of HTML in the middle of your route handler:

routerAdd("POST", "/api/signup", (e) => {
  const html = $os.readFile("pb_hooks/emails/welcome.html");

  const message = new MailerMessage({
    from: { address: e.app.settings().meta.senderAddress },
    to: [{ address: e.requestInfo().body.email }],
    subject: "Welcome!",
    html: html,
  });
  e.app.newMailClient().send(message);

  return e.json(200, { ok: true });
});

The trade-off, said out loud

Real binary files — images, PDFs, anything that isn’t text — still aren’t supported in pb_hooks. The hook content is stored as text, and reading a PNG as text corrupts it; doing that safely needs a different transport, which isn’t built yet. That’s what pb_public is for in the meantime: it already accepts any file type through a full deploy, so images and other assets that your app serves — as opposed to files your hook code reads — have a home today.

Try it

Push a nested hook file to an existing instance:

mkdir -p pb_hooks/emails
echo "<p>Hi {{name}}!</p>" > pb_hooks/emails/welcome.html
pbc pocketbase hooks push ./pb_hooks --name my-app-db
pbc pocketbase hooks ls --name my-app-db

For everything else hooks can do — record events, cron jobs, custom routes — see Extending with Hooks. If you haven’t deployed a PocketBase instance yet, Deploying PocketBase covers the whole pbc pocketbase deploy flow, and Ship Schema Migrations to a Running Instance covers the other two directories a deploy carries.

Open the portal →