Skip to content

[SDKs] Align how the SDKs handle unregistered custom fields #1121

Description

@laurabelinfante

Summary

The TypeScript SDK passes unregistered custom fields through. Pydantic's default drops unknown keys, so the Python SDK silently loses them. extensions/types.py sets extra="allow" for the extension types, but nothing else does, so behavior differs by model as well as by language.

The intended behavior is to preserve unregistered custom fields, so the Python SDK should be updated to match TypeScript. The harder part is making sure unregistered custom fields still conform to the base custom field shape.

Acceptance criteria

  • Current behavior in both SDKs is documented on this ticket
  • The Python SDK preserves unregistered custom fields, matching TypeScript
  • Unregistered custom fields are validated against the base custom field shape in both SDKs
  • Tests cover unregistered custom fields across both SDKs' supported custom-field extension paths
  • Public SDK documentation reflects the behavior
  • The Python and TypeScript changes are included in the [Py SDK] v0.9.0 release and [TS SDK] v0.9.0 release candidates, respectively

Release coordination

This ticket spans both SDKs. GitHub allows only one milestone per issue, so [Py SDK] v0.9.0 release is the primary milestone. Completion also requires the coordinated [TS SDK] v0.9.0 release; do not close this ticket until both package changes are release-ready.

Activity

  1. added
    breakingChange that breaks the published API. Belongs on a hold branch.
    sdkIssue or PR related to our SDKs
    pythonIssue or PR related to Python tooling
    on Aug 27, 2026
  2. added
    typescriptIssue or PR related to TypeScript tooling
    py-sdkRelated to Python SDK
    ts-sdkRelated to TypeScript SDK
    on Aug 27, 2026
  3. changed the title [-][SDK] Align how the SDKs handle unregistered custom fields[/-] [+][SDKs] Align how the SDKs handle unregistered custom fields[/+] on Aug 27, 2026
  4. laurabelinfante commented on Oct 13, 2026

    @laurabelinfante
    CollaboratorAuthor

    The TypeScript side of this landed in #1290: the withCustomFields catchall keeps unregistered custom fields and validates them against the base custom field shape. Leaving this open for the matching Python SDK change.

  5. SnowboardTechie commented on Oct 14, 2026

    @SnowboardTechie
    Collaborator

    Yeah, here's where both SDKs stand before the Python change, for the first AC.

    TypeScript (on main since #1290 and #1299)

    • withCustomFields() uses .catchall(CustomFieldSchema), so unregistered entries are kept and validated against the base custom field shape on Opportunity and AwdRecipientIndividual, through definePlugin() and getClient() too.
    • A missing name, fieldType, or value, an invalid fieldType or schema URL, and a null, string, or array in place of the entry are all rejected. value: null is accepted.

    Python (main at 1e1525c4)

    • Bare OpportunityBase (dict[str, CustomField]) already keeps and validates unregistered entries.
    • CustomFieldSet containers, which every plugin uses (including the grants.gov plugin), silently drop them, valid or not.
    • with_custom_fields() keeps them, but only validates object entries that have a fieldType. It fills a missing name from the map key, passes other malformed entries through unchecked, and rewrites the caller's input dict while parsing.
    • get_custom_field_value() snake_cases the key before looking up an unregistered entry, so vendorPayload comes back as None or as vendor_payload's value.

    In both SDKs, unknown keys inside an entry are dropped, and the map key and nested value are otherwise left alone.

    #1308 gives both Python containers a typed dict[str, CustomField] extras slot, so unregistered entries are kept under their exact wire key and validated by the same CustomField model the bare base uses. Unregistered entries now need a complete envelope, which is the breaking part for anyone relying on the old with_custom_fields() leniency. It also makes get_custom_field_value() look up unregistered entries by their exact key. TypeScript doesn't change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

breakingChange that breaks the published API. Belongs on a hold branch.py-sdkRelated to Python SDKpythonIssue or PR related to Python toolingsdkIssue or PR related to our SDKsts-sdkRelated to TypeScript SDKtypescriptIssue or PR related to TypeScript tooling

Type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions