Natural language in. Safe, parameterized queries out.

Natural language to SQL, MongoDB and Elasticsearch, without letting an LLM write the query. QueryForge is an open-source library for Go, Python and Java: a model plans the query, plain code compiles it, and your database stays under your control.

Example· orders config · real generator output
>cancelled orders over 200 dollars, newest first
1 · Validated plan (abridged)
{
  "entity": "Order",
  "filter": { "AND": [
    { "field": "status", "operator": "equals",
      "value": "CANCELLED" },
    { "field": "amount", "operator": "gt",
      "value": 200 }
  ]},
  "sort": [{ "field": "createdAt", "dir": "DESC" }],
  "limit": 50
}
2 · Compiled by code, no model involved
SELECT status, refunded, created_at, amount, tags, customer_name
FROM orders
WHERE (status = $1 AND amount > $2)
ORDER BY created_at DESC
LIMIT 50

args: ["CANCELLED", 200]

Notice what is missing. The config marks internalNote as not returnable, so it never appears in the query’s field list. Values travel as bound parameters, not as text in the statement.

Built for your stack

Works with the databases and models you already use.

DatabasesCompile one plan to any of these
  • PostgreSQL
  • MySQL
  • MongoDB
  • Elasticsearch
  • OpenSearch
Model providersSwitching is a config change
  • OpenAI
  • Anthropic
  • Amazon Bedrock
  • Google Gemini
  • + Mistral, Groq, Ollama and any OpenAI-compatible endpoint
How it works

The model plans. Code compiles.

Most text-to-SQL tools let the model write SQL and then try to check it. QueryForge splits the job, so everything that must be guaranteed lives in code you can test offline.

01

A user asks

A plain sentence, such as “cancelled orders over 200 dollars”. Your application also passes the user’s scope, like a tenant ID.

02

The model fills in a plan

A typed query structure limited to the fields and operators in your config. The validator rejects anything invented before it goes further.

03

Code compiles the query

Generators turn the validated plan into parameterized SQL, MongoDB or Elasticsearch queries. No AI and no network in this step.

Under the hood

  1. User sentence“cancelled orders over 200”
  2. AIModel fills a planLimited to your config’s fields
  3. CodeValidatorRejects anything invented
  4. CodeYour app adds scopeTenant, user, role
  5. CodeGeneratorsSQL, MongoDB, Elasticsearch
Use case

Replace a page full of filters with one input.

Your users already know what they want to find. Instead of making them operate a form of dropdowns and ranges, let them type it. QueryForge turns the sentence into the same validated filters the form used to collect.

Orders · FiltersBefore
Seven controls to learn, set and combine correctly.
OrdersAfter
Understood as
WHERE
OrderCustomerStatusAmountPlaced

Illustrative sample data. The WHERE clauses are real QueryForge output for the example orders config; in your app the model produces the plan from the user’s sentence. Scope filters such as the tenant are added on top by your application.

Security model

How QueryForge prevents LLM SQL injection and prompt injection.

There is no model-written SQL to inject into. These are the guarantees, and what each one means in practice.

No model-written queries

The model fills a typed plan limited to your config. Plain code produces the query text.

Values are bound parameters

User text becomes $1, $2. Only identifiers from your config reach the statement.

Invented fields are rejected

A field that is not in your config fails validation before anything compiles, with “did you mean” suggestions.

Writes cannot be expressed

The plan has no mutation node, so every output is a SELECT or a find.

Hidden fields stay hidden

A field marked not returnable is excluded from the default projection as well as from explicit selects.

Impossible requests are refused

A typed refusal, not a plausible query built on a lookalike field.

What it does not replace. Keep using a read-only database role, and take scope values from your authenticated session, not from user input. The user’s sentence and your config’s field vocabulary are sent to the model provider you choose. Read the full design.

Compare

Same question. Different guarantees.

Four requests that trip up text-to-SQL when the model writes the query. On the right is what QueryForge actually produced, from the real engine, using the example orders config.

A user typesdelete all cancelled orders
Model writes the SQLIllustrative: what an unguarded model can emit
DELETE FROM orders
WHERE status = 'CANCELLED';

A write statement reaches your database, unless a read-only role or a parser happens to block it.

QueryForgeReal output from the QueryForge engine
SELECT status, refunded, created_at, amount, tags, customer_name
FROM orders WHERE status = $1 LIMIT 50
-- args: ["CANCELLED"]

