Safari web extensions for iOS use standard web technologies to provide powerful browser customizations. Learn how you can build an extension that works for iPhone and iPad, and discover how you can publish your extension on the App Store.
Jon Davis: Hello, I'm Jon Davis, web technologies evangelist for the Safari and WebKit teams. I'm excited to be your guide on how to build and deploy Safari extensions for iOS. Now, there are a couple of different types of extensions for iOS. We've had content blockers on iOS for a few years, and that's a type of extension that allows you to configure powerful rules to block resource loads. But I'm here to tell you about Safari web extensions. They were introduced last year on macOS Big Sur and this fall, we brought them to iOS 15 and iPadOS 15. It's a type of extension that uses web technologies that works across other browsers, and our users are loving them. It's the most popular category on the App Store, and they're easy to install, like any other app. And that's because Safari web extensions are distributed with an app. And that means you get the benefits of being on the App Store. Users can discover your extensions, you can easily sell your extensions up front -- You can even take advantage of powerful features like in-app payments to unlock advanced features of your extensions or use TestFlight to run a beta program. But maybe you're saying, "That's all well and good, John, but I'm a web developer, not an app developer." Well, me too! And they asked me to be your guide. So I'm here to help you learn Xcode, and we're going to build a new project from scratch. I'm also going to touch on the privacy-preserving permissions model of Safari and how to submit your extension with some tips on sailing through that App Review process. So let's get started working with Xcode. And to do that, we'll need to download and install it from the App Store. I'm going to launch the App Store, and in Search I'll type "Xcode" and it comes up just about right away. And then all you need to do is click the Get button and then click Install. Now, it is a sizeable download that'll take a while but when you're able to launch it, Xcode will prompt you to install Command Line Tools. If you skipped installing them, you can easily do that later from the command line in Terminal. You'll just need to type "xcode-select" and add the install flag, and that'll kick off the install process. Now, my environment is ready to go, so I won't run this command now, but you'll want to make sure you have them installed because they include an important tool called the Safari web extension converter. It's useful if you've written a web extension for another browser or one you've written for Safari on macOS that you'd like to upgrade with support for Safari on iOS. Now I have a simple demo web extension in my Documents folder and this hello-world extension is written for another browser, and it simply shows a "Hello World" message in a pop-up. Now I can use the converter tool to upgrade it to work with iOS. In Terminal, just type "xc", as in Xcode -- so that's "xcrun". And then "safari-web-extension-converter" and a path to the project. If you have an extension written for Safari on macOS, you can also upgrade it using the converter tool. Just type "xcrun safari-web-extension-converter" and add the rebuild-project flag followed by the path to the macOS extension. But let's actually see what the convertor tool does when we run it on the Hello World extension.
And pressing Return, it begins the process. And what it's actually doing is wrapping our extension into an app project that we can compile and run for iOS and macOS. Now, switching back to the Terminal for a moment, I want to point out this note that has a warning here, showing that iOS extensions require nonpersistent background pages. This is actually really great because it's basically giving us a to-do list of the things we'll need to check to make sure our extension will work for iOS. Let's get back into Xcode. This extension is almost ready. The background script is already set up to run in a nonpersistent way. We just need to tell Safari to run it that way. So all I need to do to get this to work for iOS is to edit the manifest file. And in the background section, I'll add the persistent key with a value of false. And that's it! Let's try running this in the simulator. I'm going to use the Build Targets menu and choose iPhone 13 Pro. Now, I have a lot of simulators installed already, but you may need to use the Add Additional Simulators to download the ones you'd like to test in. So I'll select iPhone 13 Pro and click the build and run button; the one that looks like a play button. And this is going to build our app and launch it in the simulator. You can see the status here in the upper-right of the status bar. Now, switching to the simulator, I'll need to wait until the app gets installed and launches.
Once it's launched, I can switch over to the Settings app, find the Safari section, and tap on Extensions, and then turn on our Hello World extension. Now we can try it out in Safari. Launch Safari and tap the Aa menu. There, you'll find the Hello World extension, and I can tap on it, and it prompts for permission. I'll talk more about that later, but for now, I'll select Allow For One Day. And there we go. A "Hello World!" message in a pop-up. And that's how easy it is for you to convert a web extension written for another browser. So I had an idea for a project to build a preview of Open Graph metadata; the same metadata that's used to show previews on social media sites like Twitter. You get this nice image, a title, and description of the web page. And so, I'd like to build an extension that can preview that data using a pop-up. So that's what we're going to build today. So let's start by creating a new project in Xcode. I'll select Safari Extension App under Multiplatform and click Next. For the product name I'm going to use "Open Graph Preview." It already has my team filled in and an organization identifier using the reverse domain convention. I'll leave the language as Swift and click Next. Now it's asking for a place to save the project -- the desktop will work fine -- and I'm going to leave the "Create Git repository on Mac" enabled -- I love having version control built in -- and I'll click Create. And this will generate the project for us from the template. Now, before we go any further, let me give you a brief overview of Xcode. On the left side of the window is the navigation sidebar with all of the resources for your project organized for you. In the center is the main editor for the files you select from the navigator sidebar and then the right is an inspector sidebar that changes based on the file you're editing. We're not going to need the inspector sidebar so I'm going to go ahead and hide it so that we have a little bit more room to work with. Now let's take a look at the files that make up a new Safari web extension project, and at the top of the navigator sidebar is the project file. When you select it, it opens the main editor to a configuration option screen for the project. Now below that is the Shared (App) folder, and this has the resources for the app launch screen. When you click the disclosure triangle next to the Resources folder, you'll see it's just web resources. In fact, it's just an HTML file that you can edit, and the template sets you up to easily customize the markup, the styles, and the script. The Shared (Extensions) folder has all of the resources for our extension. And then when you expand the Resources folder, you'll see all the familiar files for a web extension. The manifest is here, the background script, the content script, and the pop-up files. The iOS (App) and macOS (App) folders have platform-specific resources and then below that the iOS (Extension) and macOS (Extension) folders have extension-specific resources for those platforms. We're not going to need to worry about them for this project, so I'm going to go ahead and put them away because all of our work will be in the Shared (Extension) files. And let's start in the manifest file. And you can see that the template provides everything we need to get started, including filling out the name and description from the project information. Below that, it specifies some default icons; you'll want to remember to provide your own for your project.
Next, will make some adjustments to our pop-up's style sheet. Here, I only need to make a few minor adjustments to make it look great on all platforms. And last, I just need to set up a responsive design layout for the images to fill the space and maintain aspect ratio. Finally, we come to the heart of the extension by editing the popup script, and first we need to grab a reference to those placeholder elements we defined in our popup HTML file. Next, we need to send a message to the content script and to do that, we need to query for the active tab. So to query for the active tab in the current window, we'll use the tabs.query API. And in the tabs.query API, we'll filter by active tab in the current window. Once we have the correct tab, we can now send the message. And in sending the message, we'll pass the current tabs ID and of course, our magic word. Then when we get the response, we simply need to update our placeholder elements with the data. That's it! Now, let's try running our extension in the simulator. Again, from the Target menu, I'll select iPhone 13 Pro simulator. I'll click the build and run button to kick off the process and switch to the simulator, where I'll wait for the app to launch. Once it's launched, I can enable our new extension in the Settings app. So let's switch to Settings and scroll to Safari. Then tap it and tap Extensions, and under Open Graph Preview I can toggle it on. Now let's launch Safari and I'll tap the Aa menu. And there in the menu is Open Graph Preview. Now, when I tap on this, I get a permission request for the content script. I'll select Allow For One Day. And bingo, we have a preview of this page's open graph data. Now let's try it on another page. Again from the Aa menu, I'll tap Open Graph Preview and Allow For One Day. And there we have it, but that's not quite right. We shouldn't have a broken image. So to figure out what's going on here, I can use Web Inspector in Safari. If you haven't enabled it, go to Safari Preferences and under the Advanced tab enable Show Develop menu in the menu bar. Then from the Develop menu, you can select Web Inspector for the content from the simulator. And when I do that, I can easily see that the image did not load. So if the URL is wrong, that's probably coming from the content. So let's open another Web Inspector for webkit.org.
Now I can search for the og meta tag data. And here I can see we have a title, and we have a description -- Ah, but we're missing the image. And that's what's going on. We have an undefined case we're not handling. So back in Xcode, all we need to do is handle that undefined case. Now I can try running this again. Clicking the Build and Run button, I'll click Replace to replace it on the simulator, and once it launches on the simulator, I can jump back into Safari and try it again.
From the Aa menu, I'll select Open Graph Preview and bingo. Perfect! It shows the title and description, but we should make sure it works on a page that does have an image, and it does. Perfect! Well, getting it to work in the simulator is a great way to test on a number of devices, but it's always a good idea to test on a real device. So I have an iPhone handy. Back in Xcode, I'll select the Target menu and choose the iPhone. Now let's build and run and launch it on this device.
After it's running on the device and launches, I'll need to enable it in Settings. So let's switch back to the Settings app. Under Safari and Extensions and Open Graph Preview, we'll toggle it on. Now, launching Safari again, I'll tap the Aa menu. And I can see the extension and tap it, and Allow For One Day. And it works perfect. Let's try this on a few other pages to make sure we have all of the cases working. Here on developer.apple.com it didn't prompt for extra permission because it's part of the apple.com domain. Let's try on webkit.org.
And here we'll tap the Aa menu again and Open Graph Preview and again it prompts because this is a different website. Perfect. And one more on the post with an image. We'll tap Aa, Open Graph Preview. Perfect! So, excellent. Now that we have our app and extension working in the simulator and a real device, I'd say we're ready to submit this to the App Store. First, there's a quick detail to take care of before submitting. In your project settings, select your iOS app target and be sure to set an App Category. I'll select Developer Tools. Next, from the Product menu, select Archive. And similar to the build process, you'll see the progress in the right side of the status bar. When it's ready, select the build archive you're ready to submit, and click the Distribute App button. From the assistant, under method of distribution we'll select App Store Connect and click Next. For destination, I'll leave Upload and click Next. And it will begin to communicate with App Store Connect to prepare an app record.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.