Skip to content
macOSDeep Dive Published Updated 5 min readViews unavailable

Finder Sync Extensions on macOS: Monitor Roots, Badges, and Finder Actions

Use Finder Sync for bounded Finder UI over local sync roots; learn its callback, badge, and shared-state limits, and when File Provider is required.

Finder Sync is a macOS extension point for applications that synchronize a local folder with a remote data source and want Finder to show status or offer actions. The extension can register directories, respond when a user browses them, assign badges, and provide Finder menus or toolbar UI. It is not the synchronization engine: the app remains responsible for remote state, conflict handling, file operations, and durable retries.

That boundary matters when comparing Finder Sync with File Provider. File Provider lets macOS manage a system-visible replica, placeholders, content materialization, eviction, and enumeration. Finder Sync instead augments Finder’s presentation of a local sync folder. Adding badges does not make an online-only item safely downloadable on demand, and a Finder menu action does not prove a server-side operation completed.

Register the roots you actually manage

Use the shared FIFinderSyncController to set the extension’s directoryURLs. Apple documents that Finder sends beginObservingDirectory(at:) and endObservingDirectory(at:) for monitored directories and their subdirectories. The extension should set its roots when it starts, including an empty set when the account has no managed folders. Do not register the user’s entire home directory simply to avoid maintaining a precise list; broad roots create needless callbacks and can expose unrelated paths to extension logic.

An observation callback means Finder is displaying a monitored directory or descendant. It is not a file-system journal and does not guarantee the app saw every remote or local change. Use your synchronization engine’s own durable change source to update state; have Finder callbacks request a bounded snapshot for what is visible. If an item changes while no Finder window is open, reconcile its badge when Finder next asks or when the app explicitly updates the displayed item.

import Cocoa
import FinderSync

final class SyncFinderExtension: FIFinderSync {
    override init() {
        super.init()
        // The containing app supplies the current, user-approved roots.
        let roots = loadConfiguredSyncRootsFromAppGroup()
        FIFinderSyncController.default().directoryURLs = Set(roots)
    }

    override func requestBadgeIdentifier(for url: URL) {
        guard let state = readCachedSyncState(for: url) else { return }
        FIFinderSyncController.default().setBadgeIdentifier(state.badgeID, for: url)
    }
}

The names in this excerpt reflect the current Swift API surface, but the storage functions are application-defined. Resolve the exact SDK availability and extension target in Xcode before treating it as a build-ready sample. Keep the badge path fast: read a small local snapshot, return, and let the sync engine refresh the cache asynchronously. Do not block Finder on a network request, prompt for credentials in a badge callback, or perform unbounded directory walks.

Treat extension processes and shared state explicitly

Finder Sync runs outside the containing app. The host app and extension need an intentional sharing mechanism, such as a correctly configured App Group container, and a versioned representation of the minimum state Finder needs. Write state atomically or use a small database with a clear consistency policy; never assume that a singleton, in-memory cache, or default preferences instance is shared automatically between processes.

Apple’s archived extension guide notes that Finder can instantiate additional extension instances for Open and Save dialogs, separately from the Finder browsing instance. Although the guide is archived, the current API documentation also describes observation callbacks for displayed folders. Design state access for multiple short-lived extension processes: serialize durable mutations in one owner, make reads safe during replacement, and do not use process-local state as the sole record of a sync operation.

The extension should expose operations, not unrestricted authority. A menu item such as “Pause sync” can send a narrow command with an account/root identifier to the main app or shared service. Validate that the selected URL is inside a configured root, re-check account authorization in the component that performs the mutation, and report completion only after that component confirms it. A stale Finder selection can refer to a moved, deleted, or replaced file.

Badges are a small, bounded projection

Register badge identifiers and images with the controller, then answer the badge request for visible URLs. Apple advises setting an initial badge through the request callback and updating only URLs that have already received a badge. Clear the badge with an empty identifier when the status no longer applies. Avoid treating a badge as a security label: Finder decoration is user-interface feedback, not an access-control decision.

Represent a small status vocabulary such as “syncing,” “attention,” and “offline,” not every internal transfer phase. Coalesce rapid state changes before updating Finder. If the item is no longer visible or the state cache is stale, return a neutral result and let the next update reconcile it. Keep names, account identifiers, remote paths, and error details out of badge IDs and user-visible labels unless they are necessary and privacy-reviewed.

Menus should be context-aware and minimal. Derive the selected items from the controller, verify every item still belongs to a managed root, and disable operations that cannot be applied to the entire selection. If an operation is asynchronous, provide progress in the containing app or another supported surface; Finder’s menu callback is not a durable job queue.

Package, test, and diagnose the extension boundary

The extension needs the Finder Sync extension point identifier, a principal class, the appropriate signing configuration, and any app-group entitlement used for shared state. Apple’s archived guide documents the plist shape and the extension bundle relationship; validate current target settings and distribution requirements against the installed Xcode/macOS SDK rather than copying an old project file verbatim. A successful build does not prove Finder loaded the intended extension or accepted its configuration.

Test a clean install, first launch, account sign-in and sign-out, root add/remove, Finder restart, Open/Save dialogs, simultaneous Finder windows, a large directory, a stale shared-state version, and an app upgrade while Finder remains open. Confirm beginObservingDirectory and endObservingDirectory are idempotent, state reads never block on remote I/O, and a command for a deleted selection fails safely. Measure callback latency, cache age, badge refresh delay, and extension restarts without recording private filenames.

Use File Provider instead when the product needs system-managed placeholders and materialization. Use Finder Sync when the product already owns a local replica and needs a restrained Finder integration. Choosing the correct boundary prevents a badge extension from becoming an accidental, under-specified file synchronization service.

Related:

Sources:

Comments