Extending PocketBase with JavaScript & Go: Custom API Routes and Logic
You’ve seen how PocketBase can simplify backend development with its one-file magic. But what if your project needs more — custom routes, database logic, or deeper control over how things run?
In this follow-up, we’ll dive into how to extend PocketBase with JavaScript (or Go) to create a backend that truly fits your business needs.

Extending PocketBase
The big question is, why would you want to extend PocketBase? Or more so, what's even the meaning of 'extend'? Let's have a look:
English defines the term 'extend' by:
- to make available
- to increase the scope, meaning, or application of
Source: https://www.merriam-webster.com/dictionary/extend
By the literal definition, extending PocketBase means adding the scope, functionality, or amending the existing functionality to act in a particular way.
Why extend PocketBase?
There are a ton of reasons why I would think of extending any software in existence, not just PocketBase. As the docs would put it;
... PocketBase can be used as a framework which enables you to write your own custom app business logic in Go or JavaScript and still have a portable backend at the end.
The essence of extending PocketBase is to customize and fit the functionality of PocketBase into your business context. I think of PocketBase as a canvas that you can tune to fit the design in mind. Here are some context-specific scenarios I would extend PocketBase:
- Adding custom routes: PocketBase creates API routes for record CRUD operations as well as authentication needs. However, you may need to have some routes to handle business needs, like an endpoint to compute reports or bulk import/export data from your database, etc.
- Binding to event hooks and intercepting responses: Pocketbase has several hooks, like app hooks, mailer hooks, real-time hooks, record model hooks, collection model hooks, request hooks, and base model hooks. These hooks allow us to modify the default process by checking user input, formatting data, removing data, etc.
- Register custom console commands: If you like playing with the PocketBase command-line utility, you may need to add some cmd commands to invoke your business logic. For instance, maybe migrate new data after installation or export existing data into JSON or even SQL?
There is quite a lot that can be added to PocketBase; check the docs section on Go and Javascript extending sections. Now that we’ve looked at why you might want to extend PocketBase, let’s talk about how you can do it — and which language might be the better fit for your use case.
Extend with Go or JavaScript?
One of the things to think about beforehand is the language to use to extend PocketBase. As the docs note, you can use PocketBase as a framework in Go or make use of the JavaScript SDK. Each has its own pros and cons. Let's have a look:
Extending with Go
PocketBase is written in Go, so if you have some basic knowledge in Go, you may find this option more applicable. Beware that you shall be building PocketBase, so you have lots of flexibility. For instance, you can add any 3rd party library to add any kind of functionality you need. In contrast, the Go APIs are verbose and may take time to master well.

Extending with JavaScript
In contrast with the Go approach, using JavaScript entails using the standard-built PocketBase binary. This helps you avoid the hassle of building the binary package, which, if you are using a tiny VPS, may take a couple of minutes, depending on the 3rd party packages added. A big plus here is, if you have knowledge in JavaScript, this would be the best place to drop your knowledge.
However, note that PocketBase uses the Goja module for loading and executing JavaScript, which lacks some functionalities unlike Node.js. As such, beware of some inherent limitations like missing concurrent execution using setTimeout
and setInterval
, support for Common JS modules (CJS), and the unique handler scopes for hooks, which make sharing variables outside the handler hard.

Something to keep in mind is that, with the embedded Goja package, browser-specific dependencies like fs, window, etc
are not supported. As such, any 3rd-party modules that also depend on the same will not work.
Despite these limitations, JavaScript is still a powerful and accessible option for extending PocketBase — so let’s walk through what that actually looks like in practice.
How do we extend PocketBase with JavaScript?
Picking up from where we left in our last blog, your PocketBase directory should be structured as below:

Two things before we get started:
- Open the folder in Visual Studio Code so that we can add the scripts. Use any editor of your choosing.
- We'll be launching the pocketbase as a
dev
so that we can see the debug logs as the app starts. This may be helpful to monitor our hooks.
In this section, we’ll explore how to extend PocketBase using JavaScript—covering topics such as adding new routes, custom database interactions, middleware, and hooks.
a. Adding New Routes
One of the primary ways to extend PocketBase is by creating custom API endpoints. Whether you need to aggregate data from various sources, perform specialized computations, or integrate third-party services, adding new routes gives you full control over your API. For example, you can define a new GET endpoint that fetches records from a specific collection:
// Example: Adding a custom GET route to PocketBase
routerAdd('GET', '/org/public-endpoint', (e) => {
// Interact with the internal database, do your
// aggregation and return data if successful
e.json(200, { success: true })
});
// register "POST /org/secret-endpoint" route
// (allowed only for authenticated users)
routerAdd("POST", "/org/secret-endpoint", (e) => {
// do something ...
return e.json(200, { "success": true })
}, $apis.requireAuth())
In this snippet, we add a route /org/public-endpoint
and /org/private-endpoint
allow for interaction with the database layer, with later requiring user log in. This approach encapsulates custom business logic without modifying the core system.