The plan has no way to say delete. The worst possible output is a read.

The left side is illustrative: it shows what an unguarded model can emit, not a claim about any specific product. Whether it happens depends on the model, the prompt and the safeguards around it.

The full comparison

Model writes SQL (typical)QueryForge
Who writes the query textThe modelPlain code, from a validated plan
How user values are passedDepends on the prompt and any checks addedAlways bound parameters
Invented columns or tablesPossible; caught late or not at allRejected by the validator before compiling
Writes and destructive statementsPossible unless blocked by a role or parserCannot be expressed
Tenant filterLeft to the model to includeAdded by your application, outside the model
Hidden columnsThe model can select them unless filtered afterwardsExcluded from the projection
TestingProbabilistic; needs evaluation setsThe compile step is deterministic and testable offline

“Typical” describes the common approach of asking a model for SQL. Individual products vary and add their own safeguards. Read why this design works.

Governance

Your application decides what users can see. Not the model.

Tenant and user filters are added by your application after the model answers, so the model never learns the scope field exists. Turn on policy.requiredScope and a query that arrives without the tenant filter is refused instead of running unscoped.

User sentence“show my cancelled orders”
Model produces a validated planKnows your fields. Never sees the tenant filter.
Your application adds the scopetenant_id AND-ed on after the model answers
Generator compiles a parameterized queryYour code runs it with your own database driver
For developers

Running in a few lines.

You need a config file that describes your data (the config builder makes one in your browser) and a model API key in your environment. QueryForge returns the query and its arguments, and your code runs them.

Go
go get github.com/awsaman-ai/queryforge
Python
pip install queryforge-ai
Java

Maven io.github.awsaman-ai:queryforge plus a platform classifier. Install guide

Minimal example (Go)
cfg, _ := qf.LoadConfig("orders.config.json")
engine := qf.New(cfg)

res, _ := engine.Translate(ctx,
    "cancelled orders over 200 dollars", "sql", nil)

fmt.Println(res.Query.SQL)   // SELECT … WHERE (status = $1 AND amount > $2) …
fmt.Println(res.Query.Args)  // [CANCELLED 200]
OpenAI-compatibleAnthropicAmazon BedrockGeminiMistralGroqOllama (local)
Guides

Go deeper.

FAQ

Common questions.

What is text-to-SQL?

Text-to-SQL, also called natural language to SQL, turns a plain-language question into a database query. QueryForge applies the idea to SQL, MongoDB and Elasticsearch, and keeps the model out of the query text: it fills in a validated plan, and code generates the parameterized query.

How do you prevent SQL injection in text-to-SQL?

Do not let the model write the query text. QueryForge has the model fill in a typed plan limited to your config, validates it, and compiles it with code. User values travel as bound parameters, writes cannot be expressed, and tenant scope is added outside the model. Keep a read-only database role as well.

Is text-to-SQL a replacement for an ORM or query builder?

No, they solve different problems. An ORM or query builder is how your developers write queries; text-to-SQL is how your end users ask questions. QueryForge sits in front of your data layer and returns a query and its arguments, which you run with the driver or ORM you already use.

Does QueryForge connect to my database?

No. It returns the compiled query and its bound parameters, and your application runs them with its own database driver. The compile step is plain code with no network access.

Which databases are supported?

PostgreSQL, MySQL, MongoDB, Elasticsearch and OpenSearch. One request can compile to several of them from the same plan.

Which LLMs can it use?

Any OpenAI-compatible endpoint, Anthropic's native Messages API and Amazon Bedrock. Built-in presets include OpenAI, Anthropic, Gemini, Mistral, Groq and Ollama for local models.

Which languages are supported?

Go, Python and Java SDKs, plus an MCP server for AI assistants.

Is it safe for multi-tenant applications?

Scope filters such as tenant and user IDs are added by your application after the model answers, so the model never sees or decides them. With policy.requiredScope set, a query that arrives without the tenant filter is refused.

Is QueryForge open source?

Yes. It is released under the Apache-2.0 license and developed on GitHub.

Open source · Apache-2.0 · maintained by Amtry · Releases · Issues · Security policy · License

Let your users ask. Keep control of your data.

Try it on a sample config in the playground, or build a config for your own data.