diff --git a/e2e/src/api/specs/album.e2e-spec.ts b/e2e/src/api/specs/album.e2e-spec.ts
index 2310b4718c..de320ee95f 100644
--- a/e2e/src/api/specs/album.e2e-spec.ts
+++ b/e2e/src/api/specs/album.e2e-spec.ts
@@ -1,6 +1,7 @@
 import {
   AlbumResponseDto,
   AssetFileUploadResponseDto,
+  AssetOrder,
   LoginResponseDto,
   SharedLinkType,
   deleteUser,
@@ -353,6 +354,7 @@ describe('/album', () => {
         assetCount: 0,
         owner: expect.objectContaining({ email: user1.userEmail }),
         isActivityEnabled: true,
+        order: AssetOrder.Desc,
       });
     });
   });
diff --git a/mobile/openapi/doc/AlbumResponseDto.md b/mobile/openapi/doc/AlbumResponseDto.md
index bc00d30af1..dd4a94e883 100644
--- a/mobile/openapi/doc/AlbumResponseDto.md
+++ b/mobile/openapi/doc/AlbumResponseDto.md
@@ -19,6 +19,7 @@ Name | Type | Description | Notes
 **id** | **String** |  | 
 **isActivityEnabled** | **bool** |  | 
 **lastModifiedAssetTimestamp** | [**DateTime**](DateTime.md) |  | [optional] 
+**order** | [**AssetOrder**](AssetOrder.md) |  | [optional] 
 **owner** | [**UserResponseDto**](UserResponseDto.md) |  | 
 **ownerId** | **String** |  | 
 **shared** | **bool** |  | 
diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md
index c65e6a605f..1aaf195f3a 100644
--- a/mobile/openapi/doc/AssetApi.md
+++ b/mobile/openapi/doc/AssetApi.md
@@ -834,7 +834,7 @@ Name | Type | Description  | Notes
 [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
 
 # **getTimeBucket**
-> List<AssetResponseDto> getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked)
+> List<AssetResponseDto> getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked)
 
 
 
@@ -864,13 +864,14 @@ final isArchived = true; // bool |
 final isFavorite = true; // bool | 
 final isTrashed = true; // bool | 
 final key = key_example; // String | 
+final order = ; // AssetOrder | 
 final personId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | 
 final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | 
 final withPartners = true; // bool | 
 final withStacked = true; // bool | 
 
 try {
-    final result = api_instance.getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked);
+    final result = api_instance.getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked);
     print(result);
 } catch (e) {
     print('Exception when calling AssetApi->getTimeBucket: $e\n');
@@ -888,6 +889,7 @@ Name | Type | Description  | Notes
  **isFavorite** | **bool**|  | [optional] 
  **isTrashed** | **bool**|  | [optional] 
  **key** | **String**|  | [optional] 
+ **order** | [**AssetOrder**](.md)|  | [optional] 
  **personId** | **String**|  | [optional] 
  **userId** | **String**|  | [optional] 
  **withPartners** | **bool**|  | [optional] 
@@ -909,7 +911,7 @@ Name | Type | Description  | Notes
 [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
 
 # **getTimeBuckets**
-> List<TimeBucketResponseDto> getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked)
+> List<TimeBucketResponseDto> getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked)
 
 
 
@@ -938,13 +940,14 @@ final isArchived = true; // bool |
 final isFavorite = true; // bool | 
 final isTrashed = true; // bool | 
 final key = key_example; // String | 
