---
name: writing-taxiql
description: Writes correct, idiomatic TaxiQL queries against a Taxi schema — fetching data with find/stream, supplying inputs with given, projecting and enriching results, filtering, expressions, stdlib functions, mutations with call, and publishing queries as endpoints. Use when asked to write, fix, explain or review a TaxiQL query, or when working with .taxi / .taxiql files that contain find, stream, given or call statements.
---

# Writing TaxiQL

TaxiQL is the query language for [Taxi](https://taxilang.org). It is **semantic and type-driven**: you describe the
data you want, in terms of the semantic types declared in a Taxi schema, and the query engine (e.g. Orbital) works out
which services to call, and in what order, to produce it.

This file is the core guide. Read it fully before writing a query. For anything beyond the basics, read the relevant
file under `references/` (see [Reference guides](#reference-guides)) — they contain detailed rules, correct / incorrect
examples, and verified test cases.

> Examples in this skill use illustrative schemas. They are **not** the schema you are working with. Only use the
> types, models, services and operations that are actually declared in the user's Taxi project.

## Workflow

1. **Read the schema first.** Find the models, semantic types and service operations that are relevant. Note which
   operations exist, what they take as inputs, and what they return — this decides how the query must be written.
2. **Pick a query root** that an operation can actually return (see [Choosing a query root](#choosing-a-query-root)).
3. **Supply inputs** with `given` or a `find` constraint, depending on the operation's signature.
4. **Project** the result into the shape the user wants, letting discovery enrich fields from other services.
5. **Validate.** If you have the `taxi` CLI, an Orbital instance, or the playground (https://playground.taxilang.org)
   available, compile / run the query and fix any errors before presenting it.

Write one query that satisfies the whole request. Don't ask the user for endpoint names, operation names or field
names when the schema already contains enough information to proceed.

## Core query forms

Fetch data with `find`:

```taxi
find { Customer[] }
find { Customer }
```

Subscribe to a stream of events with `stream`:

```taxi
stream { OrderEvent }
```

Transform or enrich results with a projection:

```taxi
find { Order[] } as {
    id: OrderId
    customerName: CustomerName
}[]
```

TaxiQL can discover values not present directly on the source object. If the current scope contains types needed by
another operation, the engine may call that operation automatically to populate the requested type.

Use only types and operations declared in the schema. Never invent schema names. Import fully-qualified types at the
top of the query and use the short name in the query body:

```taxi
import com.acme.orders.Order
import com.acme.orders.OrderId

find { Order[] } as {
    id: OrderId
}[]
```

## Supplying values and filtering

There are three different mechanisms. Choose according to the **operation that returns the target data**.

### Operation has an output contract

Use a `find` constraint:

```taxi
// operation getProduct(ProductSku): Product(...)
find { Product(ProductSku == "ABC-123") }
```

### Operation requires an input but has no output contract

Supply the value using `given`:

```taxi
// operation getProduct(ProductSku): Product
given { ProductSku = "ABC-123" }
find { Product }
```

`given` makes data available to the query and to service calls. It does **NOT** filter results.

For operations returning a collection which require an input, supply the input with `given`:

```taxi
// operation getOrders(CustomerId): Order[]
given { CustomerId = "C42" }
find { Order[] }
```

### No operation can accept the filtering value

Fetch a returnable collection and filter it locally:

```taxi
find {
    Order[].filter((OrderStatus) -> OrderStatus == "pending")
}
```

`.filter()` belongs **inside** the `find {}` braces.

Always preserve user-supplied literal values exactly, including case.

More detail: `references/query.md` (given vs constraints) and `references/filtering.md`.

## Choosing a query root

The type at the root of `find` must actually be obtainable.

`find { X[] }` requires a source capable of returning `X` or `X[]`. Do not root a query at a type that can only be
obtained through enrichment.

Instead, root at obtainable data and project the desired related data:

```taxi
find { Order[] } as {
    orderId: OrderId
    customerName: CustomerName
}[]
```

## Discovery and enrichment

During a projection, TaxiQL resolves requested values from:

1. the current source object;
2. data already in query context;
3. service calls that can produce the missing type.

Therefore, do not manually orchestrate service calls when semantic discovery can join the data.

```taxi
find { Purchase[] } as {
    amount: Money
    customerName: CustomerName
}[]
```

If `Purchase` contains `CustomerId`, and an operation accepts `CustomerId` and returns a model containing
`CustomerName`, the engine performs that enrichment automatically.

`@Id` is a strict discovery restriction: when a model has an `@Id`, only operations accepting that identifier type may
be used to fetch that model.

More detail: `references/discovery.md`.

## Projections

`[]` after a projection means iteration:

```taxi
find { Film[] } as {
    title: FilmTitle
}[]
```

Important projection semantics:

```text
A[] as B[]  → iterate A, producing one B per A
A as B      → transform one object
A[] as B    → aggregate/reduce collection to one result
A as B[]    → transform one object into a collection; does NOT iterate
```

Projecting a stream also requires the trailing `[]` — each event is transformed individually:

```taxi
stream { OrderEvent } as {
    id: OrderId
    status: OrderStatus
}[]
```

Omitting the `[]` creates an aggregating projection, which is rejected on streams.

For an aggregate or single result over a collection, omit the trailing `[]`:

```taxi
find { Order[] } as {
    total: Decimal = OrderAmount.sum()
}
```

When returning a parent together with a child collection, use a nested projection:

```taxi
find { Order } as {
    id: OrderId
    lines: OrderLine[] as {
        sku: ProductSku
        quantity: Quantity
    }[]
}
```

Do not use an array scope tuple expecting it to iterate.

Use a named scope only when field-name access is needed:

```taxi
find { Customer[] } as (customer: Customer) -> {
    name: String = upperCase(customer.name)
}[]
```

Prefer semantic type access where possible.

More detail: `references/projections.md` and `references/expressions.md`.

## Writes

Write operations (`write operation` in the schema) are **never** invoked implicitly. Use an explicit `call`:

```taxi
given {
    customer: Customer = { ... }
}
call CustomerService::saveCustomer
```

Data may also be found and transformed before the call:

```taxi
find { Customer[] } as CustomerUpdate[]
call CustomerService::saveCustomers
```

More detail: `references/mutations.md`.

## Compute-only queries

When there is no source collection to fetch, put computed fields directly inside `find`:

```taxi
given { age: Int = 20 }

find {
    allowed: Boolean = age >= 18
}
```

Do not write an empty `find { } as { ... }`.

## Reference guides

Read the relevant guide before writing anything beyond a simple `find` + projection. Each contains rules, ❌ incorrect
and ✅ correct examples, and `json` test cases (`schema`, `query`, `stubs`, `expectedJson`) showing exactly what a
query returns against a given schema.

| File | Read when the query involves… |
| --- | --- |
| `references/query.md` | `find` / `stream` basics, `given` vs `find` constraints, `::` type traversal, overall query structure |
| `references/projections.md` | reshaping results with `as`, iteration vs aggregation, `[]` suffix, projection scopes, spread |
| `references/filtering.md` | constraints in `find {}` vs `.filter()`, choosing server-side vs client-side filtering |
| `references/discovery.md` | enriching data from other services, `@Id` lookups |
| `references/expressions.md` | computed fields, `when` conditionals, date formatting, expression types, scoping rules |
| `references/stdlib.md` | built-in functions: strings, math, collections, dates, `convert`, `allOf` / `anyOf` / `noneOf` |
| `references/mutations.md` | writing data with `call` and `write operation` |
| `references/streaming.md` | `stream {}`, `filterEach`, combining streams |
| `references/publishing.md` | named `query {}` blocks published as HTTP endpoints or stream processors |
| `references/query-control.md` | `using` / `excluding` services, `@Cache` |
| `references/patterns.md` | end-to-end patterns: aggregation, nested enrichment |
| `references/advanced.md` | user-defined functions, throwing errors |
| `references/troubleshooting.md` | a query returns unexpected `null` values |

Some capabilities (publishing queries, caching, stream processors) are features of the Orbital query engine rather
than the Taxi compiler alone.

## Related skills

To write or change the Taxi **schema** itself (types, models, services, operations), use the `writing-taxi` skill:
https://taxilang.org/skills/writing-taxi/SKILL.md
