How to make a Mac app launch at login with SMAppService
Updated · Dmytro Virych
Guide · 3 min read
On macOS 13 and later, call SMAppService.mainApp.register() to open your app at login and unregister() to stop, and read SMAppService.mainApp.status to show the real state in your settings.
A menu bar app, a clipboard manager or a timer is only useful if it is
already running when the person needs it. "Open at login" is the setting
that makes that happen, and since macOS 13 Apple has given it a small,
dedicated API: SMAppService.
Which API should you use?
Use SMAppService from the ServiceManagement framework. Apple's
documentation says that in macOS 13 and later you use it "to register and
control LoginItems, LaunchAgents, and LaunchDaemons" for your app, and that
its register() and unregister() methods "provide a replacement for"
SMLoginItemSetEnabled.
That older function is deprecated as of macOS 13, and Apple's note on it is
short: "Please use SMAppService instead." It also needed a separate helper
app inside your bundle, in Contents/Library/LoginItems, whose only job was
to launch the main app. SMAppService.mainApp needs no helper: Apple
describes it as the service that "corresponds to the main application as a
login item".
If your app still supports macOS 12 or earlier, you need the old path for those systems. If its deployment target is macOS 13 or later, you only need the new API.
The code
import ServiceManagement
func setOpensAtLogin(_ enabled: Bool) throws {
if enabled {
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
}
That is the whole mechanism. Per Apple, after register() "the application
launches on subsequent logins". It does not relaunch the app now, because it
is already running. After unregister() the app "continues running, but
becomes unregistered to prevent future launches at login".
Why read the status instead of saving a Boolean?
The person can switch your app off in the Login Items section of System
Settings without ever opening your app. A Boolean you saved in
UserDefaults would then show "on" when the system says "off". So treat
SMAppService.mainApp.status as the only source of truth, and read it
whenever you show the setting. It is one of four values:
| Status | Apple's description |
|---|---|
.enabled | The service has been successfully registered and is eligible to run. |
.requiresApproval | The service has been successfully registered, but the user needs to take action in System Preferences. |
.notRegistered | The service hasn't registered with the Service Management framework, or the service attempted to reregister after it was already registered. |
.notFound | An error occurred and the framework couldn't find this service. |
A SwiftUI toggle that follows that rule:
import ServiceManagement
import SwiftUI
struct OpenAtLoginToggle: View {
@State private var status = SMAppService.mainApp.status
var body: some View {
Toggle("Open at login", isOn: Binding(
get: { status == .enabled },
set: { wanted in
do {
if wanted {
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
print("Open at login:", error)
}
status = SMAppService.mainApp.status
}
))
.onAppear { status = SMAppService.mainApp.status }
if status == .requiresApproval {
Button("Allow in System Settings…") {
SMAppService.openSystemSettingsLoginItems()
}
}
}
}
The status is read again after every change and every time the view appears, so the toggle shows what the system will actually do.
What errors can register() and unregister() throw?
Apple documents two you should expect in normal use:
register()on a service that is already registered returnskSMErrorAlreadyRegistered. In Swift that arrives as a thrown error.unregister()on a service that is not registered returnskSMErrorJobNotFound.
Neither is a real failure for a toggle, which is why the example above logs
the error and then trusts the status. register() can also fail with
kSMErrorLaunchDeniedByUser when the person has not approved the item. The
.requiresApproval branch sends them to the right pane of System Settings
with openSystemSettingsLoginItems().
Should it be on by default?
For the Mac App Store, no. Guideline 2.4.5 (iii) says apps "may not auto-launch or have other code run automatically at startup or login without consent". An app you sell from your own site is not reviewed against that guideline, but the same rule is a good default. A setting the person turned on themselves is one they won't resent, and a first-launch prompt with the toggle in it asks for consent and makes the feature easy to find.