macos_file_open_handler
Reliably process files opened through Finder, Dock-icon drops, or open -a in
a Flutter macOS app.
This plugin is designed for applications that do more than observe a path. It buffers requests that arrive before Dart starts listening, preserves every macOS request as one batch, and waits for your asynchronous callback before it delivers the next batch.
When to use this package
Use macos_file_open_handler when one or more of these guarantees matter:
- A file that launched the app must not be lost while Flutter is starting.
- Files opened together must reach Dart together as one immutable batch.
- Imports must run one at a time and in arrival order.
- Cancellation must wait for active work and native cleanup.
- Callback, decoding, channel, and cleanup failures need one error path.
This package is not required to obtain the initial macOS sandbox permission. macOS grants a dynamic sandbox extension for files opened or dropped by the user when the host has the appropriate user-selected-file entitlement. See Security scopes and persistent access for the lifetime and bookmark details.
Choosing between similar packages
These are the closest pub.dev packages as of August 20, 2026:
| Package | API model | Startup delivery | Multiple-file request | Async completion | macOS integration |
|---|---|---|---|---|---|
macos_file_open_handler |
One async batch callback | Buffered natively until Dart listens | Preserved | Awaited before the next batch and cleanup | Flutter lifecycle delegate |
file_open 0.1.0 |
Broadcast Stream<List<Uri>> |
Requires early initialization; its documentation warns that initial files may be missed | Preserved | Not acknowledged by the source stream | Runtime AppDelegate interception |
file_open_handler 0.0.2 |
Single-path getter and callback | Retains the last forwarded path | Not preserved | No batch completion contract | Manual host AppDelegate forwarding |
Choose this package when reliable startup delivery and serial, acknowledged
processing are more important than stream composition. Choose file_open when
you want a conventional broadcast stream, possibly with multiple listeners,
and your application owns its sequencing and file-access lifecycle. The older
file_open_handler is mainly relevant to an existing single-file integration
that already forwards its host AppDelegate callback.
A stream is not generally better or worse than a callback. A stream is a good
notification API, but its source does not await an async function passed to
listen. Here callback completion is part of the contract: it provides
backpressure and gives the plugin a deterministic cleanup boundary.
Quick start
Add the package:
flutter pub add macos_file_open_handler
Configure the document types and sandbox entitlement described in macOS setup, then install one application-wide listener early in the app lifecycle:
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:macos_file_open_handler/macos_file_open_handler.dart';
late final MacosFileOpenSubscription fileOpenSubscription;
void main() {
WidgetsFlutterBinding.ensureInitialized();
fileOpenSubscription = MacosFileOpenHandler.instance.listen(
(files) async {
for (final file in files) {
await importFile(file);
}
},
onError: (error, stackTrace) {
debugPrint('Could not open file: $error');
},
);
runApp(const MyApp());
}
MacosOpenedFile provides the file's name, absolute path, and original
file uri. Replace importFile with the operation your app needs. Await every
operation that uses an opened file:
import 'dart:io';
Future<void> importFile(MacosOpenedFile file) async {
final bytes = await File.fromUri(file.uri).readAsBytes();
await saveToLibrary(file.name, bytes);
}
For large files, copy or stream their contents instead of reading the entire file into memory.
Handle a multi-file request as one unit
The callback receives exactly one list for each operating-system request. You can import that list in one transaction:
fileOpenSubscription = MacosFileOpenHandler.instance.listen(
(files) async {
await importTogether(files.map((file) => file.path).toList());
},
);
Own the listener from a widget
If a widget owns the listener instead of keeping it for the entire application lifetime, cancel it during disposal:
import 'dart:async';
late final MacosFileOpenSubscription subscription;
@override
void initState() {
super.initState();
subscription = MacosFileOpenHandler.instance.listen(handleOpenedFiles);
}
@override
void dispose() {
unawaited(subscription.cancel());
super.dispose();
}
Only one listener can be active at a time. For an application-wide listener, keep its subscription alive for the application lifetime.
macOS setup
The host app chooses which files it accepts. Add CFBundleDocumentTypes to
macos/Runner/Info.plist; for example, to accept movie files:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Movie</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.movie</string>
</array>
</dict>
</array>
Sandboxed apps must also declare the access they need in both Debug/Profile and Release entitlements:
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
Use com.apple.security.files.user-selected.read-write instead when the app
modifies opened files. File extensions, Uniform Type Identifiers, and sandbox
permissions are intentionally host-app configuration because they differ per
consumer.
The plugin supports macOS 10.15 or newer and includes both CocoaPods and Swift Package Manager integration. Flutter 3.16 or newer is required because that release added the macOS application lifecycle forwarding used to receive file open events in plugins.
Delivery and cleanup behavior
Ordering and errors
Requests are processed serially in arrival order. A slow callback delays later batches, so keep callbacks bounded and await only work that must finish before the next request begins.
Callback, malformed-event, event-stream, and native-release errors are sent to
onError. If onError is omitted, they are reported as uncaught errors in the
zone that installed the listener. A failed callback does not prevent later
batches from being processed.
Cancellation
Awaiting subscription.cancel() waits for an active callback and its native
cleanup before detaching the listener. It is also safe to await cancellation
from inside the callback; the request takes effect immediately and cleanup
finishes after the callback returns. Install a replacement listener only after
the previous callback and cancellation have completed.
Native batch release is idempotent. The Dart handler retries a failed release and cancellation asks native code to release every batch it still retains.
Security scopes and persistent access
macOS automatically extends a sandbox for files opened or dropped through standard user interactions. This initial access comes from macOS, not from this package. See Apple's App Sandbox documentation.
The plugin additionally calls startAccessingSecurityScopedResource() for
each received file URL. When that call succeeds, it retains the original URL
until your callback finishes and balances its call in a finally path, even
if the callback throws. It does not claim to revoke or control the separate
dynamic sandbox extension granted by macOS.
Do not start unawaited file work inside the callback: it may continue after the plugin's explicitly started scope has been stopped. If the app only copies the file into its own container, no bookmark is needed after that copy completes. To access the original file in a future launch, create and store a security-scoped bookmark in native macOS code. Bookmark creation and resolution are not part of this package's current API.
Coexistence with URL handlers
Non-file URLs are ignored so URL-scheme plugins can handle them. For a mixed batch of file and non-file URLs, this plugin processes the files but leaves the callback unclaimed so Flutter can continue to later lifecycle delegates. A later delegate should filter file URLs to avoid processing them twice.
Libraries
- macos_file_open_handler
- Public API for receiving files opened through macOS.