jqlang playground: a browser jq editor where the query never leaves the page
A jq playground built with Next.js
At a glance
- What is it?
- The jq playground is a Next.js application that runs jq through a WebAssembly build in the browser, so your JSON and your query are processed locally and only a snippet you choose to share reaches the server. It also exposes a server-side jq API backed by a worker pool sized from the machine's memory.
- Who is it for?
- The jq playground fits anyone who wants to try a jq filter against real JSON without installing jq or uploading the document to a service, and it also works as a reference implementation of jq-wasm under Next.js. It does not fit a team that needs the server-side jq API to be the system of record, since that path runs a memory-bound worker pool with a queue that returns 429 when it overflows.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 22 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two execution paths that behave differently
The playground is a jq playground built with Next.js, and it offers two ways to run a query, with different consequences for your data.
The in-browser path is the default. It integrates the jq-wasm package, a WebAssembly based jq JSON processor for Node.js and browsers with no native dependencies, and all jq queries run directly in the browser. Your JSON input is processed locally as well. The documentation is explicit that all jq queries and the HTTP requests to fetch JSON are processed locally in your browser.
The server-side path is the public jq API, reachable through POST and GET on /api/jq. Those queries execute in a server-side worker pool. Since jq runs in WebAssembly, the pool is memory-bound, and its size defaults to a value derived from the machine's available RAM, for example 2 workers on a 512 MB instance. Two environment variables control it: JQ_POOL_MAX_THREADS sets the maximum concurrent jq worker threads, and JQ_POOL_MAX_QUEUE sets how many requests may wait for a free worker, defaulting to twenty times the thread count, so 40 on a 512 MB instance. Past the queue limit the API returns 429.
That distinction matters in practice. In-browser execution is unaffected by the pool settings, so a large document or a slow filter only affects the machine you are sitting at, while the API path inherits the memory pressure of the server.
Sharing is opt-in and only the query travels
The privacy story rests on a single distinction: what is processed locally and what is sent to the server.
If you share your jq query, a unique URL is generated on the server. Other people can open that shared snippet, and the query still runs locally in their browser. So the object that leaves your machine is the query text, not the document you pasted into the input box. The documentation states that snippets are sent to the server only if you choose to share them.
That design has a consequence worth naming. A shared snippet is a URL containing a query that anyone can open, and the query text may itself be sensitive if it embeds values from the document, such as a filter on a specific account identifier. The playground does not describe any redaction of the query before storage, so treat a shared snippet as public.
Storage for shared snippets is PostgreSQL, and the application uses Prisma as the database layer, with a @prisma/adapter-pg driver adapter and the schema and migration files in a prisma directory. The compose file wires this up for development with a Postgres 16 service named pg, with a healthcheck running pg_isready every five seconds, and the app service waits for that health condition before it starts. The DATABASE_URL environment variable is the only one the README calls required at runtime.
Running it locally in two ways
The prerequisites are Node.js, with 14.x or above recommended, npm or yarn, and PostgreSQL for the shared snippets. The repository has both a Dockerfile and a Dockerfile.dev plus a compose.yaml, and the development path is a single command:
docker compose upThat brings up the app in development mode with hot reload and the local PostgreSQL database, and you then open http://localhost:3000. The compose file builds from Dockerfile.dev, sets DATABASE_URL to a postgres URL pointing at the pg service, and runs a migration followed by the dev server as its command. The source tree is mounted as a volume with node_modules and the .next directory overridden, so the container does not shadow your local dependencies.
A production build is two npm scripts:
npm run build
npm run startBoth are also reachable at http://localhost:3000 in production mode. The build script is not just a Next.js build, it first runs the OpenAPI generation step, because the package manifest defines build as running the openapi script and then next build, and the openapi script itself runs next-openapi-gen generate followed by a script that adds OpenAPI examples. The generated contract is checked in as next.openapi.json and configured through next-openapi-gen.config.ts.
The clone step is the usual one:
git clone https://github.com/jqlang/playground
cd playgroundThe package manifest tells you what the app is made of
The dependency list in package.json is a good map of the implementation. The jq engine is jq-wasm at version 3.0.0-jq-1.8.2, which pins both the wrapper release and the jq version it embeds. The worker pool for the server-side API is piscina, and rate-limiter-flexible handles request limiting.
The editor is Monaco, through @monaco-editor/react, with monaco-editor as a dev dependency and a scripts/copy-monaco.sh step that runs after install to copy it into place. The UI is React 19 with MUI 9 and Emotion, plus MUI Lab and the icons package. Other pieces worth noting are ajv for JSON Schema validation, zod for schema checks in the TypeScript code, uuid for identifiers, @scalar/api-reference-react for rendering the OpenAPI reference, and @sentry/nextjs with @sentry/node for error reporting.
The build uses the webpack flag explicitly on both next dev and next build, and the container increases the heap for the build step with NODE_OPTIONS set to --max-old-space-size=4096, with the comment explaining that the Scalar API reference dependency is large.
There is a security-adjacent detail in the overrides block. The manifest pins minimatch to 10.2.1 or newer, dompurify to 3.4.11, postcss to 8.5.10 and @hono/node-server to 1.19.13 or newer. Overriding transitive dependencies like that is a deliberate way to pull in patched versions without waiting for every parent package to release, and it is worth knowing that the manifest does it rather than relying on the lockfile alone.
Deployment shape and the Prisma CLI wrinkle
The production Dockerfile is written for a container platform, with a fly.toml at the top of the repository and a fly_launch_runtime label naming Next.js and Prisma as the runtime. The image is a multi stage build on node:24-slim, with the node version read from a .node-version file and passed as a build argument.
The build stage installs the native toolchain needed to compile modules, including build-essential, node-gyp, openssl, pkg-config and python-is-python3, then installs dependencies with npm ci including dev dependencies, copies the application, and runs the build. A separate throwaway stage installs a self-contained Prisma CLI with its full production dependency closure, and the comment in the Dockerfile explains why: Prisma 7's @prisma/config pulls in hoisted dependencies that a cherry-pick of node_modules/prisma and @prisma misses, which breaks prisma migrate deploy with a Cannot find module 'effect' error. The fix is a dedicated install that resolves the complete tree, pinned to the already installed version so the client and CLI do not drift.
That is a fair amount of scaffolding for a playground, and it reflects what the app has become: a Next.js application with a database, an API, generated OpenAPI documentation and error reporting, rather than a static page with a jq editor bolted on.
Where the playground is the wrong choice
The first limitation is that this is a client-side tool with a server attached, and the server part is for sharing. If your requirement is to run jq over data you cannot paste into a browser tab, the in-browser path does not help and the API is an undocumented-by-contract alternative to installing jq.
The second is that the server-side API is memory-bound by design. A WebAssembly jq instance in a worker thread is not a lightweight process, which is why the pool size is derived from RAM rather than from CPU count, and why the queue has a hard limit that returns 429. On a small instance that means the API degrades to two concurrent queries. If you want throughput, you want a native jq binary and a process pool, not this.
The third is the sharing model, which is fine for a query tutorial and wrong for a team workflow. A shared snippet is stored in PostgreSQL and addressed by URL, with no mention of access control, expiry or listing, so an organisation cannot use shared links as a curated set of saved filters without taking on the storage and the privacy considerations itself.
There is also a smaller point about the stack. The manifest is pinned to a very recent set of versions, Next 16, React 19, MUI 9, Prisma 7 and TypeScript 6, and the Dockerfile has a known sharp edge around Prisma's hoisted dependencies that the author had to work around in a dedicated install stage. A project that forks this for its own use inherits those version choices and that workaround.
Against a plain local jq install, the comparison is straightforward. jq on your machine is faster, scriptable and has no browser involved. The playground earns its place when you want to paste a document and iterate on a filter with visible results immediately, or when you want to send someone a link to the exact query rather than a screenshot of your terminal.
Editorial conclusion
The jq playground fits anyone who wants to try a jq filter against real JSON without installing jq or uploading the document to a service, and it also works as a reference implementation of jq-wasm under Next.js. It does not fit a team that needs the server-side jq API to be the system of record, since that path runs a memory-bound worker pool with a queue that returns 429 when it overflows. Before deploying it, set DATABASE_URL for shared snippets, leave JQ_POOL_MAX_THREADS at its memory-derived default unless your instance size is unusual, and remember that sharing is the only thing that sends anything to the server.
Frequently asked questions
Does the jq playground send my JSON data to a server?
No. Both your JSON input and your jq queries are processed locally in your browser, and the HTTP requests that fetch JSON are local as well. Only a query you choose to share is sent to the server, where a unique URL is generated so others can open it and run it in their own browser.
How does the playground run jq without installing it?
It integrates the jq-wasm package, a WebAssembly based jq JSON processor for Node.js and browsers with no native dependencies, at version 3.0.0-jq-1.8.2. All queries run directly in the browser through that build.
How do I run the jq playground locally?
You need Node.js, npm or yarn, and PostgreSQL. `docker compose up` starts the app in development mode with hot reload and a local Postgres 16 database, then you open http://localhost:3000. For a production build, run `npm run build` followed by `npm run start`.
What do JQ_POOL_MAX_THREADS and JQ_POOL_MAX_QUEUE control?
They size the server-side worker pool used by the /api/jq endpoint. The thread count defaults to a value derived from the machine's available RAM, for example 2 workers on a 512 MB instance, and the queue limit defaults to twenty times that, so 40, with the API returning 429 past it. In-browser execution is unaffected by both.
Where are shared snippets stored?
In PostgreSQL, accessed through Prisma with the adapter-pg driver adapter. The compose file runs a Postgres 16 service with a healthcheck, and DATABASE_URL is the only environment variable the README calls required at runtime.
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/jqlang-playground)Was this useful?