Once your routes are in place, the next natural step is working directly with the database to handle more advanced operations. So, let's take a look.
b. Custom Database Interactions
While PocketBase’s integrated database is designed for speed and simplicity, many applications require more advanced data manipulation. Using the PocketBase JavaScript SDK, you can execute complex queries, apply filters, sort data, and even perform batch updates. Consider the following example:
// register "POST /org/secret-endpoint" route (allowed only for authenticated users)
routerAdd("POST", "/org/secret-endpoint", (e) => {
// Other code here ...
// Lets run in a transaction block
$app.runInTransaction((txApp) => {
// update a record
const record = txApp.findRecordById("articles", "RECORD_ID")
record.set("status", "active")
txApp.save(record) // Update Record
// Create a record
let collection = $app.findCollectionByNameOrId("<some collection>")
let record = new Record(collection)
record.set("title", "Lorem ipsum")
record.set("active", true)
txApp.save(record) // Save Record
// run a custom raw query (doesn't fire event hooks)
txApp.db().newQuery("DELETE FROM articles WHERE status = 'pending'").execute()
})
return e.json(200, { "success": true })
}, $apis.requireAuth())
Here, we are making use of transactions to ensure the changes are only committed if no error is thrown, lest we have a rollback
. We update an existing record, create a new record, and delete another record, all operations that must execute successfully before we commit the changes to the database.
This example demonstrates how you can leverage the SDK to perform refined data operations—tailoring data retrieval to fit your application’s unique needs.
c. Integrating Middlewares
Beyond adding routes and custom database interactions, PocketBase supports middlewares, enabling you to inject logic at critical points in your application workflow. Middlewares can be essential in validating data before proceeding to the next step or even checking authentication and much more. Below is a simple middleware added to the /some/route
route, any logical condition can be done there to decide if code execution should proceed to e.next()
or return back to the client.
// Attach middleware to a single route
// "GET /some/route"
routerAdd("GET", "/some/route", (e) => {
// Execute some db query, ...
return e.string(200, "Hello!")
}, (e) => {
// Do some checks here ...
// Your custome middleware ...
return e.next()
})
Utilizing middleware ensures that your customizations remain modular and centralized, which is especially beneficial for maintaining data integrity and consistency across your application.
PocketBase out of the box offers some middlewares for use. These include:
// Require the request client to be unauthenticated (aka. guest).
$apis.requireGuestOnly()
// Require the request client to be authenticated
// (optionally specify a list of allowed auth collection names, default to any).
$apis.requireAuth(optCollectionNames...)
// Require the request client to be authenticated as superuser
// (this is an alias for $apis.requireAuth("_superusers")).
$apis.requireSuperuserAuth()
// Require the request client to be authenticated as superuser OR
// regular auth record with id matching the specified route parameter (default to "id").
$apis.requireSuperuserOrOwnerAuth(ownerIdParam)
// Changes the global 32MB default request body size limit (set it to 0 for no limit).
// Note that system record routes have dynamic body size limit based on their collection field types.
$apis.bodyLimit(limitBytes)
// Compresses the HTTP response using Gzip compression scheme.
$apis.gzip()
// Instructs the activity logger to log only requests that have failed/returned an error.
$apis.skipSuccessActivityLog()
With your extensions in place — routes, data logic, and middleware — it's important to consider a few best practices to ensure your setup remains maintainable and secure over time.
Best Practices for Extending PocketBase
- Modular Code Organization: Separate your custom routes, hooks, and database logic into distinct modules. This practice not only makes your code more maintainable but also facilitates future scalability.
- Robust Error Handling: Implement comprehensive error handling within your custom endpoints and hooks to ensure that exceptions do not cascade into critical failures.
- Security Considerations: Always validate and sanitize incoming data, particularly in routes that interact directly with your database. This is crucial for preventing common vulnerabilities such as injection attacks.
- Clear Documentation: Maintain thorough documentation for each extension. This aids both current team members and future developers in understanding the purpose and functionality of your customizations.
Conclusion
Whether it’s by adding new routes to expose custom logic, leveraging the SDK for complex database interactions, or incorporating middleware and hooks to manage application flow, these techniques empower you to build a more dynamic and responsive system. Embrace these extension strategies to make PocketBase not just a backend service but a flexible platform that evolves with your project’s needs.
Similar to JavaScript, Go can be utilized in a similar manner to extend the core functionality. Whichever tool you pick, analyze the options and pick what works best for you and the team.
Have any questions, suggestions, or concerns? Please let me know below. Cheers!