Skip to content

Optional fields in response schemas should be nullable #369

Description

@mryhmln

Summary

The mock server null-initializes optional schema properties on create — matching database NULL behavior for unset columns. As a result, GET responses return null for any optional field not provided. Blueprint schemas don't currently mark optional fields as nullable: true, so generated SDK clients using strict type validation (e.g., Zod) reject the null values with type errors.

This mismatch surfaced in the steel thread: income records with unset incomeBasis, unearnedType, startDate, and endDate cause ZodError in listMemberIncome, breaking the income section form.

Expected behavior

null is a valid value for any optional field in a GET response. Consumers do not need to distinguish between null (field stored as NULL in the database) and absent (field not in the response) — both mean "not set."

Industry convention

This is the standard pattern across major REST APIs:

  • GitHub REST API — optional fields are returned as null when unset, not omitted. The schema marks them nullable: true.
  • Stripe API — optional fields that haven't been set are returned as null explicitly.
  • JSON:API spec — resource attributes may be null; absent and null are treated as distinct but both valid.
  • OpenAPI best practice — fields that may be stored as NULL and returned as null in responses should be declared nullable: true (OAS 3.0) or type: [T, 'null'] (OAS 3.1). Omitting nullable implies the field is never null.

Decision

All optional fields in blueprint response schemas should be marked nullable: true. The convention is:

  • Required fields — always present, never null.
  • Optional fields — may be absent on write; returned as null in GET responses when not set. Always nullable: true.

This should be documented in packages/contracts/patterns/api-patterns.yaml as a standing convention so states extending schemas follow the same pattern.

Scope

All optional properties across all blueprint schemas. Known immediate impact:

  • packages/contracts/schemas/common/income.yamlincomeBasis, unearnedType, startDate, endDate

All other schemas with optional non-required fields are affected but haven't surfaced yet because they haven't been exercised by a generated SDK client with strict response validation.

Short-term workaround

Adopters can mark the specific fields causing issues as nullable: true in their overlays while the blanket fix is applied to the blueprint.

Metadata

Metadata

Assignees

No one assigned

    Labels

    cross-cuttingConcerns that span multiple domains or the platform layer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions