---
title: "Advanced"
description: "Auth schemes, date_time_converter, supported OpenAPI features, name collisions, and known limitations."
---

This page covers advanced configuration and features. If you haven't completed the basics yet,
start with the [Getting Started guide](/getting-started).

---

## Authentication

The generator reads `securitySchemes` from your OpenAPI spec and emits static factory methods
on the aggregator class — one factory per scheme type found. Pass the factory's return value
via the `interceptors:` constructor parameter.

### Bearer Token

Emitted when the spec declares `type: http, scheme: bearer`. The factory returns a Dio
`Interceptor` that sets `Authorization: Bearer <token>` on every request.

```dart
final client = ExampleApiClient(
  interceptors: [
    ExampleApiClient.bearerAuth('your-bearer-token'),
  ],
);
```

Generated factory:

```dart
static Interceptor bearerAuth(String token) => InterceptorsWrapper(
      onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
        options.headers['Authorization'] = 'Bearer $token';
        handler.next(options);
      },
    );
```

<Warning>
Do not hardcode tokens in version-controlled code. Retrieve them from environment variables or
secure storage at runtime.
</Warning>

### API Key (Header)

Emitted when the spec declares `type: apiKey, in: header`. The `headerName` defaults to the
`name` field from the spec's security scheme definition, or `'X-Api-Key'` if absent.

```dart
ExampleApiClient(
  interceptors: [
    ExampleApiClient.apiKeyAuth('my-key', headerName: 'X-Api-Key'),
  ],
);
```

Generated factory:

```dart
static Interceptor apiKeyAuth(String apiKey,
        {String headerName = 'X-Api-Key'}) =>
    InterceptorsWrapper(
      onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
        options.headers[headerName] = apiKey;
        handler.next(options);
      },
    );
```

### API Key (Query Parameter)

Emitted when the spec declares `type: apiKey, in: query`. The `paramName` defaults to the
`name` field from the spec's security scheme definition, or `'api_key'` if absent.

```dart
ExampleApiClient(
  interceptors: [
    ExampleApiClient.apiKeyQueryAuth('my-key', paramName: 'api_key'),
  ],
);
```

<Warning>
API keys in query parameters appear in server logs and browser history. Prefer header-based
API keys or Bearer tokens when possible.
</Warning>

### HTTP Basic Auth

Emitted when the spec declares `type: http, scheme: basic`. The factory base64-encodes
`username:password` and sets `Authorization: Basic <credentials>` on every request. The
aggregator file automatically imports `dart:convert` when this factory is emitted.

```dart
ExampleApiClient(
  interceptors: [
    ExampleApiClient.basicAuth('alice', 'secret'),
  ],
);
```

<Warning>
HTTP Basic auth transmits credentials on every request. Always use HTTPS and consider a more
secure scheme (Bearer, OAuth) for production APIs.
</Warning>

---

## date_time_converter

Controls how `string` + `format: date-time` fields are serialized globally. The setting
applies to every `DateTime` field in every generated model.

| Value | `fromJson` | `toJson` |
|-------|-----------|---------|
| `iso8601` (default) | `DateTime.parse(json['field'] as String)` | `instance.field.toIso8601String()` |
| `timestamp` | `DateTime.fromMillisecondsSinceEpoch(json['field'] as int)` | `instance.field.millisecondsSinceEpoch` |

```yaml title="build.yaml"
targets:
  $default:
    builders:
      dart_openapi_generator:
        options:
          input_spec: "openapi/api.yaml"
          output_dir: "lib/generated"
          date_time_converter: "timestamp"
```

Per-field overrides are not supported — this setting applies globally.

<Warning>
Do not point `output_dir` at a directory that also contains hand-authored files. Generated file
names could collide with authored file names, overwriting the authored file on the next build.
Use a dedicated subdirectory such as `lib/generated/`.
</Warning>

---

## Error Handling via DioException

Every generated service method throws `DioException` on non-2xx HTTP responses. Catch it to
handle API errors:

```dart
try {
  final user = await client.users.getUser('user-id');
  print(user.name);
} on DioException catch (e) {
  final statusCode = e.response?.statusCode;
  final body = e.response?.data;
  print('API error $statusCode: $body');
}
```

- `DioException` is also thrown for network errors (timeout, no connectivity). Check `e.type`
  to distinguish network errors from HTTP errors.
- When a spec operation has non-2xx response codes, the generator emits a comment in the
  method body:
  ```dart
  // NOTE: only 2xx responses are handled. Add error handling for: 404, 422
  // Access error body via: (e as DioException).response?.data
  ```

