Why FlyQL?

Or: what happens when you need users to filter data, and every existing tool is either too dangerous or too heavy.

The problem

You're building an application that shows data. Users need to slice it — filter by status, search by field, combine conditions. This is not a hard requirement to understand. It is surprisingly hard to implement safely.

The options most developers reach for first are either too powerful, too fragile, or too specific to one runtime. FlyQL started as an attempt to find the narrow thing that fits exactly: expressive enough to be useful, constrained enough to be safe, and portable enough to use across a stack.

Why not raw SQL?

The obvious answer is injection. But parameterized queries solve injection — the subtler problem is that parameterized queries don't solve structure. If users can choose which columns to filter on and which operators to use, you still have to validate every column name, every operator, and every type coercion. Get any of it wrong and you're either leaking schema or crashing on a type mismatch.

You also can't run SQL in-memory. If you want the same filter logic to work client-side, in a streaming pipeline, or in a test without a database, SQL gets you nothing.

Why not a filter UI?

Drag-and-drop filter builders are reasonable for simple cases. They become painful when you have more than a handful of columns, when you need boolean logic beyond AND, when columns are nested inside JSON, or when you need to hand the filter off to a backend written in a different language than the frontend.

A text query language also gives power users something to copy, save, share, and put in version control. A filter UI doesn't. For Telescope — the log explorer FlyQL was built for — this mattered a lot. Engineers wanted to paste a query into a URL and share it.

Why not CEL, goexpr, or similar?

Expression languages like CEL (Common Expression Language) or goexpr are capable — too capable. They're Turing-complete or close to it. They can call functions, access globals, and do things no user query should do. Every capability you expose is also a capability an attacker can probe.

FlyQL's grammar is intentionally minimal. It can compare a field to a value. It can combine conditions with AND, OR, and NOT. It can match with =, !=, <, >, <=, >=, ~, like, in, and a few others. That's it. There are no functions. No subexpressions. No way to escape the sandbox because the sandbox is the entire language.

The limitations are the point.

Why not Monaco or CodeMirror?

For a query input field that's a small part of a larger admin panel or dashboard, Monaco is several megabytes of JavaScript that needs to be loaded, configured, and themed. CodeMirror is lighter but still requires integrating a separate plugin ecosystem for every feature.

FlyQL ships a Vue 3 editor component built specifically for FlyQL syntax. It's small, has zero heavy dependencies, knows about your column schema, and highlights errors inline. There's no LSP to run, no grammar file to configure, no plugin ecosystem to wire up — the tokenizer is built into the library. You import one component. That's it.

Cross-language parity

The hardest part of building FlyQL wasn't the parser. It was making the same query mean the same thing in Go, Python, and JavaScript. Type coercions, operator semantics, how regex matching works, what happens with null values — all of these need to be identical across runtimes or the system becomes unpredictable.

If your backend is Go and your frontend is JavaScript, the FlyQL query a user types in the editor generates the same SQL when sent to the server. You can also evaluate the same query client-side against a local dataset and get the same rows back. This is only possible if the implementations are tested against the same test cases — which is what the E2E test suite verifies.

Where it came from

FlyQL was built for Telescope, an open-source log viewer for ClickHouse, Kubernetes, Docker, and more. Telescope needed a way for users to write filter queries against log data. The columns are dynamic, the data is large, and the interface spans a Python backend and a Vue frontend.

After evaluating the existing options, none fit well enough. So I built a language, a parser, an editor, and a generator — and then extracted it into a standalone library that any application can use.

If you're building something that shows data to users and needs a filter input — whether it's a log viewer, an admin panel, a metrics dashboard, or an analytics tool — FlyQL is probably the smallest, safest way to get there.