Repository navigation
[Flight] Support Temporal objects - #38193
Open
madebyjulz wants to merge 1 commit into
Open
madebyjulz wants to merge 1 commit into
madebyjulz wants to merge 1 commit into
Conversation
Temporal objects have a toJSON method, so today they arrive on the other
side of Flight as plain strings, with a DEV warning that they're not
supported.
This adds support for all eight Temporal types (Instant, ZonedDateTime,
PlainDate, PlainTime, PlainDateTime, PlainYearMonth, PlainMonthDay and
Duration) in both directions: from Server Components to Client
Components, and as arguments to Server Functions. It's behind a new
experimental flag, enableFlightTemporal.
Like Date and BigInt, they're encoded inline as a tagged string: "$t", a
character that identifies the type, then the result of toJSON(). The type
has to be encoded because it can't always be inferred from the string.
E.g. a PlainYearMonth in a non-ISO calendar is formatted as a full date.
Objects are identified by their Symbol.toStringTag brand instead of
instanceof so that this works with a polyfill that isn't installed on the
global, and with objects from other Realms. They're revived using the
global Temporal. If it isn't available, decoding throws an error that
says to load a polyfill.
A ZonedDateTime is revived with {offset: 'use'} so that it represents the
same exact time even if the time zone database of the receiver has
different rules than the sender's. The reply decoder rejects Temporal
strings longer than 300 characters before parsing them, like the existing
limit for BigInt.
The DEV debug info serializer is unchanged.
This also adds temporal-polyfill as a dev dependency since the Node.js
version used in CI doesn't have Temporal.
willbyers1
approved these changes
Nov 10, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Temporal objects can't be passed through Flight today. They have a
toJSONmethod, so they arrive on the other side as plain strings, with a DEV warning that they're not supported (#34142). As Temporal replacesDate, that means manually callingtoString()andTemporal.*.from()at every boundary.This adds support for all eight Temporal types (
Instant,ZonedDateTime,PlainDate,PlainTime,PlainDateTime,PlainYearMonth,PlainMonthDay,Duration) in both directions: from Server Components to Client Components, and as arguments to Server Functions. It's behind a new experimental flag,enableFlightTemporal.Wire format. Like
Date($D) andBigInt($n), they're encoded inline as a tagged string:$t, a character that identifies the type, then the result oftoJSON(). The type has to be encoded because it can't always be inferred from the string. For example, aPlainYearMonthin a non-ISO calendar is formatted as a full date.Encoding. Objects are identified by their
Symbol.toStringTagbrand rather thaninstanceof, so this works with a polyfill that isn't installed on the global (the case in the issue) and with objects from other Realms. On the server this happens wheretoJSONis already called, so plain objects don't pay for the check. In the reply encoder,JSON.stringifyhas already calledtoJSON, so strings are compared to their original value only if they start with a character that a Temporal string can start with, similar to the existing trailingZcheck forDate.Decoding. Objects are revived using the global
Temporal. If it isn't available, decoding throws an error that says to load a polyfill.ZonedDateTimeis revived with{offset: 'use'}so that it represents the same exact time even if the time zone database of the receiver has different rules than the sender's, which is otherwise an error. The reply decoder rejects Temporal strings longer than 300 characters before parsing them, like the existing limit forBigInt.Not included. The DEV debug info serializer is unchanged, so Temporal objects in replayed console logs and in Server Component props in DevTools look the same as before. Supporting them there needs a decision on what to do when the client has no
Temporal, since debug info is sent regardless of whether the app passes Temporal objects to the client.This also adds
temporal-polyfillas a dev dependency since the Node.js version used in CI doesn't have Temporal. It's pinned to 0.3.x because 1.x is ESM-only and can't be loaded by Jest here.How did you test this change?
Added 14 tests to
ReactFlight-test.jsandReactFlightDOMReply-test.jscovering: all eight types in both directions, non-ISO calendars, top-level values, values resolved by a Promise, objects from a polyfill that isn't the global one, strings that look like Temporal objects staying strings, theZonedDateTimeoffset behavior, the error whenTemporalis missing, the length limit and malformed input in a reply, and the unchanged behavior when the flag is off.All pass on Node.js 24, where the polyfill is the global
Temporal. The new tests also pass on Node.js 26 with the native implementation.The new error codes (613 to 615) were added to
codes.jsonby hand.