---

## Testing

`mockito` generates a mock as `class MockUserApi extends Mock implements UserApi` — it always
reaches for `implements`, never `extends`. Generated **service** classes are plain `class`
declarations, so they mock out of the box. Generated **models** are `final class` by default,
and `final` forbids `implements` outside the declaring library, so no test double can be
produced for them.

### `MissingDummyValueError`

The symptom is an error that names `mockito` rather than the generated model, and — the part
that costs the most time — it fires while the stub is being *defined*, not when the stubbed
method is called:

```dart
final api = MockUserApi();
// throws here, on the `when(...)` line itself
when(api.getUser('1')).thenAnswer((_) async => User(id: '1', name: 'A', email: 'a@b.c'));
```

```
MissingDummyValueError: User

This means Mockito was not smart enough to generate a dummy value of type
'User'. Please consider using either 'provideDummy' or 'provideDummyBuilder'
functions to give Mockito a proper dummy value.
```

`when(api.getUser('1'))` invokes the method for real before `when()` ever sees it, and the
generated mock passes `returnValue:` as an eagerly evaluated argument. That value comes from
`dummyValue<User>()`, which falls back to building a `SmartFake` — and a `SmartFake` *implements*
`User`. A `final class` cannot be implemented, so there is no fallback and the call throws, even
though the very next line would have replaced the return value.

### Option 1 — generate models without the modifier

