> ## 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.

# Choose or swap a provider

> Pick a built-in provider, inject it through the deployment's provider container, and bring your own by implementing the contract interface. Providers are injected by the deployment, never baked into the platform.

A [provider](/docs/concepts/how-it-is-built) is the narrowest swap point in
Voyant: a single implementation behind a contract interface. Storage backends,
notification transports, and payment processors are all providers. Module code
targets the contract, and the **deployment** chooses which implementation it
gets. The platform never knows whether storage is R2 or S3, or whether email
goes through Voyant or your own Resend account.

Reach for a provider when the question is "how do I swap one implementation for
another?" If you are adding a new bounded capability with its own records, build
a [module](/docs/platform/extending/apps) instead. If you are adding a route or hook to
an existing module, build an [extension](/docs/platform/fundamentals/extensions).

This guide shows the recipe for three providers: storage, notifications, and
payments. The pattern is the same for all of them.

## How providers reach the platform

A deployment assembles its providers into one typed container and passes it to
`createVoyantApp({ providers })`. In the operator application that container is
built by `buildOperatorProviders()` in `src/api/composition.ts`, and `app.ts`
hands it straight to the platform:

```ts theme={null}
// src/api/app.ts (deployment-owned)
import { createVoyantApp } from "@voyant-travel/framework"
import { buildOperatorProviders } from "./composition"

export const app = createVoyantApp({
  providers: buildOperatorProviders(),
  modules: deploymentLocalModules,
  extensions: deploymentLocalExtensions,
})
```

`buildOperatorProviders()` returns a container that satisfies the platform's
`FrameworkProviders` contract. Each entry is a deployment-supplied resolver or
factory the platform calls when it needs that capability. Swapping a provider
means changing one entry in this container, nothing in the platform and nothing
in the modules.

## Swap a storage backend

Storage targets a single `StorageProvider` contract from
`@voyant-travel/storage/types`. Picking a backend is choosing which factory
creates that provider. See the [Storage](/docs/platform/fundamentals/storage) page
for the full contract and each backend's options.

<Steps>
  <Step title="Pick a built-in provider">
    The `@voyant-travel/storage` package ships three providers, each a factory
    that satisfies the same contract.

    | Backend           | Factory                             | Subpath                                          |
    | ----------------- | ----------------------------------- | ------------------------------------------------ |
    | Local (in-memory) | `createLocalStorageProvider`        | `@voyant-travel/storage/providers/local`         |
    | S3-compatible     | `createS3CompatibleStorageProvider` | `@voyant-travel/storage/providers/s3-compatible` |
    | Gateway           | `createGatewayStorageProvider`      | `@voyant-travel/storage/providers/gateway`       |
  </Step>

  <Step title="Wrap it in the service">
    Most deployments use one backend, so `createStorageService(provider)` wraps a
    single provider as a named `StorageService` with the same
    `upload` / `delete` / `signedUrl` / `get` surface.

    ```ts theme={null}
    import { createStorageService } from "@voyant-travel/storage"
    import { createS3CompatibleStorageProvider } from "@voyant-travel/storage/providers/s3-compatible"

    export function createDocumentStorage(env: CloudflareBindings) {
      return createStorageService(
        createS3CompatibleStorageProvider({
          bucket: env.MEDIA_BUCKET,
          publicBaseUrl: "https://cdn.example.com/",
        }),
      )
    }
    ```
  </Step>

  <Step title="Inject it through the provider container">
    Hand the factory to the deployment's provider container so module code
    resolves storage from the deployment rather than constructing it. In the
    operator application this is the `createOperatorDocumentStorage` entry on the
    container returned by `buildOperatorProviders()`.

    To move from R2 to S3, swap only the factory call:

    ```ts theme={null}
    import { createS3CompatibleStorageProvider } from "@voyant-travel/storage/providers/s3-compatible"

    createStorageService(
      createS3CompatibleStorageProvider({
        region: "us-east-1",
        bucket: "my-bucket",
        accessKeyId: env.AWS_ACCESS_KEY_ID,
        secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
        // endpoint + forcePathStyle for MinIO, Wasabi, B2, DigitalOcean Spaces
      }),
    )
    ```

    No module changes, because every module already targets `StorageProvider`.
  </Step>
</Steps>

## Swap a notification transport

Notifications use a list of providers rather than one. `createNotificationService([...])`
routes each send by its `channel`, and later providers override earlier ones on
channel conflict. That ordering rule is the seam: register a local console sink
first for development, then layer the real transports on top.

