onChange.errors while the shopper is typing, and onError.fieldErrors after a rejected submit(). Both use the same FieldError shape, so a single renderer covers both surfaces.
Error shape
Gate submission on complete
The reliable signal for enabling your submit button is onChange.complete. It already accounts for required, format schemas, custom validate callbacks, and (for compound elements) sub-element state.
value !== null. Some elements emit partial typed shapes mid-typing; that is intentional and does not mean the value is valid.
Render inline errors from onChange.errors
FieldError[] appears on onError.fieldErrors after a rejected submit. Reuse the same renderer.
Render submit-time errors from onError.fieldErrors
- Switch on
code(the union is closed but new codes are added in minor releases, so keep adefaultbranch). - Render
messageas-is; it is already localized and shopper-safe. - Do not surface
cause; it carries raw diagnostics intended for observability.
Layer custom rules with validate
To add rules on top of the built-ins, leave validate unset and gate your business logic elsewhere (a server-side check, or inside your own onChange handler). To replace the built-ins entirely, pass a validate callback:
validate opts the element fully out of built-in checks; the callback owns the entire contract. See validate.
Bucket Checkout errors by source
Inside Checkout, every emitted FieldError carries a source string identifying which slot of the value envelope the field belongs to:
source values map directly to a top-level key on CheckoutElementValue (contact, billingAddress, shippingAddress). Two do not: paymentMethod errors belong to whichever payment method is currently active — read them off value.card, value.bank, value.applePay, or value.googlePay, keyed by value.method. customElement errors belong to a specific key inside value.customElements.
Bank key rotation
If your server rejects an encrypted bank payload with aUNKNOWN_KID error (an expected condition during key rotation), call overflow.invalidateBankFieldEncryptionCache() and retry bank.submit() once. The same pattern applies inside Checkout — invalidate, then call checkout.submit() again from the same handler: