Someone on your team writes every query in PromQL. Someone else only touches Query Builder. Both are “right” — SigNoz genuinely supports both. The problem shows up the moment a panel needs to cross from one person to the other.
The PromQL person writes a query that works first try, hands the panel over so someone can add a having filter through the UI. They switch to Query Builder. It's empty. Nothing carried over. The panel gets rebuilt from zero.
The Query Builder person goes the other way — clicks their way to a working panel, hits a feature the builder doesn’t expose, and has no idea what to type to get the same result in PromQL.
Same tool, same data, two dialects that don’t talk to each other.
So I built Janus, a browser extension that converts between them, in both directions, without leaving the SigNoz tab. Named after the Roman god with two faces, looking both ways at once, felt fitting for something that has to fluently speak two directions of the same thing.
What Janus Actually Does
Two modes, one popup:
PromQL → Query Builder :
Paste a query, or pull one straight off a panel that’s already in PromQL mode. Convert it. Push the result into the panel it came from, or open it fresh in Explorer.
Query Builder → PromQL :
Point Janus at a dashboard panel. It reads that panel’s live query state and converts it. Anything it can’t convert cleanly goes into a Notes section instead of getting quietly guessed at.
That second part mattered more to me than the conversion itself. A tool that guesses silently and a tool that’s honest about its gaps look identical right up until the moment they don’t.
WorkFlow :
┌────────────────────────┐
│ SigNoz │
│ dashboard / explorer │
└───────────┬────────────┘
┌──────────────┴──────────────┐
│ │
- - - - - - - - - - - - - - - - - - - - - - - - - -
| PromQL → Query Builder | | Query Builder → PromQL |
- - - - - - - - - - - - - - - - - - - - - - - - - -
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ Paste PromQL, or load │ │ Panel URL, auto-filled │
│ from a panel URL │ │ from the active tab │
└────────────┬────────────┘ └────────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ Parse and convert │ │ Read live compositeQuery│
│ │ │ from the URL │
└────────────┬────────────┘ └────────────┬─────────────┘
│ │
│ ▼
│ ┌────────────────────────┐
│ │ Convert, show warnings│
│ └───────────┬────────────┘
│ │
▼ ▼
┌────────────────────────┐ ┌─────────────────────────┐
│ Open in Explorer, or │ │ Open in the same panel,│
│ back in the panel │ │ now in PromQL mode │
└────────────┬───────────┘ └─────────────┬───────────┘
│ │
└──────────────┬───────────────┘
│
▼
┌─────────────────────────┐
│ back to SigNoz │
└─────────────────────────┘
Both directions read and write SigNoz’s own compositeQuery URL parameter : no DOM scraping, no reading class names that'll break the moment SigNoz's frontend changes. Janus reads the same state SigNoz's own UI reads.
How I Actually Built It
I didn’t start with a browser extension.
I started with promql2qb, a Go CLI that only did PromQL → Query Builder JSON, no UI, nothing pretty; just a command that printed text.
The reasoning was simple: I wanted to figure out SigNoz's actual internals without also fighting a browser manifest at the same time. It's still the reference implementation for that direction's core logic and Janus ports the same conversion rules to TypeScript rather than reinventing them.
Info: If you need a real SigNoz instance to test any of this against and you’re on Windows like I was, I wrote up the fastest way to get one running — WSL2, Docker Engine, five minutes, no Docker Desktop crash-loop.
That decision paid off fast, because almost everything I assumed going in turned out to be wrong.
Wrong guess #1 — time isn’t time.
SigNoz’s fixed-step aggregation and PromQL’s _over_time functions look like the same idea. I ran the identical query at 60s, 120s, and 300s steps, once through SigNoz's UI, once through the PromQL equivalent and compared the plots.
At 60s and 120s: close enough to pass as interchangeable.
At 300s: SigNoz flattened to almost one value, while PromQL kept the full shape underneath.
Why: SigNoz bins the whole range into fixed buckets. PromQL’s
_over_timeis a sliding window looking backward from each point. They only agree when the step is small relative to how the data moves.
Wrong guess #2 — it passed my own tests, and that’s exactly the problem.
My having conversion produced sum() > 50, no metric name inside the parens. Looked fine. Passed every test I wrote, because I'd written the tests to expect the same wrong thing.
I only found the real shape sum(gen) > 50,by capturing an actual request SigNoz's UI sent and comparing it field by field against what I was generating.
Wrong guess #3 — the most expensive one.
Once I moved from CLI to extension, I wanted Janus to open a converted query directly back into SigNoz. My first URL guess didn’t work. I concluded the path was wrong and rebuilt the whole flow around a different route.
The path was fine the entire time. The JSON I was sending it wasn’t.
SigNoz’s frontend URL state and its query API turned out to be two different envelopes for the same query, not one shape reused twice, which is what I’d assumed. I only caught it by putting a captured URL side by side with the real API request it triggered.
Then a different kind of problem entirely.
Building the reverse direction, Query Builder back to PromQL, meant handling real boolean logic: AND, OR, NOT, IN. Converting that cleanly into PromQL meant parsing that grammar and reducing it to disjunctive normal form, so any boolean combination collapses into one or more AND-only branches — each one mapping to a clean PromQL selector.
Outcomes
Janus does both directions today, live inside SigNoz’s own tabs, tested the whole way through against a real self-hosted instance, not a mocked API. Every JSON shape it produces has a real captured payload it was checked against.
It’s not comprehensive, and I didn’t want it to pretend to be. It covers a defined subset of PromQL: the aggregations, functions, and grouping people actually reach for and the project is upfront, in its own LIMITATIONS.md, about what falls outside that subset.
Proof both directions actually land back in a working SigNoz panel :
What I’d Do Differently
I’d capture real network traffic earlier. All three wrong guesses above cost time for the same reason: I trusted a plausible-looking assumption before checking it against something real. The fix, every time, was identical; stop reasoning about how SigNoz probably works, go watch what it actually sends.
I’d test the reverse direction against messier, real dashboards from the start. Most of my Query Builder test cases were ones I’d built myself clean, on purpose.
Real dashboards, clicked together by different people over months, are messier than anything I’d construct to test against. And messier states are exactly where a converter’s quiet assumptions get found out.
Two directions, one popup.
Most of the real work wasn’t the conversion logic, it was catching my own wrong guesses against something real, instead of trusting the first one that looked right.
If your team is split between PromQL people and Query Builder people, give it a try: github.com/MettaSurendhar/janus · CLI reference implementation: promql2qb







Top comments (0)