Join our newsletter! Get Swift & SwiftUI tips, project updates, and discounts on our books...JOIN OUR NEWSLETTER!Monthly Swift insights, updates, and deals...

Provide macOS system-wide services from your app

Services on macOS allow us to extend our app’s functionality to the entire system, enabling users to interact with our app’s features while working in other contexts without explicitly opening it. These services are accessible via the context menu or from an application's Services menu in the macOS menu bar.

I recently implemented services for URL encoding and decoding strings in my macOS utility, EncodeDecode, and in this post, I’ll explain how I did it.

macOS context menu with 'URL-Decode' and 'URL-Encode' services for text selection

For my app, I implemented two services: URL-Encode and URL-Decode. These services become available when users select text anywhere on the system, such as in TextEdit or Xcode. When a user selects text and invokes one of the services, macOS launches EncodeDecode, passes the selected string to my app for processing, and replaces the original text with the processed result.

# Define service methods in code

To implement this functionality, I needed to define the required methods in code. This involved creating a service provider class that inherits from NSObject and implementing methods that process the text from the pasteboard.

import AppKit

class EncodeDecodeServiceProvider: NSObject {
    @objc func encode(
        _ pasteboard: NSPasteboard,
        userData: String?,
        error: AutoreleasingUnsafeMutablePointer<NSString>
    ) {
        guard let string = pasteboard.string(
            forType: NSPasteboard.PasteboardType.string
        ) else {
            return
        }
        pasteboard.clearContents()
        pasteboard.setString(
            EncodingManager.encodeUrl(string),
            forType: .string
        )
    }
    
    @objc func decode(
        _ pasteboard: NSPasteboard,
        userData: String?,
        error: AutoreleasingUnsafeMutablePointer<NSString>
    ) {
        guard let string = pasteboard.string(
            forType: NSPasteboard.PasteboardType.string
        ) else {
            return
        }
        pasteboard.clearContents()
        pasteboard.setString(
            EncodingManager.decode(urlString: string),
            forType: .string
        )
    }
}

# Register the service provider

After defining the service methods, the next step was to register the service provider. I did it in the applicationDidFinishLaunching(_:) method of my application delegate.

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(
        _ aNotification: Notification
    ) {
        NSApplication.shared
            .servicesProvider = EncodeDecodeServiceProvider()
    }
}

Only one service provider can be registered per application. If our app provides multiple services, a single object must handle all of them, as I did in EncodeDecode.

# Update Info.plist

Finally, I needed to declare the services in the Info.plist file. Here is how the NSServices key is configured for EncodeDecode:

<key>NSServices</key>
<array>
    <dict>
        <key>NSKeyEquivalent</key>
        <dict>
            <key>default</key>
            <string>E</string>
        </dict>
        <key>NSMenuItem</key>
        <dict>
            <key>default</key>
            <string>URL-Encode</string>
        </dict>
        <key>NSMessage</key>
        <string>encode</string>
        <key>NSPortName</key>
        <string>EncodeDecode</string>
        <key>NSRestricted</key>
        <false/>
        <key>NSRequiredContext</key>
        <dict/>
        <key>NSReturnTypes</key>
        <array>
            <string>NSStringPboardType</string>
        </array>
        <key>NSSendTypes</key>
        <array>
            <string>NSStringPboardType</string>
        </array>
    </dict>
    <dict>
        <key>NSKeyEquivalent</key>
        <dict>
            <key>default</key>
            <string>D</string>
        </dict>
        <key>NSMenuItem</key>
        <dict>
            <key>default</key>
            <string>URL-Decode</string>
        </dict>
        <key>NSMessage</key>
        <string>decode</string>
        <key>NSPortName</key>
        <string>EncodeDecode</string>
        <key>NSRestricted</key>
        <false/>
        <key>NSRequiredContext</key>
        <dict/>
        <key>NSReturnTypes</key>
        <array>
            <string>NSStringPboardType</string>
        </array>
        <key>NSSendTypes</key>
        <array>
            <string>NSStringPboardType</string>
        </array>
    </dict>
</array>

The NSServices key contains an array of dictionaries, each defining a service. For EncodeDecode, there are two services: URL-Encode and URL-Decode. Each service specifies the name as it appears in the Services menu, along with a keyboard shortcut for quick access. The NSMessage key maps to the selector method handling the service, and NSPortName identifies the app providing the service. Both services define NSSendTypes and NSReturnTypes to specify that they work with text data from the pasteboard.

To simplify the process of adding and editing these configurations, I used the XML editor in Xcode. You can access it by right-clicking on the Info.plist file and selecting Open As > Source Code. This approach provides full control and makes it easier to define the necessary keys and values.

# Testing and debugging

During development and testing, I found that the most reliable way to see updated services was to log out of my macOS user account and log back in.

To ensure that the system has recognized your service, you can also use the pbs tool to list the registered services. Run the following command in the terminal with the dump_pboard option:

/System/Library/CoreServices/pbs -dump_pboard

This command will display a list of registered services, allowing you to verify that your service has been correctly registered.


Users can manage available services in System Settings under Keyboard > Keyboard Shortcuts > Services. They can enable or disable services, assign shortcuts for quick access, and ensure only relevant services appear in an app’s Services menu.

System Settings showing the Services section under Keyboard Shortcuts, with options like URL-Encode and URL-Decode enabled


If you're an experienced Swift developer looking to learn advanced techniques, check out my latest book Swift Gems. It’s packed with tips and tricks focused solely on the Swift language and Swift Standard Library. From optimizing collections and handling strings to mastering asynchronous programming and debugging, "Swift Gems" provides practical advice that will elevate your Swift development skills to the next level. Grab your copy and let's explore these advanced techniques together.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Level up your Swift skills!$35

100+ tips to take your Swift code to the next level

Swift Gemsby Natalia Panferova

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms

Level up your Swift skills!

100+ tips to take your Swift code to the next level

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Swift Gems

by Natalia Panferova

$35