diff --git a/e2e/src/api/specs/search.e2e-spec.ts b/e2e/src/api/specs/search.e2e-spec.ts
index f46c07e0b8..b49dac642e 100644
--- a/e2e/src/api/specs/search.e2e-spec.ts
+++ b/e2e/src/api/specs/search.e2e-spec.ts
@@ -339,6 +339,13 @@ describe('/search', () => {
         should: 'should search by model',
         deferred: () => ({ dto: { model: 'Canon EOS 7D' }, assets: [assetDenali] }),
       },
+      {
+        should: 'should allow searching the upload library (libraryId: null)',
+        deferred: () => ({
+          dto: { libraryId: null, size: 1 },
+          assets: [assetLast],
+        }),
+      },
     ];
 
     for (const { should, deferred } of searchTests) {
diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart
index 322373ee58..d77f2e7736 100644
--- a/mobile/openapi/lib/model/metadata_search_dto.dart
+++ b/mobile/openapi/lib/model/metadata_search_dto.dart
@@ -192,12 +192,6 @@ class MetadataSearchDto {
   ///
   String? lensModel;
 
-  ///
-  /// Please note: This property should have been non-nullable! Since the specification file
-  /// does not include a default value (using the "default:" property), however, the generated
-  /// source code must fall back to having a nullable type.
-  /// Consider adding a "default:" property in the specification file to hide this note.
-  ///
   String? libraryId;
 
   ///
diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart
index 0ff8cf6115..25927f4244 100644
--- a/mobile/openapi/lib/model/smart_search_dto.dart
+++ b/mobile/openapi/lib/model/smart_search_dto.dart
@@ -150,12 +150,6 @@ class SmartSearchDto {
   ///
   String? lensModel;
 
-  ///
-  /// Please note: This property should have been non-nullable! Since the specification file
-  /// does not include a default value (using the "default:" property), however, the generated
-  /// source code must fall back to having a nullable type.
-  /// Consider adding a "default:" property in the specification file to hide this note.
-  ///
   String? libraryId;
 
   ///
diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index e884b4fc29..17f74d33b0 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -9026,6 +9026,7 @@
           },
           "libraryId": {
             "format": "uuid",
+            "nullable": true,
             "type": "string"
           },
           "make": {
@@ -10140,6 +10141,7 @@
           },
           "libraryId": {
             "format": "uuid",
+            "nullable": true,
             "type": "string"
           },
           "make": {
diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts
index 43e24e939b..143ec74e65 100644
--- a/open-api/typescript-sdk/src/fetch-client.ts
+++ b/open-api/typescript-sdk/src/fetch-client.ts
@@ -697,7 +697,7 @@ export type MetadataSearchDto = {
     isOffline?: boolean;
     isVisible?: boolean;
     lensModel?: string;
-    libraryId?: string;
+    libraryId?: string | null;
     make?: string;
     model?: string;
     order?: AssetOrder;
@@ -768,7 +768,7 @@ export type SmartSearchDto = {
     isOffline?: boolean;
     isVisible?: boolean;
     lensModel?: string;
-    libraryId?: string;
+    libraryId?: string | null;
     make?: string;
     model?: string;
     page?: number;
diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts
index 5927aa86fc..59bb95b475 100644
--- a/server/src/dtos/search.dto.ts
+++ b/server/src/dtos/search.dto.ts
@@ -9,8 +9,8 @@ import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
 import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
 
 class BaseSearchDto {
-  @ValidateUUID({ optional: true })
-  libraryId?: string;
+  @ValidateUUID({ optional: true, nullable: true })
+  libraryId?: string | null;
 
   @IsString()
   @IsNotEmpty()
diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts
index d5382a04fa..c84b56c62e 100644
--- a/server/src/interfaces/search.interface.ts
+++ b/server/src/interfaces/search.interface.ts
@@ -45,7 +45,7 @@ export interface SearchAssetIDOptions {
 
 export interface SearchUserIdOptions {
   deviceId?: string;
-  libraryId?: string;
+  libraryId?: string | null;
   userIds?: string[];
 }
 
diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts
index 65456e8192..944978bddd 100644
--- a/server/src/utils/database.ts
+++ b/server/src/utils/database.ts
@@ -52,6 +52,11 @@ export function searchAssetBuilder(
   }
 
   const id = _.pick(options, ['checksum', 'deviceAssetId', 'deviceId', 'id', 'libraryId']);
+
+  if (id.libraryId === null) {
+    id.libraryId = IsNull() as unknown as string;
+  }
+
   builder.andWhere(_.omitBy(id, _.isUndefined));
 
   if (options.userIds) {
diff --git a/server/src/validation.ts b/server/src/validation.ts
index 6fb1684c06..4a8d8db866 100644
--- a/server/src/validation.ts
+++ b/server/src/validation.ts
@@ -80,13 +80,13 @@ export function Optional({ nullable, ...validationOptions }: OptionalOptions = {
   return ValidateIf((object: any, v: any) => v !== undefined, validationOptions);
 }
 
-type UUIDOptions = { optional?: boolean; each?: boolean };
+type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean };
 export const ValidateUUID = (options?: UUIDOptions) => {
-  const { optional, each } = { optional: false, each: false, ...options };
+  const { optional, each, nullable } = { optional: false, each: false, nullable: false, ...options };
   return applyDecorators(
     IsUUID('4', { each }),
     ApiProperty({ format: 'uuid' }),
-    optional ? Optional() : IsNotEmpty(),
+    optional ? Optional({ nullable }) : IsNotEmpty(),
     each ? IsArray() : IsString(),
   );
 };