+final order = ; // AssetOrder | 
 final personId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | 
 final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | 
 final withPartners = true; // bool | 
 final withStacked = true; // bool | 
 
 try {
-    final result = api_instance.getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked);
+    final result = api_instance.getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked);
     print(result);
 } catch (e) {
     print('Exception when calling AssetApi->getTimeBuckets: $e\n');
@@ -961,6 +964,7 @@ Name | Type | Description  | Notes
  **isFavorite** | **bool**|  | [optional] 
  **isTrashed** | **bool**|  | [optional] 
  **key** | **String**|  | [optional] 
+ **order** | [**AssetOrder**](.md)|  | [optional] 
  **personId** | **String**|  | [optional] 
  **userId** | **String**|  | [optional] 
  **withPartners** | **bool**|  | [optional] 
diff --git a/mobile/openapi/doc/UpdateAlbumDto.md b/mobile/openapi/doc/UpdateAlbumDto.md
index 4ded87d1bf..89edf1c6ef 100644
--- a/mobile/openapi/doc/UpdateAlbumDto.md
+++ b/mobile/openapi/doc/UpdateAlbumDto.md
@@ -12,6 +12,7 @@ Name | Type | Description | Notes
 **albumThumbnailAssetId** | **String** |  | [optional] 
 **description** | **String** |  | [optional] 
 **isActivityEnabled** | **bool** |  | [optional] 
+**order** | [**AssetOrder**](AssetOrder.md) |  | [optional] 
 
 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 
diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart
index 786129b457..b0395bfcbe 100644
--- a/mobile/openapi/lib/api/asset_api.dart
+++ b/mobile/openapi/lib/api/asset_api.dart
@@ -852,6 +852,8 @@ class AssetApi {
   ///
   /// * [String] key:
   ///
+  /// * [AssetOrder] order:
+  ///
   /// * [String] personId:
   ///
   /// * [String] userId:
@@ -859,7 +861,7 @@ class AssetApi {
   /// * [bool] withPartners:
   ///
   /// * [bool] withStacked:
-  Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
+  Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
     // ignore: prefer_const_declarations
     final path = r'/asset/time-bucket';
 
@@ -885,6 +887,9 @@ class AssetApi {
     if (key != null) {
       queryParams.addAll(_queryParams('', 'key', key));
     }
+    if (order != null) {
+      queryParams.addAll(_queryParams('', 'order', order));
+    }
     if (personId != null) {
       queryParams.addAll(_queryParams('', 'personId', personId));
     }
@@ -930,6 +935,8 @@ class AssetApi {
   ///
   /// * [String] key:
   ///
+  /// * [AssetOrder] order:
+  ///
   /// * [String] personId:
   ///
   /// * [String] userId:
@@ -937,8 +944,8 @@ class AssetApi {
   /// * [bool] withPartners:
   ///
   /// * [bool] withStacked:
-  Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
-    final response = await getTimeBucketWithHttpInfo(size, timeBucket,  albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
+  Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
+    final response = await getTimeBucketWithHttpInfo(size, timeBucket,  albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
     if (response.statusCode >= HttpStatus.badRequest) {
       throw ApiException(response.statusCode, await _decodeBodyBytes(response));
     }
@@ -970,6 +977,8 @@ class AssetApi {
   ///
   /// * [String] key:
   ///
+  /// * [AssetOrder] order:
+  ///
   /// * [String] personId:
   ///
   /// * [String] userId:
@@ -977,7 +986,7 @@ class AssetApi {
   /// * [bool] withPartners:
   ///
   /// * [bool] withStacked:
-  Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
+  Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
     // ignore: prefer_const_declarations
     final path = r'/asset/time-buckets';
 
@@ -1003,6 +1012,9 @@ class AssetApi {
     if (key != null) {
       queryParams.addAll(_queryParams('', 'key', key));
     }
+    if (order != null) {
+      queryParams.addAll(_queryParams('', 'order', order));
+    }
     if (personId != null) {
       queryParams.addAll(_queryParams('', 'personId', personId));
     }
@@ -1045,6 +1057,8 @@ class AssetApi {
   ///
   /// * [String] key:
   ///
+  /// * [AssetOrder] order:
+  ///
   /// * [String] personId:
   ///
   /// * [String] userId:
@@ -1052,8 +1066,8 @@ class AssetApi {
   /// * [bool] withPartners:
   ///
   /// * [bool] withStacked:
-  Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
-    final response = await getTimeBucketsWithHttpInfo(size,  albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
+  Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
+    final response = await getTimeBucketsWithHttpInfo(size,  albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
     if (response.statusCode >= HttpStatus.badRequest) {
       throw ApiException(response.statusCode, await _decodeBodyBytes(response));
     }
diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart
index 43e24f87be..d764028558 100644
--- a/mobile/openapi/lib/model/album_response_dto.dart
+++ b/mobile/openapi/lib/model/album_response_dto.dart
@@ -24,6 +24,7 @@ class AlbumResponseDto {
     required this.id,
     required this.isActivityEnabled,
     this.lastModifiedAssetTimestamp,
+    this.order,
     required this.owner,
     required this.ownerId,
     required this.shared,
@@ -66,6 +67,14 @@ class AlbumResponseDto {
   ///
   DateTime? lastModifiedAssetTimestamp;
 
+  ///
+  /// 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.
+  ///
+  AssetOrder? order;
+
   UserResponseDto owner;
 
   String ownerId;
@@ -97,6 +106,7 @@ class AlbumResponseDto {
     other.id == id &&
     other.isActivityEnabled == isActivityEnabled &&
     other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp &&
+    other.order == order &&
     other.owner == owner &&
     other.ownerId == ownerId &&
     other.shared == shared &&
@@ -118,6 +128,7 @@ class AlbumResponseDto {
     (id.hashCode) +
     (isActivityEnabled.hashCode) +
     (lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) +
+    (order == null ? 0 : order!.hashCode) +
     (owner.hashCode) +
     (ownerId.hashCode) +
     (shared.hashCode) +
@@ -126,7 +137,7 @@ class AlbumResponseDto {
     (updatedAt.hashCode);
 
   @override
-  String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]';
+  String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]';
 
   Map<String, dynamic> toJson() {
     final json = <String, dynamic>{};
@@ -152,6 +163,11 @@ class AlbumResponseDto {
       json[r'lastModifiedAssetTimestamp'] = this.lastModifiedAssetTimestamp!.toUtc().toIso8601String();
     } else {
     //  json[r'lastModifiedAssetTimestamp'] = null;
+    }
+    if (this.order != null) {
+      json[r'order'] = this.order;
+    } else {
+    //  json[r'order'] = null;
     }
       json[r'owner'] = this.owner;
       json[r'ownerId'] = this.ownerId;
@@ -185,6 +201,7 @@ class AlbumResponseDto {
         id: mapValueOfType<String>(json, r'id')!,
         isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
         lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''),
+        order: AssetOrder.fromJson(json[r'order']),
         owner: UserResponseDto.fromJson(json[r'owner'])!,
         ownerId: mapValueOfType<String>(json, r'ownerId')!,
         shared: mapValueOfType<bool>(json, r'shared')!,
diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart
index dfe245aaf8..d9408cedfb 100644
--- a/mobile/openapi/lib/model/update_album_dto.dart
+++ b/mobile/openapi/lib/model/update_album_dto.dart
@@ -17,6 +17,7 @@ class UpdateAlbumDto {
     this.albumThumbnailAssetId,
     this.description,
     this.isActivityEnabled,
+    this.order,
   });
 
   ///
@@ -51,12 +52,21 @@ class UpdateAlbumDto {
   ///
   bool? isActivityEnabled;
 
+  ///
+  /// 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.
+  ///
+  AssetOrder? order;
+
   @override
   bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto &&
     other.albumName == albumName &&
     other.albumThumbnailAssetId == albumThumbnailAssetId &&
     other.description == description &&
-    other.isActivityEnabled == isActivityEnabled;
+    other.isActivityEnabled == isActivityEnabled &&
+    other.order == order;
 
   @override
   int get hashCode =>
@@ -64,10 +74,11 @@ class UpdateAlbumDto {
     (albumName == null ? 0 : albumName!.hashCode) +
     (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
     (description == null ? 0 : description!.hashCode) +
-    (isActivityEnabled == null ? 0 : isActivityEnabled!.hashCode);
+    (isActivityEnabled == null ? 0 : isActivityEnabled!.hashCode) +
+    (order == null ? 0 : order!.hashCode);
 
   @override
-  String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description, isActivityEnabled=$isActivityEnabled]';
+  String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description, isActivityEnabled=$isActivityEnabled, order=$order]';
 
   Map<String, dynamic> toJson() {
     final json = <String, dynamic>{};
@@ -91,6 +102,11 @@ class UpdateAlbumDto {
     } else {
     //  json[r'isActivityEnabled'] = null;
     }
+    if (this.order != null) {
+      json[r'order'] = this.order;
+    } else {
+    //  json[r'order'] = null;
+    }
     return json;
   }
 
@@ -106,6 +122,7 @@ class UpdateAlbumDto {
         albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
         description: mapValueOfType<String>(json, r'description'),
         isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled'),
+        order: AssetOrder.fromJson(json[r'order']),
       );
     }
     return null;
diff --git a/mobile/openapi/test/album_response_dto_test.dart b/mobile/openapi/test/album_response_dto_test.dart
index 933f77c196..5c79e5d2fc 100644
--- a/mobile/openapi/test/album_response_dto_test.dart
+++ b/mobile/openapi/test/album_response_dto_test.dart
@@ -71,6 +71,11 @@ void main() {
       // TODO
     });
 
+    // AssetOrder order
+    test('to test the property `order`', () async {
+      // TODO
+    });
+
     // UserResponseDto owner
     test('to test the property `owner`', () async {
       // TODO
diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart
index 846a5998cc..d210d0e4d9 100644
--- a/mobile/openapi/test/asset_api_test.dart
+++ b/mobile/openapi/test/asset_api_test.dart
@@ -95,12 +95,12 @@ void main() {
       // TODO
     });
 
-    //Future<List<AssetResponseDto>> getTimeBucket(TimeBucketSize size, String timeBucket, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, String personId, String userId, bool withPartners, bool withStacked }) async
+    //Future<List<AssetResponseDto>> getTimeBucket(TimeBucketSize size, String timeBucket, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, AssetOrder order, String personId, String userId, bool withPartners, bool withStacked }) async
     test('test getTimeBucket', () async {
       // TODO
     });
 
-    //Future<List<TimeBucketResponseDto>> getTimeBuckets(TimeBucketSize size, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, String personId, String userId, bool withPartners, bool withStacked }) async
+    //Future<List<TimeBucketResponseDto>> getTimeBuckets(TimeBucketSize size, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, AssetOrder order, String personId, String userId, bool withPartners, bool withStacked }) async
     test('test getTimeBuckets', () async {
       // TODO
     });
diff --git a/mobile/openapi/test/update_album_dto_test.dart b/mobile/openapi/test/update_album_dto_test.dart
index 67ec80010d..7f1591a52c 100644
--- a/mobile/openapi/test/update_album_dto_test.dart
+++ b/mobile/openapi/test/update_album_dto_test.dart
@@ -36,6 +36,11 @@ void main() {
       // TODO
     });
 
+    // AssetOrder order
+    test('to test the property `order`', () async {
+      // TODO
+    });
+
 
   });
 
diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index 2540baf775..15ada078cb 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -1765,6 +1765,14 @@
               "type": "string"
             }
           },
+          {
+            "name": "order",
+            "required": false,
+            "in": "query",
+            "schema": {
+              "$ref": "#/components/schemas/AssetOrder"
+            }
+          },
           {
             "name": "personId",
             "required": false,
@@ -1901,6 +1909,14 @@
               "type": "string"
             }
           },
+          {
+            "name": "order",
+            "required": false,
+            "in": "query",
+            "schema": {
+              "$ref": "#/components/schemas/AssetOrder"
+            }
+          },
           {
             "name": "personId",
             "required": false,
@@ -6722,6 +6738,9 @@
             "format": "date-time",
             "type": "string"
           },
+          "order": {
+            "$ref": "#/components/schemas/AssetOrder"
+          },
           "owner": {
             "$ref": "#/components/schemas/UserResponseDto"
           },
@@ -10335,6 +10354,9 @@
           },
           "isActivityEnabled": {
             "type": "boolean"
+          },
+          "order": {
+            "$ref": "#/components/schemas/AssetOrder"
           }
         },
         "type": "object"
diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts
index acf540aff1..6a660f4e1b 100644
--- a/open-api/typescript-sdk/src/fetch-client.ts
+++ b/open-api/typescript-sdk/src/fetch-client.ts
@@ -153,6 +153,7 @@ export type AlbumResponseDto = {
     id: string;
     isActivityEnabled: boolean;
     lastModifiedAssetTimestamp?: string;
+    order?: AssetOrder;
     owner: UserResponseDto;
     ownerId: string;
     shared: boolean;
@@ -176,6 +177,7 @@ export type UpdateAlbumDto = {
     albumThumbnailAssetId?: string;
     description?: string;
     isActivityEnabled?: boolean;
+    order?: AssetOrder;
 };
 export type BulkIdsDto = {
     ids: string[];
@@ -1453,12 +1455,13 @@ export function getAssetThumbnail({ format, id, key }: {
         ...opts
     }));
 }
-export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, personId, size, timeBucket, userId, withPartners, withStacked }: {
+export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, timeBucket, userId, withPartners, withStacked }: {
     albumId?: string;
     isArchived?: boolean;
     isFavorite?: boolean;
     isTrashed?: boolean;
     key?: string;
+    order?: AssetOrder;
     personId?: string;
     size: TimeBucketSize;
     timeBucket: string;
@@ -1475,6 +1478,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
         isFavorite,
         isTrashed,
         key,
+        order,
         personId,
         size,
         timeBucket,
@@ -1485,12 +1489,13 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
         ...opts
     }));
 }
-export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, personId, size, userId, withPartners, withStacked }: {
+export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, userId, withPartners, withStacked }: {
     albumId?: string;
     isArchived?: boolean;
     isFavorite?: boolean;
     isTrashed?: boolean;
     key?: string;
+    order?: AssetOrder;
     personId?: string;
     size: TimeBucketSize;
     userId?: string;
@@ -1506,6 +1511,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
         isFavorite,
         isTrashed,
         key,
+        order,
         personId,
         size,
         userId,
@@ -2747,6 +2753,10 @@ export enum AssetTypeEnum {
     Audio = "AUDIO",
     Other = "OTHER"
 }
+export enum AssetOrder {
+    Asc = "asc",
+    Desc = "desc"
+}
 export enum Error {
     Duplicate = "duplicate",
     NoPermission = "no_permission",
@@ -2774,10 +2784,6 @@ export enum TimeBucketSize {
     Day = "DAY",
     Month = "MONTH"
 }
-export enum AssetOrder {
-    Asc = "asc",
-    Desc = "desc"
-}
 export enum EntityType {
     Asset = "ASSET",
     Album = "ALBUM"
diff --git a/server/src/domain/album/album-response.dto.ts b/server/src/domain/album/album-response.dto.ts
index 168b385928..bcca1cd315 100644
--- a/server/src/domain/album/album-response.dto.ts
+++ b/server/src/domain/album/album-response.dto.ts
@@ -1,4 +1,5 @@
-import { AlbumEntity } from '@app/infra/entities';
+import { AlbumEntity, AssetOrder } from '@app/infra/entities';
+import { Optional } from '@nestjs/common';
 import { ApiProperty } from '@nestjs/swagger';
 import { AssetResponseDto, mapAsset } from '../asset';
 import { AuthDto } from '../auth/auth.dto';
@@ -23,6 +24,9 @@ export class AlbumResponseDto {
   startDate?: Date;
   endDate?: Date;
   isActivityEnabled!: boolean;
+  @Optional()
+  @ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
+  order?: AssetOrder;
 }
 
 export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDto): AlbumResponseDto => {
@@ -63,6 +67,7 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt
     assets: (withAssets ? assets : []).map((asset) => mapAsset(asset, { auth })),
     assetCount: entity.assets?.length || 0,
     isActivityEnabled: entity.isActivityEnabled,
+    order: entity.order,
   };
 };
 
diff --git a/server/src/domain/album/album.service.ts b/server/src/domain/album/album.service.ts
index 9a7b940f77..dc3d510d4b 100644
--- a/server/src/domain/album/album.service.ts
+++ b/server/src/domain/album/album.service.ts
@@ -148,6 +148,7 @@ export class AlbumService {
       description: dto.description,
       albumThumbnailAssetId: dto.albumThumbnailAssetId,
       isActivityEnabled: dto.isActivityEnabled,
+      order: dto.order,
     });
 
     return mapAlbumWithoutAssets(updatedAlbum);
diff --git a/server/src/domain/album/dto/album-update.dto.ts b/server/src/domain/album/dto/album-update.dto.ts
index 1b6c754f02..4f88cefbbd 100644
--- a/server/src/domain/album/dto/album-update.dto.ts
+++ b/server/src/domain/album/dto/album-update.dto.ts
@@ -1,4 +1,6 @@
-import { IsString } from 'class-validator';
+import { AssetOrder } from '@app/infra/entities';
+import { ApiProperty } from '@nestjs/swagger';
+import { IsEnum, IsString } from 'class-validator';
 import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util';
 
 export class UpdateAlbumDto {
@@ -15,4 +17,9 @@ export class UpdateAlbumDto {
 
   @ValidateBoolean({ optional: true })
   isActivityEnabled?: boolean;
+
+  @IsEnum(AssetOrder)
+  @Optional()
+  @ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' })
+  order?: AssetOrder;
 }
diff --git a/server/src/domain/asset/dto/asset.dto.ts b/server/src/domain/asset/dto/asset.dto.ts
index 8b5c675d89..2abe31d0ad 100644
--- a/server/src/domain/asset/dto/asset.dto.ts
+++ b/server/src/domain/asset/dto/asset.dto.ts
@@ -18,11 +18,6 @@ export class DeviceIdDto {
   deviceId!: string;
 }
 
-export enum AssetOrder {
-  ASC = 'asc',
-  DESC = 'desc',
-}
-
 const hasGPS = (o: { latitude: undefined; longitude: undefined }) =>
   o.latitude !== undefined || o.longitude !== undefined;
 const ValidateGPS = () => ValidateIf(hasGPS);
diff --git a/server/src/domain/asset/dto/time-bucket.dto.ts b/server/src/domain/asset/dto/time-bucket.dto.ts
index 597a5de356..7c5b9c212b 100644
--- a/server/src/domain/asset/dto/time-bucket.dto.ts
+++ b/server/src/domain/asset/dto/time-bucket.dto.ts
@@ -1,6 +1,7 @@
+import { AssetOrder } from '@app/infra/entities';
 import { ApiProperty } from '@nestjs/swagger';
 import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
-import { ValidateBoolean, ValidateUUID } from '../../domain.util';
+import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util';
 import { TimeBucketSize } from '../../repositories';
 
 export class TimeBucketDto {
@@ -32,6 +33,11 @@ export class TimeBucketDto {
 
   @ValidateBoolean({ optional: true })
   withPartners?: boolean;
+
+  @IsEnum(AssetOrder)
+  @Optional()
+  @ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' })
+  order?: AssetOrder;
 }
 
 export class TimeBucketAssetDto extends TimeBucketDto {
diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts
index 3627004421..8b14ce597e 100644
--- a/server/src/domain/repositories/asset.repository.ts
+++ b/server/src/domain/repositories/asset.repository.ts
@@ -1,5 +1,5 @@
 import { AssetSearchOptions, ReverseGeocodeResult, SearchExploreItem } from '@app/domain';
-import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities';
+import { AssetEntity, AssetJobStatusEntity, AssetOrder, AssetType, ExifEntity } from '@app/infra/entities';
 import { FindOptionsRelations, FindOptionsSelect } from 'typeorm';
 import { Paginated, PaginationOptions } from '../domain.util';
 
@@ -66,6 +66,7 @@ export interface AssetBuilderOptions {
 
 export interface TimeBucketOptions extends AssetBuilderOptions {
   size: TimeBucketSize;
+  order?: AssetOrder;
 }
 
 export interface TimeBucketItem {
diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts
index 9fa7d8e8ba..1bc67266a3 100644
--- a/server/src/domain/search/dto/search.dto.ts
+++ b/server/src/domain/search/dto/search.dto.ts
@@ -1,5 +1,4 @@
-import { AssetOrder } from '@app/domain/asset/dto/asset.dto';
-import { AssetType, GeodataPlacesEntity } from '@app/infra/entities';
+import { AssetOrder, AssetType, GeodataPlacesEntity } from '@app/infra/entities';
 import { ApiProperty } from '@nestjs/swagger';
 import { Type } from 'class-transformer';
 import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts
index 4cb0665e08..56c4498bce 100644
--- a/server/src/domain/search/search.service.ts
+++ b/server/src/domain/search/search.service.ts
@@ -1,6 +1,6 @@
-import { AssetEntity } from '@app/infra/entities';
+import { AssetEntity, AssetOrder } from '@app/infra/entities';
 import { Inject, Injectable } from '@nestjs/common';
-import { AssetOrder, AssetResponseDto, mapAsset } from '../asset';
+import { AssetResponseDto, mapAsset } from '../asset';
 import { AuthDto } from '../auth';
 import { PersonResponseDto } from '../person';
 import {
diff --git a/server/src/infra/entities/album.entity.ts b/server/src/infra/entities/album.entity.ts
index fbc125351a..daa8fcbc36 100644
--- a/server/src/infra/entities/album.entity.ts
+++ b/server/src/infra/entities/album.entity.ts
@@ -14,6 +14,12 @@ import { AssetEntity } from './asset.entity';
 import { SharedLinkEntity } from './shared-link.entity';
 import { UserEntity } from './user.entity';
 
+// ran into issues when importing the enum from `asset.dto.ts`
+export enum AssetOrder {
+  ASC = 'asc',
+  DESC = 'desc',
+}
+
 @Entity('albums')
 export class AlbumEntity {
   @PrimaryGeneratedColumn('uuid')
@@ -59,4 +65,7 @@ export class AlbumEntity {
 
   @Column({ default: true })
   isActivityEnabled!: boolean;
+
+  @Column({ type: 'varchar', default: AssetOrder.DESC })
+  order!: AssetOrder;
 }
diff --git a/server/src/infra/migrations/1710182081326-AscendingOrderAlbum.ts b/server/src/infra/migrations/1710182081326-AscendingOrderAlbum.ts
new file mode 100644
index 0000000000..b672ff2b20
--- /dev/null
+++ b/server/src/infra/migrations/1710182081326-AscendingOrderAlbum.ts
@@ -0,0 +1,14 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class AscendingOrderAlbum1710182081326 implements MigrationInterface {
+    name = 'AscendingOrderAlbum1710182081326'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`ALTER TABLE "albums" ADD "order" character varying NOT NULL DEFAULT 'desc'`);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "order"`);
+    }
+
+}
diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts
index 5d571d11eb..871a44460b 100644
--- a/server/src/infra/repositories/asset.repository.ts
+++ b/server/src/infra/repositories/asset.repository.ts
@@ -36,7 +36,7 @@ import {
   Not,
   Repository,
 } from 'typeorm';
-import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity, SmartInfoEntity } from '../entities';
+import { AssetEntity, AssetJobStatusEntity, AssetOrder, AssetType, ExifEntity, SmartInfoEntity } from '../entities';
 import { DummyValue, GenerateSql } from '../infra.util';
 import { Chunked, ChunkedArray, OptionalBetween, paginate, paginatedBuilder, searchAssetBuilder } from '../infra.utils';
 import { Instrumentation } from '../instrumentation';
@@ -607,7 +607,7 @@ export class AssetRepository implements IAssetRepository {
       .select(`COUNT(asset.id)::int`, 'count')
       .addSelect(truncated, 'timeBucket')
       .groupBy(truncated)
-      .orderBy(truncated, 'DESC')
+      .orderBy(truncated, options.order === AssetOrder.ASC ? 'ASC' : 'DESC')
       .getRawMany();
   }
 
@@ -620,7 +620,7 @@ export class AssetRepository implements IAssetRepository {
         // First sort by the day in localtime (put it in the right bucket)
         .orderBy(truncated, 'DESC')
         // and then sort by the actual time
-        .addOrderBy('asset.fileCreatedAt', 'DESC')
+        .addOrderBy('asset.fileCreatedAt', options.order === AssetOrder.ASC ? 'ASC' : 'DESC')
         .getMany()
     );
   }
diff --git a/server/src/infra/sql/album.repository.sql b/server/src/infra/sql/album.repository.sql
index d9b2e896e9..ddedc00959 100644
--- a/server/src/infra/sql/album.repository.sql
+++ b/server/src/infra/sql/album.repository.sql
@@ -15,6 +15,7 @@ FROM
       "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
       "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
       "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+      "AlbumEntity"."order" AS "AlbumEntity_order",
       "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
       "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
       "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
@@ -91,6 +92,7 @@ SELECT
   "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
   "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
   "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+  "AlbumEntity"."order" AS "AlbumEntity_order",
   "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
   "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
   "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
@@ -149,6 +151,7 @@ SELECT
   "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
   "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
   "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+  "AlbumEntity"."order" AS "AlbumEntity_order",
   "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
   "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
   "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
@@ -279,6 +282,7 @@ SELECT
   "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
   "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
   "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+  "AlbumEntity"."order" AS "AlbumEntity_order",
   "AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
   "AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
   "AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
@@ -352,6 +356,7 @@ SELECT
   "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
   "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
   "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+  "AlbumEntity"."order" AS "AlbumEntity_order",
   "AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
   "AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
   "AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
@@ -462,6 +467,7 @@ SELECT
   "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
   "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
   "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+  "AlbumEntity"."order" AS "AlbumEntity_order",
   "AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
   "AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
   "AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
@@ -553,6 +559,7 @@ SELECT
   "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
   "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
   "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
+  "AlbumEntity"."order" AS "AlbumEntity_order",
   "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
   "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
   "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
diff --git a/server/src/infra/sql/shared.link.repository.sql b/server/src/infra/sql/shared.link.repository.sql
index b5e6894130..27531cfc9e 100644
--- a/server/src/infra/sql/shared.link.repository.sql
+++ b/server/src/infra/sql/shared.link.repository.sql
@@ -87,6 +87,7 @@ FROM
       "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt",
       "SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId",
       "SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled",
+      "SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order",
       "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_id",
       "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceAssetId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceAssetId",
       "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."ownerId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_ownerId",
@@ -248,6 +249,7 @@ SELECT
   "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt",
   "SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId",
   "SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled",
+  "SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order",
   "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id",
   "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name",
   "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."avatarColor" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_avatarColor",
diff --git a/server/test/fixtures/album.stub.ts b/server/test/fixtures/album.stub.ts
index 2fdc5b5dd4..bfb6acb6d1 100644
--- a/server/test/fixtures/album.stub.ts
+++ b/server/test/fixtures/album.stub.ts
@@ -1,4 +1,4 @@
-import { AlbumEntity } from '@app/infra/entities';
+import { AlbumEntity, AssetOrder } from '@app/infra/entities';
 import { assetStub } from './asset.stub';
 import { authStub } from './auth.stub';
 import { userStub } from './user.stub';
@@ -19,6 +19,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   sharedWithUser: Object.freeze<AlbumEntity>({
     id: 'album-2',
@@ -35,6 +36,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [userStub.user1],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   sharedWithMultiple: Object.freeze<AlbumEntity>({
     id: 'album-3',
@@ -51,6 +53,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [userStub.user1, userStub.user2],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   sharedWithAdmin: Object.freeze<AlbumEntity>({
     id: 'album-3',
@@ -67,6 +70,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [userStub.admin],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   oneAsset: Object.freeze<AlbumEntity>({
     id: 'album-4',
@@ -83,6 +87,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   twoAssets: Object.freeze<AlbumEntity>({
     id: 'album-4a',
@@ -99,6 +104,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   emptyWithInvalidThumbnail: Object.freeze<AlbumEntity>({
     id: 'album-5',
@@ -115,6 +121,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   emptyWithValidThumbnail: Object.freeze<AlbumEntity>({
     id: 'album-5',
@@ -131,6 +138,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   oneAssetInvalidThumbnail: Object.freeze<AlbumEntity>({
     id: 'album-6',
@@ -147,6 +155,7 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
   oneAssetValidThumbnail: Object.freeze<AlbumEntity>({
     id: 'album-6',
@@ -163,5 +172,6 @@ export const albumStub = {
     sharedLinks: [],
     sharedUsers: [],
     isActivityEnabled: true,
+    order: AssetOrder.DESC,
   }),
 };
diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts
index 61b44a544a..109f051907 100644
--- a/server/test/fixtures/shared-link.stub.ts
+++ b/server/test/fixtures/shared-link.stub.ts
@@ -1,5 +1,5 @@
 import { AlbumResponseDto, AssetResponseDto, ExifResponseDto, mapUser, SharedLinkResponseDto } from '@app/domain';
-import { AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities';
+import { AssetOrder, AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities';
 import { assetStub } from './asset.stub';
 import { authStub } from './auth.stub';
 import { libraryStub } from './library.stub';
@@ -101,6 +101,7 @@ const albumResponse: AlbumResponseDto = {
   assets: [],
   assetCount: 1,
   isActivityEnabled: true,
+  order: AssetOrder.DESC,
 };
 
 export const sharedLinkStub = {
@@ -181,6 +182,7 @@ export const sharedLinkStub = {
       sharedUsers: [],
       sharedLinks: [],
       isActivityEnabled: true,
+      order: AssetOrder.DESC,
       assets: [
         {
           id: 'id_1',
diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte
index 6cbce418ba..d5f816047f 100644
--- a/web/src/lib/components/album-page/album-options.svelte
+++ b/web/src/lib/components/album-page/album-options.svelte
@@ -1,22 +1,55 @@
 <script lang="ts">
   import Icon from '$lib/components/elements/icon.svelte';
-  import type { AlbumResponseDto, UserResponseDto } from '@immich/sdk';
-  import { mdiClose, mdiPlus } from '@mdi/js';
+  import { updateAlbumInfo, type AlbumResponseDto, type UserResponseDto, AssetOrder } from '@immich/sdk';
+  import { mdiArrowDownThin, mdiArrowUpThin, mdiClose, mdiPlus } from '@mdi/js';
   import { createEventDispatcher } from 'svelte';
 
   import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
   import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
   import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
+  import SettingDropdown from '../shared-components/settings/setting-dropdown.svelte';
+  import type { RenderedOption } from '../elements/dropdown.svelte';
+  import { handleError } from '$lib/utils/handle-error';
+  import { findKey } from 'lodash-es';
 
   export let album: AlbumResponseDto;
+  export let order: AssetOrder | undefined;
   export let user: UserResponseDto;
+  export let onChangeOrder: (order: AssetOrder) => void;
+
+  const options: Record<AssetOrder, RenderedOption> = {
+    [AssetOrder.Asc]: { icon: mdiArrowUpThin, title: 'Oldest first' },
+    [AssetOrder.Desc]: { icon: mdiArrowDownThin, title: 'Newest first' },
+  };
+
+  $: selectedOption = order ? options[order] : options[AssetOrder.Desc];
 
   const dispatch = createEventDispatcher<{
     close: void;
     toggleEnableActivity: void;
     showSelectSharedUser: void;
   }>();
+
+  const handleToggle = async (returnedOption: RenderedOption) => {
+    if (selectedOption === returnedOption) {
+      return;
+    }
+    let order = AssetOrder.Desc;
+    order = findKey(options, (option) => option === returnedOption) as AssetOrder;
+
+    try {
+      await updateAlbumInfo({
+        id: album.id,
+        updateAlbumDto: {
+          order,
+        },
+      });
+      onChangeOrder(order);
+    } catch (error) {
+      handleError(error, 'Error updating album order');
+    }
+  };
 </script>
 
 <FullScreenModal onClose={() => dispatch('close')}>
@@ -34,8 +67,16 @@
 
         <div class=" items-center justify-center p-4">
           <div class="py-2">
-            <h2 class="text-gray text-sm mb-3">SHARING</h2>
-            <div class="p-2">
+            <h2 class="text-gray text-sm mb-2">SETTINGS</h2>
+            <div class="grid p-2 gap-y-2">
+              {#if order}
+                <SettingDropdown
+                  title="Display order"
+                  options={Object.values(options)}
+                  selectedOption={options[order]}
+                  onToggle={handleToggle}
+                />
+              {/if}
               <SettingSwitch
                 title="Comments & likes"
                 subtitle="Let others respond"
diff --git a/web/src/lib/components/slideshow-settings.svelte b/web/src/lib/components/slideshow-settings.svelte
index eec2f83e37..46b5ff9631 100644
--- a/web/src/lib/components/slideshow-settings.svelte
+++ b/web/src/lib/components/slideshow-settings.svelte
@@ -20,7 +20,7 @@
     [SlideshowNavigation.DescendingOrder]: { icon: mdiArrowDownThin, title: 'Forward' },
   };
 
-  export const handleToggle = (selectedOption: RenderedOption) => {
+  const handleToggle = (selectedOption: RenderedOption) => {
     for (const [key, option] of Object.entries(options)) {
       if (option === selectedOption) {
         $slideshowNavigation = key as SlideshowNavigation;
diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts
index 15519b4b26..a61f9ed35a 100644
--- a/web/src/lib/stores/assets.store.ts
+++ b/web/src/lib/stores/assets.store.ts
@@ -161,7 +161,10 @@ export class AssetStore {
     this.assetToBucket = {};
     this.albumAssets = new Set();
 
-    const buckets = await getTimeBuckets({ ...this.options, key: getKey() });
+    const buckets = await getTimeBuckets({
+      ...this.options,
+      key: getKey(),
+    });
 
     this.initialized = true;
 
diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte
index 05d94bf3e1..9e272ce3f6 100644
--- a/web/src/routes/(user)/albums/[albumId]/+page.svelte
+++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte
@@ -58,6 +58,7 @@
     updateAlbumInfo,
     type ActivityResponseDto,
     type UserResponseDto,
+    AssetOrder,
   } from '@immich/sdk';
   import {
     mdiArrowLeft,
@@ -83,6 +84,7 @@
 
   $: album = data.album;
   $: albumId = album.id;
+  $: albumKey = `${albumId}_${albumOrder}`;
 
   $: {
     if (!album.isActivityEnabled && $numberOfComments === 0) {
@@ -112,8 +114,9 @@
   let globalWidth: number;
   let assetGridWidth: number;
   let textArea: HTMLTextAreaElement;
+  let albumOrder: AssetOrder | undefined = data.album.order;
 
-  $: assetStore = new AssetStore({ albumId });
+  $: assetStore = new AssetStore({ albumId, order: albumOrder });
   const assetInteractionStore = createAssetInteractionStore();
   const { isMultiSelectState, selectedAssets } = assetInteractionStore;
 
@@ -512,7 +515,7 @@
       style={`width:${assetGridWidth}px`}
     >
       <!-- Use key because AssetGrid can't deal with changing stores -->
-      {#key albumId}
+      {#key albumKey}
         {#if viewMode === ViewMode.SELECT_ASSETS}
           <AssetGrid
             assetStore={timelineStore}
@@ -679,7 +682,9 @@
 {#if viewMode === ViewMode.OPTIONS && $user}
   <AlbumOptions
     {album}
+    order={albumOrder}
     user={$user}
+    onChangeOrder={(order) => (albumOrder = order)}
     on:close={() => (viewMode = ViewMode.VIEW)}
     on:toggleEnableActivity={handleToggleEnableActivity}
     on:showSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)}
diff --git a/web/src/test-data/factories/album-factory.ts b/web/src/test-data/factories/album-factory.ts
index fd941f51f7..3d761fcf35 100644
--- a/web/src/test-data/factories/album-factory.ts
+++ b/web/src/test-data/factories/album-factory.ts
@@ -1,5 +1,5 @@
 import { faker } from '@faker-js/faker';
-import type { AlbumResponseDto } from '@immich/sdk';
+import { AssetOrder, type AlbumResponseDto } from '@immich/sdk';
 import { Sync } from 'factory.ts';
 import { userFactory } from './user-factory';
 
@@ -18,4 +18,5 @@ export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
   sharedUsers: [],
   hasSharedLink: false,
   isActivityEnabled: true,
+  order: AssetOrder.Desc,
 });