> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# bff.effect

- **Type:**

```ts
type BffEffectUserConfig = {
  entry?: string;
  strictEffectApproach?: true;
  openapi?: boolean | { path?: string };
  dataPlatform?: {
    enabled?: boolean;
    requireEnvelope?: boolean;
    envelopeHeader?: string;
    expectedNamespace?: string;
    validateOrigin?: boolean;
    requireTraceContext?: boolean;
    selection?: {
      maxDepth?: number;
      maxFields?: number;
      allowedLeafPaths?: string[];
    };
    batch?: {
      enabled?: boolean;
      endpoint?: `/${string}`;
      maxBatchSize?: number;
      maxBatchBytes?: number;
      flushIntervalMs?: number;
      maxConcurrency?: number;
      requestTimeoutMs?: number;
      allowedMethods?: string[];
    };
  };
};
```

- **Default:** `{}`

:::tip
Please refer to the [Enable BFF](/guides/advanced-features/bff/function.md#enable-bff) section in Basic Usage to enable BFF functionality first.
:::

`bff.effect` is only effective when `bff.runtimeFramework` is set to `'effect'`.

:::caution Install the Effect peers yourself
`effect` and `@effect/opentelemetry` are **optional exact peer dependencies** of
`@modern-js/bff-effect`. Install them in the application so its API modules and
the framework use the same Effect instance. Before setting
`runtimeFramework: 'effect'` or importing `@modern-js/bff-effect/effect`,
`/effect-edge` or `/effect-client`, install the exact cohort:

```bash
pnpm add effect@4.0.0-rc.112 @effect/opentelemetry@4.0.0-rc.112
```

The pin is exact because UltraModern ships Effect as one lockstep cohort. Apps
using only native `runtimeFramework: 'hono'` or
`@modern-js/bff-effect/data-platform` need neither package.
:::

Use `bffPlugin` from `@modern-js/plugin-bff-build-extensions` to register the
Effect runtime. It composes the native BFF plugin. Install
`@modern-js/bff-effect` and `@modern-js/plugin-bff-extensions` from the same
framework cohort as application production dependencies, so the adapter remains
available after development dependencies are removed. The build plugin can be a
development dependency. See the [runtime configuration example](/guides/advanced-features/bff/frameworks.md).

Native `@modern-js/plugin-bff/server` exports Hono APIs. For Node Effect APIs,
import framework helpers such as `defineEffectBff` from
`@modern-js/bff-effect/effect` and namespaces from the corresponding `effect/*`
modules. Worker handlers and worker request context use
`@modern-js/bff-effect/effect-edge`.

Generated UltraModern workspaces use this runtime as the only generated HTTP API
path. The API contract lives at `shared/api.ts`, the server runtime lives at
`api/index.ts`, clients live under `src/api/*-client.ts`, and generated checks
reject `api/effect`, `api/lambda`, `shared/effect`, `src/effect`, Hono server
imports, raw request handlers, manual request parsing, and manual `Response`
construction in API modules.

For Effect v4, TypeScript should use export-map aware module resolution.

```json title="tsconfig.json"
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Bundler"
  }
}
```

## bff.effect.entry

- **Type:** `string`
- **Default:** `<apiDirectory>/index`

Specifies the entry module for Effect HttpApi runtime.

```ts title="modern.config.ts"
export default defineConfig({
  bff: {
    runtimeFramework: 'effect',
    effect: {
      entry: './api/index',
    },
  },
});
```

## bff.effect.strictEffectApproach

- **Type:** `true`
- **Default:** `true`

Effect BFF modules must expose the strict Effect API surface
(`defineEffectBff(...)` or `{ api, layer }`) and raw request handlers are
rejected. Generated UltraModern apps write this marker explicitly and pair it
with source checks that reject Hono server imports, manual request parsing, and
manual `Response` construction in generated API modules.

## bff.effect.openapi

- **Type:** `boolean | { path?: string }`
- **Default:** `false`

Enable OpenAPI endpoint generation for Effect HttpApi runtime.

```ts title="modern.config.ts"
export default defineConfig({
  bff: {
    runtimeFramework: 'effect',
    effect: {
      openapi: {
        path: '/openapi.json',
      },
    },
  },
});
```

## bff.effect.dataPlatform

- **Type:**

```ts
{
  enabled?: boolean;
  requireEnvelope?: boolean;
  envelopeHeader?: string;
  expectedNamespace?: string;
  validateOrigin?: boolean;
  requireTraceContext?: boolean;
  selection?: {
    maxDepth?: number;
    maxFields?: number;
    allowedLeafPaths?: string[];
  };
  batch?: {
    enabled?: boolean;
    endpoint?: `/${string}`;
    maxBatchSize?: number;
    maxBatchBytes?: number;
    flushIntervalMs?: number;
    maxConcurrency?: number;
    requestTimeoutMs?: number;
    allowedMethods?: string[];
  };
}
```

- **Default:**

```ts
{
  enabled: true,
  requireEnvelope: false,
  envelopeHeader: 'x-modernjs-data-envelope',
  validateOrigin: true,
  requireTraceContext: false,
  batch: {
    enabled: true,
    endpoint: '/_data/batch',
    maxBatchSize: 16,
    maxBatchBytes: 64 * 1024,
    maxConcurrency: 4,
    requestTimeoutMs: 10000,
    allowedMethods: ['GET'],
  },
}
```

Configure request-envelope validation for Effect runtime.

Generated Effect client requests batching against `${bff.prefix}${batch.endpoint}` (for example, `/bff-api/_data/batch`).

Envelope validation is enabled by default but optional: requests without an envelope pass unless `requireEnvelope` is `true`. If an envelope is present, runtime validates it against the configured namespace, origin, trace, and selection policy. `expectedNamespace` is unset by default, and `selection` has no default limits unless configured.

Origin validation is enabled by default. Runtime compares envelope origin with request `Origin` header first, then falls back to request URL origin; set `validateOrigin: false` to disable that comparison.

```ts title="modern.config.ts"
export default defineConfig({
  bff: {
    runtimeFramework: 'effect',
    effect: {
      dataPlatform: {
        requireEnvelope: true,
        expectedNamespace: 'my-app',
        selection: {
          maxDepth: 6,
          maxFields: 200,
        },
      },
    },
  },
});
```

`maxConcurrency` and `requestTimeoutMs` control the server batch gateway. Native `HttpApiClient` calls send individual requests; they do not automatically batch requests.

Import a shared `HttpApi` contract and pass it to `HttpApiClient.make` or `makeEffectHttpApiClient`. The client is fully type-inferred; `defineEffectBff` exposes server handlers and does not contain a client.

## Effect cohort

UltraModern-generated workspaces pin the framework-compatible Effect cohort
through `pnpm-workspace.yaml` overrides. For the current UltraModern cohort,
generated apps use:

```yaml
trustPolicyExclude:
  - 'effect@4.0.0-rc.112'
  - '@effect/opentelemetry@4.0.0-rc.112'

overrides:
  '@effect/opentelemetry': 4.0.0-rc.112
  '@effect/vitest': 4.0.0-rc.112
  effect: 4.0.0-rc.112
```

Do not add a different direct `effect` version in an app package. A mismatched
Effect prerelease can fail while building layers or HTTP middleware because runtime
services come from different package instances. The strict 24-hour release-age
gate applies to installed packages; the current cohort has no Effect age
exemption, and override-only `@effect/vitest` is not an installed approval
target. `trustPolicyExclude` is a separate policy:
its exact `effect` and `@effect/opentelemetry` exceptions cover their
trusted-publisher to provenance metadata transition and are not release-age
approvals.

## Contract tests

Strict Effect APIs should test the declared `HttpApi` contract, not a raw
request handler. Edge-compatible tests can use the framework helper:

```ts
import { createEffectBffTestHandler } from '@modern-js/bff-effect/effect-edge';
import apiModule from '../api/index';

const testApi = await createEffectBffTestHandler({
  module: apiModule,
  prefix: '/api',
});

const response = await testApi.handler(new Request('http://localhost/api/ping'));
```

If you manually compose an Effect web handler in a low-level proof, provide the
platform services explicitly:

```ts
import * as Layer from 'effect/Layer';
import { HttpRouter, HttpServer } from 'effect/unstable/http';
import { HttpApiBuilder } from 'effect/unstable/httpapi';

const handler = HttpRouter.toWebHandler(
  HttpApiBuilder.layer(api).pipe(
    Layer.provide(apiGroupLayer),
    Layer.provide(HttpServer.layerServices),
  ),
).handler;
```

Prefer the generated helper unless the test needs to inspect the low-level
Effect router composition.

## Dynamic CORS

For dynamic origin predicates, use Effect HTTP middleware as a layer. In the
current Effect v4 beta cohort, `HttpRouter.middleware(...)` returns a `Layer`
directly:

```ts
import * as Effect from 'effect/Effect';
import * as Layer from 'effect/Layer';
import { HttpMiddleware, HttpRouter } from 'effect/unstable/http';
import { HttpApiBuilder } from 'effect/unstable/httpapi';

const corsLayer = HttpRouter.middleware(
  Effect.succeed(
    HttpMiddleware.cors({
      allowedOrigins: origin =>
        origin.endsWith('.example.com') ? origin : undefined,
    }),
  ),
);

const layer = HttpApiBuilder.layer(api).pipe(
  Layer.provide(apiGroupLayer),
  Layer.provide(corsLayer),
);
```

Do not read a `.layer` property from `HttpRouter.middleware(...)`.

## Other transports

`strictEffectApproach` governs generated HTTP API modules. Effect RPC,
WebSockets, and other transports remain valid when they are modeled as explicit
transport surfaces with typed Effect programs. They do not make raw HTTP
request handlers valid inside generated UltraModern API modules.
