diff --git a/api-report/firestore.api.md b/api-report/firestore.api.md
index 39ea33620..73e85878b 100644
--- a/api-report/firestore.api.md
+++ b/api-report/firestore.api.md
@@ -1014,6 +1014,7 @@ abstract class Expression implements firestore.Pipelines.Expression, HasUserData
ifError(catchValue: unknown): FunctionExpression;
isAbsent(): BooleanExpression;
isError(): BooleanExpression;
+ isType(type: Type): BooleanExpression;
join(delimiterExpression: Expression): Expression;
join(delimiter: string): Expression;
length(): FunctionExpression;
@@ -1508,6 +1509,12 @@ function isAbsent(field: string): BooleanExpression;
// @beta
function isError(value: Expression): BooleanExpression;
+// @beta
+function isType(fieldName: string, type: Type): BooleanExpression;
+
+// @beta
+function isType(expression: Expression, type: Type): BooleanExpression;
+
// @beta
function join(arrayFieldName: string, delimiter: string): Expression;
@@ -1940,6 +1947,8 @@ declare namespace Pipelines {
currentTimestamp,
arrayConcat,
type,
+ isType,
+ Type,
timestampTruncate,
split
}
@@ -2660,6 +2669,9 @@ function trunc(fieldName: string, decimalPlaces: number | Expression): FunctionE
// @beta
function trunc(expression: Expression, decimalPlaces: number | Expression): FunctionExpression;
+// @beta
+type Type = 'null' | 'array' | 'boolean' | 'bytes' | 'timestamp' | 'geo_point' | 'number' | 'int32' | 'int64' | 'float64' | 'decimal128' | 'map' | 'reference' | 'string' | 'vector' | 'max_key' | 'min_key' | 'object_id' | 'regex' | 'request_timestamp';
+
// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
//
diff --git a/dev/src/pipelines/expression.ts b/dev/src/pipelines/expression.ts
index 8dc924891..01ee1e4fc 100644
--- a/dev/src/pipelines/expression.ts
+++ b/dev/src/pipelines/expression.ts
@@ -29,6 +29,39 @@ import {
import {HasUserData, Serializer, validateUserInput} from '../serializer';
import {cast} from '../util';
+/**
+ * @beta
+ *
+ * An enumeration of the different types generated by the Firestore backend.
+ *
+ *
+ * - Numerics evaluate directly to backend representation (`int64` or `float64`), not JS `number`.
+ * - JavaScript `Date` and firestore `Timestamp` objects strictly evaluate to `'timestamp'`.
+ * - Advanced configurations parsing backend types (such as `decimal128`, `max_key` or `min_key` from BSON) are also incorporated in this union string type. Note that `decimal128` is a backend-only numeric type that the JavaScript SDK cannot create natively, but can be evaluated in pipelines.
+ *
+ */
+export type Type =
+ | 'null'
+ | 'array'
+ | 'boolean'
+ | 'bytes'
+ | 'timestamp'
+ | 'geo_point'
+ | 'number'
+ | 'int32'
+ | 'int64'
+ | 'float64'
+ | 'decimal128'
+ | 'map'
+ | 'reference'
+ | 'string'
+ | 'vector'
+ | 'max_key'
+ | 'min_key'
+ | 'object_id'
+ | 'regex'
+ | 'request_timestamp';
+
/**
* @beta
* Represents an expression that can be evaluated to a value within the execution of a `Pipeline`.
@@ -2497,6 +2530,28 @@ export abstract class Expression
return new FunctionExpression('type', [this]);
}
+ /**
+ * @beta
+ * Creates an expression that checks if the result of this expression is of the given type.
+ *
+ * @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
+ *
+ * @example
+ * ```typescript
+ * // Check if the 'price' field is specifically an integer (not just 'number')
+ * field('price').isType('int64');
+ * ```
+ *
+ * @param type The type to check for.
+ * @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
+ */
+ isType(type: Type): BooleanExpression {
+ return new FunctionExpression('is_type', [
+ this,
+ constant(type),
+ ]).asBoolean();
+ }
+
// TODO(new-expression): Add new expression method definitions above this line
/**
@@ -8456,6 +8511,48 @@ export function type(
return fieldOrExpression(fieldNameOrExpression).type();
}
+/**
+ * @beta
+ * Creates an expression that checks if the value in the specified field is of the given type.
+ *
+ * @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
+ *
+ * @example
+ * ```typescript
+ * // Check if the 'price' field is a floating point number (evaluating to true inside pipeline conditionals)
+ * isType('price', 'float64');
+ * ```
+ *
+ * @param fieldName The name of the field to check.
+ * @param type The type to check for.
+ * @returns A new `BooleanExpression` that evaluates to true if the field's value is of the given type, false otherwise.
+ */
+export function isType(fieldName: string, type: Type): BooleanExpression;
+
+/**
+ * @beta
+ * Creates an expression that checks if the result of an expression is of the given type.
+ *
+ * @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
+ *
+ * @example
+ * ```typescript
+ * // Check if the result of a calculation is a number
+ * isType(add('count', 1), 'number')
+ * ```
+ *
+ * @param expression The expression to check.
+ * @param type The type to check for.
+ * @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
+ */
+export function isType(expression: Expression, type: Type): BooleanExpression;
+export function isType(
+ fieldNameOrExpression: string | Expression,
+ type: Type,
+): BooleanExpression {
+ return fieldOrExpression(fieldNameOrExpression).isType(type);
+}
+
// TODO(new-expression): Add new top-level expression function definitions above this line
/**
diff --git a/dev/src/pipelines/index.ts b/dev/src/pipelines/index.ts
index a1ee5363c..91333c94b 100644
--- a/dev/src/pipelines/index.ts
+++ b/dev/src/pipelines/index.ts
@@ -130,6 +130,8 @@ export {
currentTimestamp,
arrayConcat,
type,
+ isType,
+ Type,
timestampTruncate,
split,
// TODO(new-expression): Add new expression exports above this line
diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts
index e6dad1206..783d8280a 100644
--- a/dev/system-test/pipeline.ts
+++ b/dev/system-test/pipeline.ts
@@ -124,6 +124,7 @@ import {
currentTimestamp,
arrayConcat,
type,
+ isType,
timestampTruncate,
split,
// TODO(new-expression): add new expression imports above this line
@@ -4386,6 +4387,74 @@ describe.skipClassic('Pipeline class', () => {
});
});
+ it('supports isType', async () => {
+ const result = await firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .replaceWith(
+ map({
+ int: constant(1),
+ float: constant(1.1),
+ str: constant('a string'),
+ bool: constant(true),
+ null: constant(null),
+ geoPoint: constant(new GeoPoint(0.1, 0.2)),
+ timestamp: constant(new Timestamp(123456, 0)),
+ bytes: constant(new Uint8Array([1, 2, 3])),
+ docRef: constant(firestore.doc(`${randomCol.path}/bar`)),
+ vector: constant(FieldValue.vector([1, 2, 3])),
+ map: map({
+ numberK: 1,
+ stringK: 'a string',
+ }),
+ array: array([1, '2', true]),
+ }),
+ )
+ .select(
+ isType(field('int'), 'int64').as('isInt64'),
+ isType(field('int'), 'number').as('isInt64IsNumber'),
+ isType(field('int'), 'decimal128').as('isInt64IsDecimal128'),
+ field('float').isType('float64').as('isFloat64'),
+ field('float').isType('number').as('isFloat64IsNumber'),
+ field('float').isType('decimal128').as('isFloat64IsDecimal128'),
+ isType('str', 'string').as('isStr'),
+ isType('int', 'string').as('isNumStr'),
+ field('bool').isType('boolean').as('isBool'),
+ isType('null', 'null').as('isNull'),
+ field('geoPoint').isType('geo_point').as('isGeoPoint'),
+ isType('timestamp', 'timestamp').as('isTimestamp'),
+ field('bytes').isType('bytes').as('isBytes'),
+ isType('docRef', 'reference').as('isDocRef'),
+ field('vector').isType('vector').as('isVector'),
+ isType('map', 'map').as('isMap'),
+ field('array').isType('array').as('isArray'),
+ field('str').isType('int64').as('isStrNum'),
+ )
+ .limit(1)
+ .execute();
+
+ expectResults(result, {
+ isInt64: true,
+ isInt64IsNumber: true,
+ isInt64IsDecimal128: false,
+ isFloat64: true,
+ isFloat64IsNumber: true,
+ isFloat64IsDecimal128: false,
+ isStr: true,
+ isNumStr: false,
+ isBool: true,
+ isNull: true,
+ isGeoPoint: true,
+ isTimestamp: true,
+ isBytes: true,
+ isDocRef: true,
+ isVector: true,
+ isMap: true,
+ isArray: true,
+ isStrNum: false,
+ });
+ });
+
// TODO(new-expression): Add new expression tests above this line
});
diff --git a/types/firestore.d.ts b/types/firestore.d.ts
index 4ae21ff7f..2272224ed 100644
--- a/types/firestore.d.ts
+++ b/types/firestore.d.ts
@@ -5136,6 +5136,23 @@ declare namespace FirebaseFirestore {
*/
type(): FunctionExpression;
+ /**
+ * @beta
+ * Creates an expression that checks if the result of this expression is of the given type.
+ *
+ * @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
+ *
+ * @example
+ * ```typescript
+ * // Check if the 'price' field is specifically an integer (not just 'number')
+ * field('price').isType('int64');
+ * ```
+ *
+ * @param type The type to check for.
+ * @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
+ */
+ isType(type: Type): BooleanExpression;
+
// TODO(new-expression): Add new expression method declarations above this line
/**
* @beta
@@ -10128,6 +10145,39 @@ declare namespace FirebaseFirestore {
timezone?: string | Expression,
): FunctionExpression;
+ /**
+ * @beta
+ *
+ * An enumeration of the different types generated by the Firestore backend.
+ *
+ *
+ * - Numerics evaluate directly to backend representation (`int64` or `float64`), not JS `number`.
+ * - JavaScript `Date` and firestore `Timestamp` objects strictly evaluate to `'timestamp'`.
+ * - Advanced configurations parsing backend types (such as `decimal128`, `max_key` or `min_key` from BSON) are also incorporated in this union string type. Note that `decimal128` is a backend-only numeric type that the JavaScript SDK cannot create natively, but can be evaluated in pipelines.
+ *
+ */
+ export type Type =
+ | 'null'
+ | 'array'
+ | 'boolean'
+ | 'bytes'
+ | 'timestamp'
+ | 'geo_point'
+ | 'number'
+ | 'int32'
+ | 'int64'
+ | 'float64'
+ | 'decimal128'
+ | 'map'
+ | 'reference'
+ | 'string'
+ | 'vector'
+ | 'max_key'
+ | 'min_key'
+ | 'object_id'
+ | 'regex'
+ | 'request_timestamp';
+
/**
* @beta
* Creates an expression that returns the data type of the data in the specified field.
@@ -10155,6 +10205,44 @@ declare namespace FirebaseFirestore {
*/
export function type(expression: Expression): FunctionExpression;
+ /**
+ * @beta
+ * Creates an expression that checks if the value in the specified field is of the given type.
+ *
+ * @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
+ *
+ * @example
+ * ```typescript
+ * // Check if the 'price' field is a floating point number (evaluating to true inside pipeline conditionals)
+ * isType('price', 'float64');
+ * ```
+ *
+ * @param fieldName The name of the field to check.
+ * @param type The type to check for.
+ * @returns A new `BooleanExpression` that evaluates to true if the field's value is of the given type, false otherwise.
+ */
+ export function isType(fieldName: string, type: Type): BooleanExpression;
+ /**
+ * @beta
+ * Creates an expression that checks if the result of an expression is of the given type.
+ *
+ * @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
+ *
+ * @example
+ * ```typescript
+ * // Check if the result of a calculation is a number
+ * isType(add('count', 1), 'number')
+ * ```
+ *
+ * @param expression The expression to check.
+ * @param type The type to check for.
+ * @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
+ */
+ export function isType(
+ expression: Expression,
+ type: Type,
+ ): BooleanExpression;
+
// TODO(new-expression): Add new top-level expression function declarations above this line
/**
* @beta