# Service input (/docs/composer/service-input)

> For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL.

Declare a service's configuration and credentials as one schema, bind values at provision time, and read them back typed.

Location: Composer > Service input

Everything a running service receives arrives through one of two channels, and choosing the channel is most of the decision:

| The value is...                                            | Declare it as                             | Provide it                                                          | Read it           |
| ---------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------- | ----------------- |
| Produced by another node (a database, another service)     | a dependency: `deps: { db: postgres() }`  | wire it at `provision()`                                            | `service.load()`  |
| Anything else (a region, a flag, a job list, a credential) | one field of the service's `input` schema | bind it at `provision()`: a literal, `envParam()`, or `envSecret()` | `service.input()` |

Dependencies are covered in [Services and contracts](https://www.prisma.io/docs/composer/services-and-contracts) and [Databases](https://www.prisma.io/docs/composer/databases). This page is the second row.

## Declare the input schema [#declare-the-input-schema]

A service declares its whole incoming configuration, plain values and credentials together, as one [Standard Schema](https://standardschema.dev). The schema gives you the TypeScript type and validates the assembled input at deploy and again at boot, so garbage config is a loud failure, not a runtime surprise. A credential is a field typed as the redacting `SecretString` box:

```ts title="src/scheduler/service.ts"
import { secretString } from '@prisma/composer/arktype';
import { type } from 'arktype';

const schedulerInput = type({
  jobs: type({ jobId: 'string', every: 'string' }).array(),
  'region?': 'string',
  apiKey: secretString(),
});

export default compute({ name: 'scheduler', input: schedulerInput /* ... */ });
```

Because the input is a real schema, legality can be conditional. "No Stripe key unless billing is on" is an ordinary union, not a framework feature, and `service.input()` narrows it like any other TypeScript union:

```ts
const chatInput = type({ stripeEnabled: 'false' }).or(
  type({ stripeEnabled: 'true', stripeSecretKey: secretString() }),
);
```

## Bind values at provision [#bind-values-at-provision]

The service declares what shapes are legal; the place that provisions it decides where each value comes from. The binding is a plain object mirroring the schema's shape, whose leaves are literals, `envParam(...)`, or `envSecret(...)`:

```ts title="module.ts"
import { envParam, envSecret } from '@prisma/composer-prisma-cloud';

provision(scheduler, {
  input: {
    jobs: [{ jobId: 'tick', every: '60s' }],  // a literal
    region: envParam('REGION'),               // a per-stage platform variable
    apiKey: envSecret('SCHEDULER_API_KEY'),   // a credential: name only, never the value
  },
});
```

Secretness is enforced by validation, not annotation: binding a plain literal where the schema expects a `SecretString` fails the deploy (a credential almost landed in plain config), and binding `envSecret` where the schema expects a plain string fails the same way. Neither side can silently misclassify a credential.

### envParam: plain values the code cannot know [#envparam-plain-values-the-code-cannot-know]

An app origin is the canonical case: it differs between production and every preview stage, and a stage's public URL does not exist until that stage first deploys. Each stage keeps its own copy of the variable, so one topology serves them all.

How the value travels: **the stage's platform variable is the store; the deploying shell only seeds it.** At deploy, preflight checks the name exists for the target stage, and a name the stage is missing is copied up from the deploying shell's environment. Once the stage has it, your shell no longer matters. To change the value later, set it on the platform and redeploy; a running instance's environment is frozen when the instance is created.

`envParam` values arrive as raw strings, so bind them to string fields.

### envSecret: credentials [#envsecret-credentials]

Seeded exactly the same way, with one rule: **the value never enters framework config**. The framework carries only the variable's name; the platform injects the value straight into the running instance. What your code gets is a `SecretString` box that redacts on logging, rendering, and JSON serialization. Leaking it takes a deliberate `.expose()`:

```ts
const input = service.input();
if (input.stripeEnabled) stripe(input.stripeSecretKey.expose());
```

A reusable Module forwards a secret need to its parent without ever learning the platform name (see [Apps and Modules](https://www.prisma.io/docs/composer/apps-and-modules#reusable-modules)), which is what lets a Module require credentials without dictating your naming.

## Absence is the schema's call [#absence-is-the-schemas-call]

An env-bound field whose variable is unset (or empty) resolves to *key omitted*. Whether that is legal is the schema's call: an optional field, a union arm, or a validation error that fails the deploy. A credential for an off-by-default feature is an ordinary optional `SecretString` field, not a framework flag. Because an omitted key can also be a typo'd variable name, the deploy report prints every key that resolved absent; check it when a value goes missing.

## The reserved port [#the-reserved-port]

Every service also gets a `port` (default 3000), outside the input schema and read through its own typed accessor:

```ts
Bun.serve({ port: service.port(), hostname: '0.0.0.0', fetch: handler });
```

`service.port()` is a sibling of `service.origin()`. Never read `process.env.PORT` yourself; the framework exports `PORT` only for a server it does not write and cannot call, Next.js's standalone `server.js`, which binds it itself.

## What travels [#what-travels]

The deploy validates the resolved binding, applies the schema's defaults, and serializes the result into one document, with each secret as a pointer naming the platform variable that holds the value, never the value itself:

```json no-copy
{ "stripeEnabled": true, "stripeSecretKey": { "$secret": "STRIPE_SECRET_KEY" } }
```

At boot the framework swaps each pointer for a redacting box over the named variable, validates against the schema again, and `service.input()` returns the typed object. The document is secret-free by construction, which is why the deploy report can print it verbatim.

## Next steps [#next-steps]

* [Deploying](https://www.prisma.io/docs/composer/deploying): how a fresh stage gets its variables in CI.
* [Testing](https://www.prisma.io/docs/composer/testing): supply an input double under the reserved `input` key.

## Related pages

- [`Apps and Modules`](https://www.prisma.io/docs/composer/apps-and-modules): How services, resources, and Modules compose into a Prisma App, and how provision() wires them together.
- [`Building blocks`](https://www.prisma.io/docs/composer/building-blocks): Compose the ready-made cron, storage, and streams Modules instead of building scheduled jobs, blob storage, or event streams yourself.
- [`Core concepts`](https://www.prisma.io/docs/composer/core-concepts): The ideas every Composer declaration and command builds on: services, resources, Modules, ports, contracts, stages, and the deploy model.
- [`Databases`](https://www.prisma.io/docs/composer/databases): Give a service a Postgres database, either as a plain connection or typed by a Prisma 8 contract with managed migrations.
- [`Deploying`](https://www.prisma.io/docs/composer/deploying): Deploy a Prisma App to production or an isolated stage, run it in CI, and tear environments down safely.