> ## Documentation Index
> Fetch the complete documentation index at: https://voyant.travel/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How Voyant is built

> Enough of the internal shape to reason about the API and the data you get back: modules as components of one deployable, why cross-domain references go through links, and where the seams are.

You do not need this page to build on Voyant. It exists because knowing the shape of the thing behind the API makes the API easier to predict.

<Note>
  **Modules are not an extension point.** They are the components the platform itself is built from. A deployment cannot add, drop, or replace one, and there is no supported path for writing your own. If you need new capability, build an [app](/docs/platform/extending/apps); to change existing behavior, build an [extension](/docs/platform/fundamentals/extensions).
</Note>

## One deployable, many modules

Voyant is assembled from domain modules — catalog, commerce, inventory, operations, bookings, finance, and the rest. They are **components of one resident Node application**, not separately deployed services.

A module owns four things:

* **Its data model** — the tables holding the canonical state of its domain.
* **Its services** — the domain logic that enforces that domain's rules.
* **Its routes** — thin HTTP handlers that validate, call a service, and shape a response.
* **Its background work** — the subscribers and jobs its behavior requires.

The test for ownership is canonical state. If a table is the authoritative record of one capability, it lives in that capability's module.

Each module usually ships alongside a `-contracts` package (the wire types shared by server and client) and a `-react` package (hooks and components built on those contracts). That is why the SDK types match the API exactly: both sides import the same contract.

## Why cross-domain reads look the way they do

Inside a module you get ordinary relational modeling: real foreign keys, constraints, relations.

Across modules you do not. A booking does not carry a foreign key into the CRM's tables; it carries a plain id, and the association is declared as a **link**. Reads stitch across modules in application code through the query graph rather than through joins.

This is why the API returns ids where you might expect embedded objects, and why some reads are composed rather than single queries. The reason is packaging: a hard cross-package foreign key would force every deployment to install both sides.

## Where the seams are

Four seams, in the order you should reach for them:

| Seam                                                       | Use it when                                                                                                    |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **[App](/docs/platform/extending/apps)**                        | You are adding capability with its own records and lifecycle. Runs outside the deployment, activated by OAuth. |
| **[Extension](/docs/platform/fundamentals/extensions)**         | You are changing how existing module behavior works.                                                           |
| **[Provider](/docs/platform/extending/adapters-and-providers)** | You are swapping a vendor behind an existing contract: storage, email, SMS, payments.                          |
| **[Custom field](/docs/platform/fundamentals/custom-fields)**   | You need to carry extra data on a core entity.                                                                 |

Automations are not a seam. They run on **your** infrastructure, calling the API and reacting to webhooks.

## Composition happens at build time

A deployment is a versioned, declarative package graph resolved at build time and lowered to one application. The project declares only its *differences* from the standard product; everything else comes from the versioned distribution. Provider bindings resolve at boot inside that fixed graph.

The practical consequence: upgrading is a version bump plus migrations, not a code merge. See [Configuration](/docs/platform/fundamentals/configuration).

<CardGroup cols={2}>
  <Card title="Architecture" icon="diagram-project" href="/docs/platform/fundamentals/architecture">
    The runtime, the request lifecycle, and the tenancy model.
  </Card>

  <Card title="Build on Voyant" icon="code" href="/docs/guides/consume-the-api">
    The part you actually work with.
  </Card>
</CardGroup>
