# New fields are additive with defaults

- **Pattern:** `ab-000369` (`api-design.versioning-evolution.additive-changes`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000369
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000369)

## Why it matters

Adding a required field without a default to an existing request schema is an immediate breaking change: every existing consumer who does not send the new field starts receiving 400 errors without any version bump or deprecation notice. iso-25010:2011 compatibility.interoperability and maintainability.modifiability are both violated — the API's stability promise is broken the moment a required field lands in production. The failure mode is insidious because it appears as a client error (400), making it look like the consumer sent a bad request when the real cause is a server-side contract change.

## Severity rationale

High because adding a required field without a default immediately breaks all existing consumers without a version bump, causing silent client-side failures.

## Remediation

Follow the additive-only rule within a version: every new request field must be optional with a sensible default. In Zod:

```ts
// Before -- adding a required field (BREAKING):
const OrderSchema = z.object({
  items: z.array(ItemSchema),
  shippingMethod: z.string(),  // required, no default -- breaks existing clients
})

// After -- additive with default (safe):
const OrderSchema = z.object({
  items: z.array(ItemSchema),
  shippingMethod: z.string().default('standard'),  // optional with fallback
})
```

The non-breaking rules: new optional request fields with defaults, new response fields (add only, never remove), new endpoints, new optional query parameters. Any change that removes, renames, or makes previously-optional fields required requires a major version bump.

## Detection

- **ID:** `additive-changes`
- **Severity:** `high`
- **What to look for:** Examine recent changes to request schemas (git history of schema files, recent PRs, or changelog entries). Check whether new fields added to request bodies are optional with default values -- meaning existing API consumers who don't send the new field will not break. Look for: new required fields without defaults (breaking change), new required query parameters, removed fields (breaking), or changed field types (breaking). Also check response additions: new response fields should not break consumers that use strict deserialization.
- **Pass criteria:** Count all fields added to request schemas in recent changes. All recent additions to request schemas are optional with sensible defaults -- at least 100% of new fields must have defaults. No new required fields have been added to existing endpoints without a version bump. Response additions are backward-compatible (new fields added, no fields removed or type-changed).
- **Fail criteria:** New required fields have been added to existing request schemas without defaults or a version bump. Or fields have been removed from responses without deprecation. Or field types have changed (string to number, object to array) without a version bump.
- **Skip (N/A) when:** The API has had no changes since initial release (no git history of schema modifications). Or the API is pre-1.0 with an explicit instability warning.
- **Detail on fail:** Identify the breaking change (e.g., "`POST /api/orders` now requires `shippingMethod` field (added 2 weeks ago) with no default value. Existing consumers not sending this field get 400 errors. No version bump accompanied this change."). Max 500 chars.
- **Remediation:** When evolving an API, follow the additive-only rule within a version:

  ```ts
  // Before -- adding a required field (BREAKING):
  const OrderSchema = z.object({
    items: z.array(ItemSchema),
    shippingMethod: z.string(),  // required, no default -- BREAKS existing clients
  })

  // After -- additive with default:
  const OrderSchema = z.object({
    items: z.array(ItemSchema),
    shippingMethod: z.string().default('standard'),  // optional with default
  })
  ```

  Rules for non-breaking changes: new optional request fields with defaults, new response fields (add only, never remove), new endpoints, new optional query parameters.

---

## External references

- iso-25010 compatibility.interoperability
- iso-25010 maintainability.modifiability

Taxons: code-quality, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000369
