PLV8: JavaScript inside Postgres and the V8 build behind it
V8 Engine Javascript Procedural Language add-on for PostgreSQL
At a glance
- What is it?
- PLV8 registers a JavaScript procedural language with PostgreSQL, but the path to a first function runs through a full compile of the V8 engine. That trade suits teams that need server-side JavaScript inside the transaction, and suits nobody who only wanted a JSON helper.
- Who is it for?
- Adopt PLV8 when the work has to run inside the query, against rows the transaction already holds, and when a V8 source build on every compiling host is affordable. Leave it alone for JSON helpers or string munging, where SQL on its own will do.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 16, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A shared library that puts JavaScript inside the database process
PLV8 is a shared library rather than a service, and that choice shapes everything else about it. It registers a procedural language with PostgreSQL so a JavaScript function can be called from SQL while the query runs inside the backend process, with no second server to deploy and no network hop between the function and the rows. The repository calls itself a V8 Engine Javascript Procedural Language add-on for PostgreSQL, and the build is C++: plv8.cc, plv8_type.cc, plv8_func.cc, plv8_param.cc, plv8_allocator.cc and plv8_guc.cc are the six entries listed as SRCS. Reading that split tells you what the extension is made of, because converting values between SQL and JavaScript is isolated in one translation unit, call dispatch lives in another, and runtime options sit in a GUC file of their own. GitHub classifies the licence as Other and points at a LICENSE file, while the top-level entries list COPYRIGHT and Changes instead, so the terms are not readable from the tree without opening the repository page.
The Makefile compiles a bundled V8 before any of the C++
Compiling PLV8 means compiling V8 first, because the Makefile's first target is `all: v8 $(OBJS)` and the v8 target resolves to a static archive named `libv8_libbase.a` inside deps/v8-cmake/build. That engine arrives as a git submodule, and the rule which creates its directory also feeds the local patches to the patch command before cmake runs at all.
cd deps/v8-cmake && mkdir -p build && cd build && cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -Denable-fPIC=ON -DCMAKE_BUILD_TYPE=Release $(V8_CMAKE_ARGS) ../ && make -j $(NUMPROC)Cmake is invoked with position independent code enabled, a policy floor of 3.5 and the optimised build type shown in that line, while a site that needs different cmake flags passes them through V8_CMAKE_ARGS instead of editing the rule. On Linux the job count is counted out of /proc/cpuinfo; on macOS it comes from hw.ncpu through sysctl. Two details here are easy to miss. The library search path for deps/v8-cmake/build is appended to SHLIB_LINK twice, once inside the branch that excludes Windows and once after that branch closes. And the generated header plus the shared object are both declared to depend on the V8 target, with a comment saying this solves a parallel make dependency without explaining why the dependency was broken in the first place.
pg_config on the path decides which Postgres gets targeted
One prerequisite is stated as a hard rule: pg_config has to sit in the path, sharing an installation directory with psql and postgres. The build file offers no way to point somewhere else. PG_CONFIG is set to the bare name pg_config and PGXS is resolved by asking that binary where its build system lives, so whichever PostgreSQL the shell reaches first decides the target, even when a different server is the one you intend to run. That matters more here than in most extensions, because PLV8 links through PGXS into PostgreSQL internals rather than against a narrow public library, and a mismatch surfaces as a build failure or as a module the server refuses to load. On a host carrying several PostgreSQL versions there is no configure flag to choose between them, only the path. The README also warns that build requirements changed again as of PLV8 3.2, which is the trap waiting for anyone following a guide written against the previous package set.
From make to a logging call in four steps
On Debian and Ubuntu the README lists what has to be installed first, and the C++ standard library package carries a version note of its own.
libtinfo5
build-essential
pkg-config
libstdc++-12-dev
cmake
gitEL8 and EL9 want the development tools group plus cmake and git, and macOS wants XCode with its command line tools plus cmake. With those in place and pg_config resolvable, the compile is a single command, which also builds V8 before it touches PLV8:
makeThe install step copies the built module into the PostgreSQL tree so the extension registers itself as available:
make installPostgres ships its own test runner and PLV8 ships expected output files under expected/, so verification happens inside a database rather than through a separate harness:
make installcheckThe extension is then created in a database and the language is callable from SQL:
=# CREATE EXTENSION plv8;Watch what the only working example in the documentation actually is. It never declares a function and never returns a value, it calls a logging helper inside an anonymous block:
=# DO $$ plv8.elog(NOTICE, "hello there!"); $$ LANGUAGE plv8;So the surface a newcomer can copy is a DO block with elog at NOTICE level, not a created function returning a value, and the rest of the language is only reachable through the linked manual at https://plv8.github.io/. General questions go to the Discord server named in the README, which asks people to search there before opening an issue.
Windows is an empty branch while macOS gets a patch
Platform handling in the Makefile is uneven in a way the requirements list does not announce. The Windows branch contains a single comment and nothing else, so no part of the build file adapts the engine recipe for Windows, and the V8 rule still creates and enters its build directory with POSIX style commands. macOS gets real treatment: the linker switches to libc++, an extra patch joins the patch list for that platform, and the job count is read from hw.ncpu. That extra patch is the documentation gap worth flagging, because the MacOS requirements name two items, XCode and cmake, and neither line mentions that a local patch is applied to the submodule on the way past. Anyone who vendors deps/v8-cmake into an image build, or who mirrors the package list into provisioning code, ends up with a host that differs from the one this build file expects, and the difference only shows up as a patch failing to apply. Docker support exists as a pointer to platforms/Docker/README.md, but no Dockerfile appears among the top-level entries.
A GCC 14 patch that upstream fixed in V8 11.6
One patch carries its own explanation inside the build file, which makes it the closest thing to a compiler support statement anywhere in the repository.
PATCH_V8 := patches/v8-cmake/gcc14-stats-collector.patchThe comment above it says that GCC 14 and libstdc++ 14 no longer provide std::remove transitively, so the stats collector header has to include <algorithm> itself, a break that was fixed upstream in V8 11.6. Two consequences follow for anyone building this. The patch set is bound to the submodule revision it was written against, and the rule that applies the patches runs once, at the moment the submodule directory first appears, so a tree that is already checked out is not patched a second time. The comment is also the only statement about toolchains in the documentation: PLV8 3.2.5 compiles with -std=c++17, and the README pins libstdc++-12-dev for Debian and Ubuntu while accepting version 10 in its place, without saying how that choice relates to the compiler break the patch fixes.
Version strings sit inside library and SQL filenames
The version is stamped into file names rather than living in a single metadata file. MODULE_big becomes plv8-$(PLV8_VERSION), which for the current tree is 3.2.5, and PLV8_DATA names plv8.control together with a generated SQL script carrying the same number, so the branch called r3.2 and the constant PLV8_VERSION = 3.2.5 have to move together by hand, and nothing in the build checks that they do. The top-level entries carry generate_upgrade.sh, plv8.sql.common, plv8.control.common and a sql/ directory, which is the shape of a generated upgrade path, yet the README does not document the steps for a database where the extension is already created and says nothing about a rollback. The last push was on 2026-09-21 and the repository has no GitHub releases, so there is no tagged artefact to pin a deployment to.
For a team planning a version jump, that gap is the real cost. Compiling is three documented commands, upgrading is undocumented, and the files on disk change name with the version, so anything that pins paths instead of extension names breaks without a warning.
Choosing a path that needs no V8 build at all
What PLV8 asks for is front loaded, and the bill does not shrink on a smaller machine: a C++ toolchain, cmake, a submodule checkout, a complete V8 compile and a matching PostgreSQL install all have to line up before one JavaScript function runs, and that price is paid per build host rather than per call. The alternative approach is to keep the logic where the tooling already is. Writing the work as SQL expressions adds no second engine and no source build at all, and moving it into the application that already issues the query takes the V8 compile out of the database entirely, at the price of another round trip and of code that runs outside the transaction reading the rows. Neither route is worse in principle. What decides between them is whether the work must see uncommitted rows, hold locks or run once per row inside a query, because that is what rules the application layer out and leaves PLV8 competing with SQL written in the database itself.
Editorial conclusion
Adopt PLV8 when the work has to run inside the query, against rows the transaction already holds, and when a V8 source build on every compiling host is affordable. Leave it alone for JSON helpers or string munging, where SQL on its own will do. Before either, run make installcheck against the PostgreSQL version you actually start, confirm that pg_config on that host points at that same install, and read what generate_upgrade.sh does, because the upgrade path for a database that already has the extension is not documented.
Frequently asked questions
Do I have to install a JavaScript runtime separately for PLV8?
No. V8 arrives as a git submodule under deps/v8-cmake and is compiled by the build itself into libv8_libbase.a, which the extension then links against. Nothing in the documentation asks for a system JavaScript runtime alongside it.
What has to be installed on Debian or Ubuntu before building PLV8?
The requirement list for Ubuntu and Debian names libtinfo5, build-essential, pkg-config, libstdc++-12-dev, cmake and git, with a note that version 10 of the C++ development package can be used instead of 12. EL8 and EL9 instead want the development tools group, cmake and git.
How do I upgrade a database that already has PLV8 installed?
The tree holds generate_upgrade.sh, plv8.sql.common, plv8.control.common and a sql/ directory, and the installed SQL file name carries the version number, but the README does not document the upgrade steps for a database where the extension is already created. It documents no rollback procedure either.
Does PLV8 build on Windows?
The Windows branch of the Makefile holds one comment and no build steps, and nothing in it adapts the V8 recipe or the job count. Only Linux and macOS have platform handling in that file, and Docker support is referenced as a separate README under platforms.
How can I check that a PLV8 build actually works?
Run make installcheck, which uses the Postgres test runner together with the expected output files kept in expected/. A quicker manual check is a DO block calling plv8.elog at NOTICE level, run in a database where CREATE EXTENSION plv8 has already succeeded.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/plv8-plv8)