---
name: writing-taxi
description: Writes and edits Taxi schemas — semantic types, models, enums, services, operations, annotations, documentation comments and date formats — following Taxi's semantic-typing conventions. Use when asked to create, extend, review or fix .taxi schema files, model an API or data source in Taxi, or describe HTTP services in Taxi. Not for writing queries (use writing-taxiql for that).
---

# Writing Taxi schemas

[Taxi](https://taxilang.org) is a language for describing data and APIs **semantically**. A Taxi schema declares
*what data means* (semantic types), *how it is structured* (models), and *where it comes from* (services and
operations). Query engines such as Orbital use those semantics to connect data across services automatically, so
getting the semantic types right is what makes a schema useful.

This is a concise reference covering the syntax that LLMs most often get wrong. It is **not** a TaxiQL (query)
reference — use the `writing-taxiql` skill for queries.

## Workflow

1. **Read the existing schema** in the project (`*.taxi` files under the `sourceRoot` in `taxi.conf`, usually `src/`).
   Reuse existing semantic types wherever the meaning matches — two fields that mean the same thing must share the
   same type, or the query engine can't connect them.
2. **Declare semantic types** for each distinct concept, then **models** built from those types, then **services**
   exposing operations.
3. When changing an existing schema, return only the new / changed declarations, not the whole schema.
4. **Compile.** If the `taxi` CLI is available, run `taxi build` in the project directory and fix any errors.

## Core primitives

| Category | Types |
| --- | --- |
| Logical | `Boolean` |
| Numeric | `Int`, `Long`, `Decimal`, `Double` |
| Text | `String` |
| Date / time | `Date`, `Time`, `DateTime`, `Instant` |

`Decimal` is for currency / exact arithmetic. `Double` is floating-point.
`Instant` is UTC, fully-zoned; `DateTime` has no zone.

## Semantic types vs models

Taxi separates **semantic meaning** from **structure**.

- A `type` declares a unit of meaning. It usually has **no fields** — it `inherits` from a primitive or another type.
- A `model` declares a **structure** (fields). Each field's type should be a semantic type, not a primitive.

```taxi
// Semantic types — no fields, just meaning
type CustomerId inherits String
type FirstName inherits String
type LastName inherits String

// Model — structure built from semantic types
model Customer {
   id : CustomerId
   firstName : FirstName
   lastName : LastName
}
```

Anti-pattern (do **not** do this):

```taxi
type Customer {           // ❌ types do not have fields
   id : String            // ❌ raw primitives, no semantic meaning
   firstName : String
}
```

## type vs model vs service vs operation

| Construct | Purpose | Example |
| --- | --- | --- |
| `type` | Semantic meaning, usually inherits a primitive | `type CustomerId inherits String` |
| `model` | Data structure with fields | `model Customer { id : CustomerId }` |
| `service` | A logical API or data source | `service CustomerApi { ... }` |
| `operation` | A read inside a service (callable by the query planner) | `operation getCustomer(CustomerId):Customer` |
| `write operation` | An explicit mutation (must be invoked, never inferred) | `write operation save(Customer):Customer` |
| `stream` | A subscription returning `Stream<T>` (Kafka, ServiceBus, SQS) | `stream prices : Stream<Price>` |
| `table` | A keyword inside a service for "the whole collection" | `table customers : Customer[]` |

Operations always live **inside** a `service { ... }` block:

```taxi
service CustomerService {
   operation getCustomer(CustomerId):Customer
   operation findCustomersByLastName(LastName):Customer[]
   write operation saveCustomer(Customer):Customer
}
```

Operation parameters and return types should be semantic types or models — this is what lets the query engine
discover that it can call `getCustomer` whenever it has a `CustomerId` and needs a `Customer`.

For services called over HTTP (`@HttpService`, `@HttpOperation`, path / query variables, headers, retries), read
`references/http-services.md`.

## Documentation comments

Use `[[ ... ]]` brackets. Markdown is allowed. Place them immediately before the declaration they describe.

```taxi
[[ Unique identifier for a customer in our system. ]]
type CustomerId inherits String

[[
A customer record.
Sourced from the CRM database.
]]
model Customer {
   id : CustomerId
}
```

`//` and `/* ... */` are line/block comments — they do **not** become docs.

## Annotations

Annotations attach metadata. They can be applied to types, models, fields, services, and operations.

```taxi
@Id
customerId : CustomerId

@Table(connection = "films-db", schema = "public", table = "customer")
model Customer { ... }
```

Annotation values are usually quoted strings. Use `=` for parameters.

`@Id` marks the identifier of a model. When a model has an `@Id`, the query engine will only look it up using
operations that accept that identifier type.

## Date and time formatting

Use `@Format("pattern")` on a date/time semantic type. The pattern is Java `DateTimeFormatter` syntax. Literal text
goes in single quotes: `'T'`, `'Z'`. A single quote is escaped as `''`.

```taxi
@Format("dd/MM/yyyy")
type BirthDate inherits Date

@Format("yyyy-MM-dd HH:mm")
type AppointmentTime inherits DateTime

@Format("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
type EventTimestamp inherits Instant
```

A model field can also override the format inline:

```taxi
model Event {
   @Format("yyyy-MM-dd")
   eventDate : EventTimestamp
}
```

The full table of pattern symbols is in `references/date-format-patterns.md`.

## Enums

```taxi
// Basic — value = name
enum Status { ACTIVE, INACTIVE }

// With explicit values
enum Country {
   NEW_ZEALAND("NZ"),
   AUSTRALIA("AUS")
}

// Lenient — case-insensitive matching of inbound values
lenient enum DayCount {
   ACT_360("ACT/360"),
   ACT_365("ACT/365")
}

// Default for unmatched values
enum Severity {
   HIGH("H"),
   LOW("L"),
   default UNKNOWN("U")
}

// Synonyms — link enums from different services
enum French {
   Un synonym of English.One,
   Deux synonym of English.Two
}
```

## Arrays

`Person[]` is the idiomatic form. `Array<Person>` is accepted as an equivalent alternative — prefer `Person[]` in new
code.

```taxi
model Person {
   friends : Person[]
   alsoFriends : Array<Person>   // same thing, less common
}
```

## Nullability

Append `?` to make a field nullable.

```taxi
model Customer {
   firstName : FirstName            // required
   middleName : MiddleName?         // optional
}
```

## Namespaces and imports

Imports go at the top of the file, before the `namespace` (if any). Always fully-qualify in the `import` and use the
short name everywhere else.

```taxi
import com.orbitalhq.jdbc.Table
import com.acme.types.CustomerId

namespace com.acme.customers

@Table(connection = "db", schema = "public", table = "customer")
model Customer {
   id : CustomerId
}
```

## Common mistakes to avoid

1. **Putting fields on a `type`.** Types have no fields. Use a `model`.
2. **Using raw primitives in models.** `id : String` carries no meaning — wrap with a semantic type:
   `type CustomerId inherits String` then `id : CustomerId`.
3. **Creating a new type for a concept that already has one.** If `CustomerId` already exists, reuse it rather than
   declaring `ClientIdentifier` — otherwise the two can't be connected.
4. **`//` for docs.** Only `[[ ... ]]` becomes documentation.
5. **Forgetting the `import`.** Annotations such as `@HttpOperation` (`taxi.http`) or `@Table`, `@KafkaService`,
   `@MongoService` (`com.orbitalhq.*`) must be imported.
6. **Confusing `operation` and `write operation`.** A `write operation` is never invoked by the query planner — the
   caller must trigger it explicitly. Most reads should be plain `operation`.
7. **Returning a bare type from a stream.** Streams must return `Stream<T>`, not just `T`.
8. **Operations outside a service.** All operations must sit inside a `service { ... }` block.
9. **Overusing inheritance.** Inherit only when there is a real "is-a" relationship. `type Email inherits String` is
   fine. `type PreferredCustomerEmail inherits CustomerEmail inherits Email inherits String` is usually a smell.

## Reference guides

| File | Read when… |
| --- | --- |
| `references/http-services.md` | declaring services that are called over HTTP / REST |
| `references/date-format-patterns.md` | writing a non-trivial `@Format` pattern |

Further documentation: https://taxilang.org/docs — in particular
[Semantic types](https://taxilang.org/docs/language/semantic-types),
[Models](https://taxilang.org/docs/language/models),
[Services](https://taxilang.org/docs/language/services) and
[Best practices for taxonomy development](https://taxilang.org/docs/language/best-practices-for-taxonomy-development).

## Related skills

To write queries against a Taxi schema, use the `writing-taxiql` skill:
https://taxilang.org/skills/writing-taxiql/SKILL.md
