skip to content

What does additionalProperties do in an OpenAPI schema, and when is false a mistake?

level: seniorimportance: should knowfreq 44%

answer

  1. Governs the keys you did not declare
  2. Three forms: open, closed, typed
  3. How a map gets modelled
  4. Independent branches make strictness backfire
  5. Strict on the way in, tolerant on the way out

basics

~20 s

In an OpenAPI schema, additionalProperties controls properties not listed under properties: true (the default) allows any, false rejects them, and a schema value types them, which is how free-form maps are modelled. Setting false breaks allOf composition and blocks additive evolution.

solid answer

~40 s

`additionalProperties` governs keys an object schema did not declare. It takes three forms. Omitted or `true` means unknown keys are allowed and unconstrained — the default. `false` means an unknown key fails validation. A **schema** value constrains the *values* of undeclared keys, which is how you model a map: `type: object` with `additionalProperties: {type: string}` generates as `Map<String, String>`. Two situations make `false` a mistake. First, inside an `allOf` branch it poisons composition: each subschema is validated independently, so the strict branch sees its siblings' properties as unknown and rejects everything. Second, on a *response* schema consumed by generated clients, it forbids the server from ever adding a field — an ordinarily additive change becomes breaking. The usual discipline is strict on requests, where rejecting typos is valuable, and tolerant on responses.

code

yaml · 23 lines
yaml
components:
  schemas:
    # a typed map: Map<String, String>
    Labels:
      type: object
      additionalProperties:
        type: string

    # strict request payload (no composition)
    CreateOrderRequest:
      type: object
      required: [sku, quantity]
      properties:
        sku: { type: string }
        quantity: { type: integer }
      additionalProperties: false

    # tolerant response: the server may add fields later
    Order:
      type: object
      required: [id]
      properties:
        id: { type: string }

go deeper

for a junior

Recall the three forms — omitted/true, false, and a value schema — and that the schema form is how a map is described.

for a middle

Explain the default, how the value-schema form generates a map type, and why undeclared keys are unconstrained unless you say otherwise.

for a senior

Show the two production failures: strictness inside an allOf branch rejecting every payload, and a closed response schema turning additive server changes into breaking client changes.

for a principal

Own the policy — strict requests, tolerant responses, where validation is actually enforced (gateway versus service), and how that policy is lint-checked across every team's specs.

## The three forms `additionalProperties` applies to an object schema and decides what happens to keys not named in `properties` (nor matched by `patternProperties` in the JSON Schema 2020-12 dialect OpenAPI 3.1 uses). 1. **Omitted, or `true`** — undeclared keys are allowed and unconstrained. This is the default, and it is why a bare `type: object` accepts absolutely any object. 2. **`false`** — an undeclared key makes the instance invalid. This is "closed content" or strict validation. 3. **A Schema Object** — undeclared keys are allowed, but each of their *values* must satisfy that schema. ## Modelling maps Form 3 is how OpenAPI expresses a dictionary. `type: object` with `additionalProperties: {type: string}` describes an arbitrary-keyed map of strings, and generators emit the target language's map type. Combine it with `properties` to describe an object with a fixed core plus typed extras: ``` type: object properties: id: { type: string } additionalProperties: { type: string } ``` Here `id` must be a string because `properties` says so, and every other key must also be a string because `additionalProperties` says so. Generators handle this shape with varying grace — many produce a class with an extra `additionalProperties` map field. A fully free-form blob is `type: object` with no `properties` and either no `additionalProperties` or `additionalProperties: true`. It is honest when the payload really is opaque (a metadata bag, a vendor passthrough) and lazy when it is not. ## Why false breaks allOf This is the single most common production failure involving the keyword. Given: ``` Dog: allOf: - $ref: '#/components/schemas/Animal' # declares name - type: object properties: breed: { type: string } additionalProperties: false ``` Each `allOf` branch is validated **independently** against the whole instance. The second branch declares only `breed`, so when it sees `name`, that is an undeclared key — and `additionalProperties: false` rejects it. No payload can ever validate. The strictness cannot "see" the sibling branch's properties. The fixes are: drop the strictness, flatten the composition into one schema that declares every property and then closes it, or (in 3.1's dialect) use `unevaluatedProperties: false` at the composed level, which is aware of what sibling subschemas evaluated. `unevaluatedProperties` is a JSON Schema 2020-12 keyword and therefore available in OpenAPI 3.1, not in 3.0. ## Why false hurts evolution The tolerant-reader principle says a consumer should ignore fields it does not understand, so a server can add a response field without breaking anyone. `additionalProperties: false` on a response schema inverts that: the moment the server adds `loyaltyTier`, every client validating against the published schema rejects the response. What was an additive change becomes a breaking one, and you learn about it from consumers rather than from your own tests. The balanced policy most teams land on: - **Requests: strict.** Rejecting an unknown key catches typos and silently-dropped fields early, and closes off mass-assignment-style surprises where a client sends a field you did not intend to accept. The cost is that adding a client-side field requires a spec update first, which is usually the right ordering anyway. - **Responses: tolerant.** Leave the default so the server can grow the payload additively. If you need to prevent accidental leakage, enforce that in the serializer, not in the published contract. Be aware that the strictness applies to whoever is *validating*. A gateway validating requests against the spec is the enforcement point; a schema that says `false` does nothing if nothing validates against it. ## Codegen behaviour Generators differ. Some map `additionalProperties: false` to a class that silently drops unknown fields; some to a strict deserializer that throws; some ignore the keyword entirely. Since the keyword's whole point is boundary behaviour, verify what your generator actually emits for each target language before treating it as enforcement. ## A quick decision guide - Free-form metadata bag? `additionalProperties: true` (or a value schema) — and say so in the description. - A typed map? `additionalProperties: {…}`. - Strict request validation with no composition? `additionalProperties: false` on a single flat schema. - Strict validation *with* composition, on OpenAPI 3.1? `unevaluatedProperties: false` at the top level. - A response contract you intend to grow? Leave it open.

  • How do you model a map of string to arbitrary object in OpenAPI?
    `type: object` with `additionalProperties` set to a Schema Object describing the value — for instance `additionalProperties: {$ref: '#/components/schemas/Widget'}` for a map of widgets. Keys are always strings in JSON, so only the value type needs describing.
  • How can you get strict validation on a schema built with allOf?
    On OpenAPI 3.1 use `unevaluatedProperties: false` at the composed level — a JSON Schema 2020-12 keyword that accounts for what sibling subschemas evaluated. On 3.0 there is no such keyword, so flatten the composition into a single schema and close that instead.
  • Why is additionalProperties: false risky on a response schema?
    It forbids the server from ever adding a field. Clients validating against the published contract reject the response the moment a new key appears, turning an ordinarily additive change into a breaking one — and you find out from consumers rather than from your own tests.
  • What is the default when additionalProperties is omitted?
    It behaves as `true`: undeclared keys are allowed and unconstrained. That is why a bare `type: object` with no other keywords accepts any object at all, which is fine for a genuine passthrough blob and lazy for anything else.

saying these in an interview costs you the question

  • Thinks it controls required properties
  • Sets false inside an allOf branch
  • Applies false to response schemas by default
  • Assumes omitting it means unknown keys are rejected
  • Believes every generator enforces it identically

context