diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift
new file mode 100644
index 0000000000..c8bfcd2842
--- /dev/null
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift
@@ -0,0 +1,82 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+import FileProvider
+import NextcloudFileProviderKit
+import OSLog
+
+extension FileProviderExtension: NSFileProviderCustomAction {
+ func performAction(
+ identifier actionIdentifier: NSFileProviderExtensionActionIdentifier,
+ onItemsWithIdentifiers itemIdentifiers: [NSFileProviderItemIdentifier],
+ completionHandler: @escaping ((any Error)?) -> Void
+ ) -> Progress {
+ switch actionIdentifier.rawValue {
+ case "com.nextcloud.desktopclient.FileProviderExt.KeepDownloadedAction":
+ return performKeepDownloadedAction(
+ keepDownloaded: true,
+ onItemsWithIdentifiers: itemIdentifiers,
+ completionHandler: completionHandler
+ )
+ case "com.nextcloud.desktopclient.FileProviderExt.AutoEvictAction":
+ return performKeepDownloadedAction(
+ keepDownloaded: false,
+ onItemsWithIdentifiers: itemIdentifiers,
+ completionHandler: completionHandler
+ )
+ default:
+ Logger.fileProviderExtension.error("Unsupported action: \(actionIdentifier.rawValue)")
+ completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError))
+ return Progress()
+ }
+ }
+
+ private func performKeepDownloadedAction(
+ keepDownloaded: Bool,
+ onItemsWithIdentifiers itemIdentifiers: [NSFileProviderItemIdentifier],
+ completionHandler: @escaping ((any Error)?) -> Void
+ ) -> Progress {
+ guard let ncAccount else {
+ Logger.fileProviderExtension.error(
+ "Not setting keep offline for items, account not set up yet."
+ )
+ completionHandler(NSFileProviderError(.notAuthenticated))
+ return Progress()
+ }
+ guard let dbManager else {
+ Logger.fileProviderExtension.error(
+ "Not setting keep offline for items as database is unreachable."
+ )
+ completionHandler(NSFileProviderError(.cannotSynchronize))
+ return Progress()
+ }
+
+ let progress = Progress()
+ for identifier in itemIdentifiers {
+ guard let item = Item.storedItem(
+ identifier: identifier,
+ account: ncAccount,
+ remoteInterface: ncKit,
+ dbManager: dbManager
+ ) else {
+ let error = NSError.fileProviderErrorForNonExistentItem(withIdentifier: identifier)
+ completionHandler(error)
+ return progress
+ }
+
+ let childProgress = Progress()
+ progress.addChild(childProgress, withPendingUnitCount: 1)
+ Task {
+ do {
+ try await item.set(keepDownloaded: keepDownloaded, domain: domain)
+ childProgress.completedUnitCount = 1
+ } catch let error {
+ completionHandler(error)
+ }
+ }
+ }
+ return progress
+ }
+}
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist
index 6649e742f6..ebccd214d6 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist
@@ -10,6 +10,25 @@
$(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME)
NSExtension
+ NSExtensionFileProviderActions
+
+
+ NSExtensionFileProviderActionActivationRule
+ SUBQUERY ( fileproviderItems, $fileproviderItem, $fileproviderItem.userInfo.displayKeepDownloaded == true ).@count > 0
+ NSExtensionFileProviderActionIdentifier
+ com.nextcloud.desktopclient.FileProviderExt.KeepDownloadedAction
+ NSExtensionFileProviderActionName
+ Always keep downloaded
+
+
+ NSExtensionFileProviderActionActivationRule
+ SUBQUERY ( fileproviderItems, $fileproviderItem, $fileproviderItem.userInfo.displayAllowAutoEvicting == true ).@count > 0
+ NSExtensionFileProviderActionIdentifier
+ com.nextcloud.desktopclient.FileProviderExt.AutoEvictAction
+ NSExtensionFileProviderActionName
+ Allow automatic freeing up space
+
+
NSExtensionFileProviderDocumentGroup
$(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN)
NSExtensionFileProviderSupportsEnumeration
diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj
index ce29a33758..c701ab4ae7 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj
+++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 530429982DD44235004BB598 /* FileProviderExtension+CustomActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530429972DD44226004BB598 /* FileProviderExtension+CustomActions.swift */; };
5307A6E62965C6FA001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E52965C6FA001E0C6A /* NextcloudKit */; };
5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E72965DAD8001E0C6A /* NextcloudKit */; };
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 531522812B8E01C6002E31BE /* ShareTableItemView.xib */; };
@@ -152,6 +153,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 530429972DD44226004BB598 /* FileProviderExtension+CustomActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+CustomActions.swift"; sourceTree = ""; };
531522812B8E01C6002E31BE /* ShareTableItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareTableItemView.xib; sourceTree = ""; };
531EDE562D897B4F00FD91F9 /* Eviction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Eviction.swift; sourceTree = ""; };
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCommunicationProtocol.h; sourceTree = ""; };
@@ -337,6 +339,7 @@
53D666602B70C9A70042C03D /* FileProviderConfig.swift */,
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */,
53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */,
+ 530429972DD44226004BB598 /* FileProviderExtension+CustomActions.swift */,
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */,
536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */,
538E397327F4765000FA63D5 /* FileProviderExt.entitlements */,
@@ -710,6 +713,7 @@
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */,
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */,
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */,
+ 530429982DD44235004BB598 /* FileProviderExtension+CustomActions.swift in Sources */,
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */,
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */,
);