# Codegen vs additionalProperties: When Free-Form Maps Break Typed Clients

**Quick answer:** An OpenAPI schema with `additionalProperties` describes a free-form map keyed by strings, and how a code generator turns that into typed code varies a lot between languages and generators. Some emit an index signature or a record type, others fall back to a generic object, and a few generate a map of a specific value type. The engineering decision is to be explicit about the map's value type rather than relying on defaults, and to verify the generated output actually preserves it instead of silently collapsing to an untyped object.

Free-form maps are common in real APIs: metadata bags, localization strings, feature flags, and per-currency pricing all use a set of keys the schema cannot enumerate. Modeling them well matters because a weak generation leaves the client with no type safety on map values, while an over-strict generation can reject legitimate keys the API adds later. The contract should say exactly what the values look like, and the generator should be chosen or configured to honor that. Assuming every generator handles an open map the same way is where teams get surprised.

## How the same schema is generated

Given a map with string values, the output across targets reflects each language's idioms.

| Target | Typical generated type | Type safety on values |
| --- | --- | --- |
| TypeScript | Record<string, string> | Strong |
| Java | Map<String, String> | Strong |
| Go | map[string]string | Strong |
| Python | Dict[str, str] (or loose) | Depends on generator |
| Untyped fallback | object / dict | None |

## Declare the value type explicitly

The schema should define what every value in the map looks like, including nested objects, rather than leaving additionalProperties as a bare boolean.

`# openapi.yaml components: schemas: Order: type: object properties: id: type: string metadata: type: object # every value is a string; unknown keys are allowed additionalProperties: type: string example: source: "checkout" campaign: "launch-2026" localizedLabels: type: object additionalProperties: type: object properties: label: type: string required: type: boolean required: [label]`

## Check the generated output, not just the build

A generator that cannot express a typed map may emit a generic object while still building successfully, so the drift is invisible until a developer notices there is no autocomplete. Inspect the generated file and assert the expected type, and pin the generator version so a change does not silently weaken the output.

`// expected generated TypeScript; a test or review can assert this shape export interface Order { id: string; metadata: Record<string, string>; localizedLabels: Record< string, { label: string; required?: boolean } >; } // a loose fallback would be `metadata?: object`, which loses value safety // and is worth catching in a generated-code check`

## Open keys, closed values

The durable pattern is to keep the key set open, because the API legitimately adds keys, while pinning the value shape in the contract so every value is still typed. Combining a local-first OpenAPI workflow with a generator that emits idiomatic maps and a CI check on the generated output keeps free-form data usable without dropping to untyped objects. If a target genuinely cannot express a typed map, that is a generator limitation to document and work around deliberately, not something to discover months into the integration.

## Frequently asked questions

### When should I use additionalProperties versus an enum of keys?

Use an enum when the key set is closed and stable, which gives stronger typing. Use additionalProperties when the API accepts arbitrary keys, such as user-supplied metadata, and type the values instead.

### What does additionalProperties: false actually do?

It forbids any property not explicitly listed, which is useful for strict request bodies but wrong for a metadata bag. Do not confuse it with the typed-map form, which allows unknown keys of a defined value type.

### Why did my generated client type the map as a plain object?

Either the schema left additionalProperties as a bare boolean with no value type, or the generator does not support typed maps in that language. Add the value type and check the generator's documentation and version.

### Can map values be required or nullable?

The value schema supports the same constraints as any schema, including nullable and required nested fields. Define them on the additionalProperties schema and verify the generated type reflects them.

This is part of the [OpenAPI Code Generation Guide](https://www.powerduck.com/blog/openapi-codegen-guide-hub/) — start there for the full picture of reliable generated clients.