```ts theme={null}
import { defineConfig } from "@voyant-travel/framework/project"

export default defineConfig({
  deployment: {
    target: "node",
    mode: "self-hosted",
    providers: {
      email: "…",
      sms: "…",
    },
  },
})
```

The selected providers are resolved at boot and handed to
`createNotificationService`. Feature code picks a **channel**, never a vendor,
so changing transport is a configuration change with no code edit.

## Bring your own provider

The built-in providers are convenience, not a closed set. Any deployment can
implement a contract against another vendor and register it in place of the
shipped factory. The platform only sees the interface.

### A custom storage backend

Implement the `StorageProvider` interface from `@voyant-travel/storage/types`,
then pass your provider to `createStorageService` exactly like a built-in one:

```ts theme={null}
import { createStorageService } from "@voyant-travel/storage"
import type { StorageProvider } from "@voyant-travel/storage/types"

function createGcsProvider(options: GcsOptions): StorageProvider {
  return {
    name: "gcs",
    async upload(body, opts) {
      // PUT to Google Cloud Storage, return { key, url }
    },
    async delete(key) {
      // DELETE the object
    },
    async signedUrl(key, expiresIn) {
      // mint a V4 signed URL
    },
    async get(key) {
      // GET the object, or null when absent
    },
  }
}

const storage = createStorageService(createGcsProvider({ /* ... */ }))
```

### A custom notification transport

Notifications make the bring-your-own path first-class. Implement
`NotificationProvider` from `@voyant-travel/notifications/types` against any
transport (raw Resend, Twilio, SES) and register it in the service list in place
of the cloud providers. Because later providers win on channel conflict, you can
keep the local sink for other channels and override just the one you replace:

```ts theme={null}
import type { NotificationProvider } from "@voyant-travel/notifications/types"

export function createTwilioSmsProvider(options: TwilioOptions): NotificationProvider {
  return {
    name: "twilio",
    channels: ["sms"],
    async send(payload) {
      // POST to the Twilio Messages API
    },
  }
}
```

Package it as a [provider](/docs/platform/extending/adapters-and-providers) and select
it as the deployment's `sms` provider. Feature code keeps naming a channel, so
nothing else changes.

Dispatch by provider name with `sendWith(name, payload)` when you need to target
a specific transport rather than route by channel.

## Payments are a provider too

Payments follow the same shape, behind one contract rather than one integration
per processor. [`@voyant-travel/payments`](https://github.com/voyant-travel/voyant/tree/main/packages/payments)
defines the **payment adapter contract** (`voyant.payment-adapter.v1`), the
provider catalog, and the remote transport. Processors are entries in that
catalog: Netopia and Voyant Pay are providers, not bespoke wiring inside the
booking flow.

The contract is deliberately narrow — initiate, status, verify — and the
platform's vocabulary is its own rather than any processor's. A provider maps
its own stage names onto shared values:

* **Session state**: `pending`, `requires_redirect`, `processing`,
  `authorized`, `paid`, `failed`, `cancelled`, `expired`.
* **Operation status**: `accepted`, `declined`, `pending`, `failed`.
* **Checkout handoff**: `redirect` or `embedded`, negotiated between what the
  provider supports and what the caller accepts.
* **Disputes**: `opened` and `under_review` are open; `won`, `lost` and
  `withdrawn` are resolutions. Every card processor produces these, so the
  contract models them once.
* **Errors**: `CAPABILITY_NOT_SUPPORTED`, `IDEMPOTENCY_KEY_REUSED`,
  `INVALID_REQUEST`, `PROVIDER_UNAVAILABLE`, `ADAPTER_FAILURE`.

A provider declares its `PaymentAdapterCapabilities`, and callers negotiate
against them rather than assuming. That is what makes a processor swap a
configuration change instead of a rewrite: finance code never learns which
provider it is talking to.

To support a different processor, implement the same contract and register the
provider in the catalog. See
[Adapters and providers](/docs/platform/extending/adapters-and-providers).

## Next steps

<CardGroup cols={2}>
  <Card title="Modules" icon="cubes" href="/docs/concepts/how-it-is-built">
    Where a provider sits in the module, adapter, provider, extension taxonomy.
  </Card>

  <Card title="Storage" icon="box-archive" href="/docs/platform/fundamentals/storage">
    The full `StorageProvider` contract and each built-in backend's options.
  </Card>

  <Card title="Adapters and providers" icon="right-left" href="/docs/platform/extending/adapters-and-providers">
    When to build which, and how the deployment graph selects it.
  </Card>

  <Card title="Configuration" icon="sliders" href="/docs/platform/fundamentals/configuration">
    How a deployment composes modules and wires the providers behind them.
  </Card>
</CardGroup>