Set [`model_class_modifier`](/options-reference#model_class_modifier-1) to `'none'`:

```yaml title="build.yaml"
targets:
  $default:
    builders:
      dart_openapi_generator:
        options:
          input_spec: "openapi/api.yaml"
          output_dir: "lib/generated"
          model_class_modifier: "none"
```

Generated models become plain `class` declarations and mock like anything else:

```dart
@GenerateMocks([UserApi])
void main() {
  test('getUser', () async {
    final api = MockUserApi();
    when(api.getUser('1')).thenAnswer((_) async => User(id: '1', name: 'A', email: 'a@b.c'));
    expect((await api.getUser('1')).id, '1');
  });
}
```

### Option 2 — register a dummy

Keeps models `final`, at the cost of one registration per type per test file:

```dart
setUpAll(() {
  provideDummy<User>(User(id: '', name: '', email: ''));
});
```

This is also the **only** option for the two kinds of generated type that
`model_class_modifier` cannot help with — `oneOf` wrappers, which are `sealed`, and generated
enums:

```dart
setUpAll(() {
  provideDummy<Notification>(const EmailNotification(address: 'a@b.c'));
  provideDummy<UserRole>(UserRole.admin);
});
```

<Warning>
  Prefer `implements` over `extends` when hand-writing a fake for a generated model. `copyWith`
  and `fromJson` always construct the base type, so a subclass added via `extends` silently loses
  its own fields on the first copy.
</Warning>

---

## Supported OpenAPI Features

### Schemas

| Feature | Support |
|---------|---------|
| `object` (with `properties`, `required`, `additionalProperties`) | Supported |
| `array` (with `items`) | Supported |
| `string`, `integer`, `number`, `boolean` primitives | Supported |
| `string` + `format: date-time` | Supported (see `date_time_converter`) |
| `enum` (string, integer, number values) | Supported |
| `allOf` (flat merge of object members) | Supported |
| `oneOf` with `discriminator` | Supported |
| `oneOf` without `discriminator` | Supported; `fromJson` tries each variant, most specific first |
| `oneOf` branch that is a `$ref` | Reused as a standalone class implementing the wrapper (see [Generated Code](/generated-code)) |
| `anyOf` | Not supported — throws a parse error. Use `oneOf` with a discriminator instead. |
| Same-document `$ref` (`#/components/schemas/…`) | Supported |
| External `$ref` (URL or file path) | Not supported |
| Recursive / circular `$ref` | Supported via `late` nullable fields |
| OpenAPI 3.0 `nullable: true` | Supported |
| OpenAPI 3.1/3.2 `type: [T, 'null']` | Supported |
| Mixing `nullable: true` + `type: [T, 'null']` on the same schema | Rejected with a parse error |
| Schema name starting with a digit | Rejected with a parse error |

### Operations

| Feature | Support |
|---------|---------|
| `get`, `post`, `put`, `patch`, `delete`, `head`, `options` methods | Supported |
| `trace` method | Parsed, routed via `_dio.request` |
| `in: path` parameters | Supported (positional, URL-encoded) |
| `in: query` parameters | Supported (required → named required, optional → named optional) |
| `in: querystring` parameters (OpenAPI 3.2) | Supported — treated identically to `in: query` |
| `in: header` parameters | Supported (optional `String?` named params merged at runtime) |
| `in: cookie` parameters | Not emitted — a warning is logged; use Dio interceptors instead |
| `requestBody` with `application/json` content | Supported |
| `requestBody` on GET/HEAD/OPTIONS | Not sent (HTTP semantics); a warning is logged |
| Inline request body schemas | Supported — named `<OperationId>Request` |
| Inline response schemas | Supported — named `<OperationId>Response` |
| Primary response selection | `200` → `201` → lowest 2xx code |
| Operations with no 2xx response | Return type is `Future<void>`; a warning is logged |
| OpenAPI 3.2 `additionalOperations` (non-standard verbs) | Supported — emitted as `_dio.request` with `Options(method: 'VERB')` |

### Security Schemes

| Scheme | Emitted factory |
|--------|----------------|
| `type: http, scheme: bearer` | `bearerAuth(String token)` |
| `type: http, scheme: basic` | `basicAuth(String username, String password)` |
| `type: apiKey, in: header` | `apiKeyAuth(String apiKey, {String headerName})` |
| `type: apiKey, in: query` | `apiKeyQueryAuth(String apiKey, {String paramName})` |
| Any other type/scheme | No factory; a warning is logged |

---

## Name Collision Handling

The generator maintains a `NameRegistry` built in two passes from the parsed `SpecDocument`.

**Class names** (pass 1: collect, pass 2: convert + validate):

- Component schema names → `toPascalCase` + Dart keyword escape
- Inline request body schemas → `<OperationId>Request` (or `<Method><PathSegments>Request`)
- Inline response schemas → `<OperationId>Response` (or `<Method><PathSegments>Response`)

If two different spec locations produce the same Dart class name after conversion, generation
aborts with an `OpenApiParseException` that names both conflicting locations. The fix is to
rename one schema or operation in the spec.

**Field names:**

- Property names → `toLowerCamelCase` + Dart keyword escape
- Dart reserved words (`class`, `default`, `in`, etc.) get a `_` suffix — e.g. a spec property
  named `class` becomes the Dart field `class_`. The `fromJson`/`toJson` key remains `'class'`.

**File names:**

- Model files: `models/<snake_case_class_name>.dart`
- Service files: `services/<tag_snake_case>_api.dart`
- Tag-to-filename conversion lowercases and replaces spaces/hyphens with underscores (does not
  apply PascalCase splitting — `PetStore` tag → `petstore_api.dart`, not `pet_store_api.dart`).

---

## Known Limitations

- **No remote/HTTPS spec support.** `input_spec` must be a local file — the builder factory reads it synchronously, before any async I/O is possible.
- **No `anyOf` support.** Schemas using `anyOf` cause a parse error. Use `oneOf` with a discriminator as an alternative.
- **Same-document `$ref` only.** External file or URL references are not resolved — they cause a parse error. All referenced schemas must be in `components/schemas` of the same spec file.
- **`oneOf` without a discriminator decodes by trial.** `fromJson` tries the variants most-specific-first and returns the first that decodes. Two variants that accept the same payload are genuinely ambiguous — add a `discriminator.propertyName` to make the choice explicit.
- **A `oneOf` wrapper shares its file with its `$ref` branches.** Dart only allows a `sealed` type to be implemented from inside its own library, so the wrapper and its branches are emitted together. Which file a model lands in therefore depends on the spec's `oneOf` graph; import the generated barrel (`generated.dart`), not individual model files.
- **`in: cookie` parameters are not generated.** The generator logs a warning and recommends Dio interceptors for cookie-based auth.
- **`requestBody` on GET/HEAD/OPTIONS is not sent.** A warning is logged; HTTP semantics prevent sending a body on these methods.
- **Per-field `date_time_converter` overrides are not supported.** The setting applies globally.
- **`oneOf` wrappers and generated enums cannot be mocked.** `model_class_modifier` does not reach them: wrappers are `sealed` (which is what makes `switch` over the union exhaustive) and enums can never be implemented. Use `provideDummy` — see [Testing](#testing).
- **No `webhooks` or root-level `pathItems` (OpenAPI 3.1/3.2).** Only `paths` and `components/schemas` are processed.
- **Only `application/json` request/response bodies.** Multipart, form-encoded, and other content types are not generated.

---

See also: [Getting Started](/getting-started) · [Options Reference](/options-reference) · [Generated Code](/generated-code)
