Natural language → validated query

Replace the whole filter panel with one sentence.

Your users stop translating what they want into dropdowns and date pickers — they just say it. QueryForge turns that sentence into a typed query plan your config constrains, then compiles it to Postgres, MySQL or MongoDB with deterministic Go. The model never writes the query.

CI passing Go 1.25+ Apache 2.0 No DB connection, ever
queryforge translate
> {{ heroSentenceTyped }}
QUERY AST
{{ line }}
SQL
{{ heroSql }}
01 · The problem

Why not just ask an LLM for SQL?

Because you can't check what comes back. Ask a model for SQL directly and it can invent a column, widen a filter, or hand you something only probably right. QueryForge never lets the model near a query string — it fills in a typed form, and ordinary Go does everything after that.

{{ row.num }}
{{ row.q }}

{{ row.a }}

02 · How it works

One typed step between the sentence and the query.

The query plan has no node type that can write, so read-only isn't a rule someone has to remember — there's no way to express a mutation. Everything after the model is pure, testable Go.

{{ stage.title }}
{{ stage.tag }}

{{ stage.desc }}

03 · One brain, many backends

The same query plan compiles anywhere.

Postgres, MySQL and MongoDB ship today. A new backend is a generator, not a rewrite — the model and validator never change.

{{ activeDb.code }}
{{ activeDb.note }}
04 · Live demo

Two ways to get the same rows.

QueryForge isn't a UI — it's the logic underneath one. Flip the toggle: the panel on the left is what most apps ship today; the box on the right is all the UI QueryForge needs, because the interesting part happens in code you can read below it.

For reference · the orders table both panels query
idstatusamountcreated_attags
1001DELIVERED6122026-07-28premium
1002CANCELLED2402026-06-30
1003DELIVERED582026-07-30express
1004PENDING9352026-08-01premium, express
1005CANCELLED5602026-07-14
Logic
Field
Operator
Value
{{ row.connectorLabel }}

{{ filterRowCount }} filter rows, 7 possible fields, 8 operators — a dropdown, an input and a remove button to wire up for each one. Add a field to your schema and this panel needs new frontend code before anyone can filter on it.

{{ p.label }}

That's the whole UI. Everything below is what the sentence actually does — the part that makes this a backend, not a widget.

{{ qfStatusLabel }}
1 · Sentence received
{{ qfText }}
2 · Query AST
{{ line }}
3 · Validated against config
{{ chk }}
4 · Compiled query
{{ qfCompiled }}

This exact sentence isn't wired into the canned demo — pick one of the presets above. In the real engine, any phrasing goes to the model next; the AST → validator → generator pipeline below runs exactly the same either way.

05 · Multi-tenancy

Scope filters: your own rules on every query.

Subscription, user, enterprise — values the caller already knows, never the user's to phrase. They're AND-ed onto the query root after the model answers, so the model never even learns the field exists.

{{ fact }}
engine.Translate(ctx, "delivered orders over 500 dollars", "sql", qf.Scope{
  "subscriptionId": session.SubscriptionID,
  "userId":         session.UserID,
})
COMPILES TO
WHERE (subscriptionId = $1 AND userId = $2
       AND status = $3 AND amount > $4)
06 · Get started

Same config, four ways to call it.

A config file describing your data and a model API key in the environment. No server, no Docker, no database connection.

{{ activeCodeText }}
INSTALL
{{ activeInstall }}
07 · Model providers

Choosing a model is a config change.

List several and QueryForge tries them in order — a rate limit or outage on one falls through to the next, transparently.

{{ p.name }}
{{ p.note }}
{{ fc.label }}
08 · Observability

Facts out. No logging decisions made for you.

A traced live request measured the deterministic half at 0.5ms against a 1,781ms model call — roughly 99.97% of a translation is one HTTP round trip. That's the one seam Observer reports on.

Tracked
Latency & token counts
Attempt outcomes
Repair attempts
Which model answered
Never logged
The question text
Scope values (tenant/user ids)
The API key
engine.SetObserver(func(ctx context.Context, e qf.Event) {
  switch e.Kind {
  case qf.EventModelCall:
    log.Info("model", "latency", e.Latency,
      "tokens", e.TotalTokens)
  case qf.EventTranslate:
    log.Info("translate", "outcome", e.Outcome,
      "duration", e.Duration)
  }
})