JavaScript extensions overview
Set up PocketBase server-side JavaScript hooks, globals, migrations, and reusable modules.
Use the embedded JavaScript VM to extend PocketBase with server-side hooks, routes, migrations, and application logic. This tutorial creates a small hook, explains the runtime boundaries you must design for, and shows where extension files belong.
Before you begin
You need a PocketBase executable and a working application directory. The prebuilt executable loads JavaScript files from a pb_hooks directory next to the executable. Hook files use the *.pb.js or *.pb.ts pattern; .pb.ts files provide editor declarations but still require JavaScript-compatible execution.
Understand the extension layout
your-app/
├── pocketbase
├── pb_hooks/
│ ├── main.pb.js
│ └── helpers.js
├── pb_migrations/
└── pb_data/
└── types.d.tsThe VM loads hook files in filename order. When hook watching is enabled, changes in pb_hooks can trigger an automatic reload; the source documents this behavior as currently supported on UNIX-based platforms. Use __hooks when you need an absolute path to the hooks directory because relative paths resolve from the process working directory, not from pb_hooks.
Create a first hook
Create pb_hooks beside the PocketBase executable, then create pb_hooks/main.pb.js.
Write a handler that continues the bootstrap chain before doing work that depends on application resources.
/// <reference path="../pb_data/types.d.ts" />
onBootstrap((e) => {
e.next()
console.log("PocketBase application initialized")
})Start the application with your normal PocketBase serve command. The application loads non-empty hook files during startup.
Check the process output for PocketBase application initialized. If you edit the file while hook watching is enabled, confirm the application reloads and emits the message again.
Create pb_hooks/helpers.js and export reusable functions. Load the module inside a handler with require(${__hooks}/helpers.js).
// pb_hooks/helpers.js
module.exports = { message: (name) => `Hello ${name}` }
// pb_hooks/main.pb.js
onBootstrap((e) => {
e.next()
const helpers = require(`${__hooks}/helpers.js`)
console.log(helpers.message("PocketBase"))
})pb_hooks and can access the built-in globals.Use the runtime globals
The VM exposes $app for the current PocketBase application, $apis.* for API routing helpers and middleware, $os.* for operating-system primitives, $security.* for JWT, random-string, and encryption helpers, and __hooks for the absolute hooks path. Other exposed APIs are documented in the JSVM reference.
Design within the runtime limits
Each handler is serialized and executed in its own isolated context. Variables declared outside a handler are not available inside it. CommonJS modules can be shared, but their registry is shared too, so avoid mutable module state when handlers may run concurrently. The engine is not a Node.js or browser environment: modules that require browser globals or unsupported Node APIs might not work. Only CommonJS loading is supported directly; bundle ESM dependencies before loading them.
The embedded engine implements much of ES6 but is not fully ECMAScript compliant. A handler has no concurrent execution primitives such as setTimeout or setInterval, and wrapped Go values can differ from native JavaScript values. In particular, use the documented get() and set() helpers for database JSON field values.