Universal Links allow your users to intelligently follow links to content inside your app or to your website. Learn how the latest enhancements in Universal Links give your users the most integrated mobile and desktop experience, even when your app isn't installed on their device.
My name is Jonathan Grynspan and
I work on the Core Frameworks
Team at Apple.
Today, I'll be showing you how
to use universal links in your
We introduced the universal
links in iOS 9 as a great way to
provide rich content both on the
web and in your app.
Today, I'll be showing you the
enhancements we've made to this
Whether you've already adopted
universal links in your iOS app
or you're adding them to your
macOS app, you will want to pay
close attention to the changes
I'll be discussing.
To begin, let's talk about what
universal links are.
Universal links are HTTP or
HTTPS URLs that Apple's
operating systems recognize as
pointing to resources either on
the web or in your app.
This means that a single URL can
represent that content, whether
your users have your app
installed or it just haven't
downloaded it yet.
They are great way to increase
user engagement within your app.
Universal links are introduced
in iOS 9 and tvOS 10.
I'm happy to announce that we
are introducing them to the Mac
with macOS 10.15, whether you're
using AppKit or UIKit.
I'll tell you more about that in
Universal links are securely
associated between your app and
Your app adopts an entitlement
in Xcode that indicates which
domains it can represent and
your web server adopts a single
JSON file that contains more
details about what parts of its
domain are representable in your
These two ways secure handshake
ensures nobody can redirect your
users into their apps instead of
We recommend that where you are
currently using custom URL
schemes, you begin migrating to
universal links today.
Custom URL schemes are
inherently insecure and can be
abused by malicious developers.
New uses of custom URL schemes
are highly discouraged.
Now that we know what universal
links are, let's talk about how
to build them.
We'll start with your web
Your web server must have a
valid HTTPS certificate.
HTTP is not secure and cannot be
used to validate an association
between your app and your
The root certificate used to
sign your HTTPS certificate must
be recognized by the operating
Custom root certificates are not
After generating your
certificate and configuring your
server, add your
This is a JSON file.
We'll discuss this format in a
When your app is installed on an
Apple device, the operating
system downloads this file to
determine what services the
server will let your app use.
The system also periodically
downloads updates for this file.
Universal links are one of many
services that may be included in
This file should be located at
HTTPS://your domain name/
Other paths are deprecated.
In the past, we've discussed
This has never been a necessary
step to support universal links
so it is now deprecated.
Support for sign JSON files and
JSON files at other paths will
be removed in a future release.
With that out of the way, let's
take a look at your
If you already have one of these
files on your web server this
probably looks familiar, but we
have a few changes to introduce
At the top level is a dictionary
whose keys are service types.
For universal links, the key is
applinks like you see here but
other services are also
We'll be focusing solely on
Under that top level key are the
apps key and the details key.
If you are targeting iOS 13,
tvOS 13 and macOS 10.15, you do
not need the apps key, so you
can remove it.
If you are continuing to provide
support for iOS 12, tvOS 12 or
earlier, you'll still need it.
For universal links, it should
always be an empty array.
The details key contains an
array of dictionaries, each of
which represents a specific apps
universal links configuration.
In the past, we supported using
a dictionary structure here
instead of an array but that
configuration is obsolete.
Under the details key is an
appID key whose value is your
Your app identifier consists of
an alphanumeric, 10 character
prefix provided by Apple, a
period, and your bundle
The prefix may or may not be
equal to your team identifier.
Check the developer portal to
confirm your app identifier.
If you have multiple apps with
the same universal links
configuration, you may not want
to repeat the relevant JSON.
If you are targeting this year's
releases, you can reduce the
size of this file by using the
plural appIDs key.
The value of that key is an
array of app identifiers.
If you need to support previous
releases, you should keep using
the singular appID key for each
Next is the paths key.
This key contains an array of
Pattern matching is performed
the same way it is in terminal.
The asterisk is used to indicate
multiple wildcard characters
while the question mark matches
just one character.
Beginning this year, we are
replacing the paths key with the
This key's value is an array of
dictionaries, each of which
contains zero or more URL
components to pattern match
As before, you can match against
the URL's path component whose
key is the forward slash.
If you need to support previous
releases, you can keep the paths
iOS 13, tvOS 13 and macOS 10.15
will ignore it if the components
key is present.
And now you can match against
the URL's fragment component
whose key is the hash mark and
you can match the query
component whose key is the
Now many, if not most URLs out
there divide their query
component up into key value
pairs called query items.
For the query component, you can
specify a dictionary instead of
a string as its value and
pattern match individual query
URLs may repeat query item names
and the operating system will
require that all instances of a
given query item name match your
Query items with no value and
absent query items are treated
by the operating system as if
they have a value equal to the
For a components dictionary to
match a candidate URL, all the
specified components must match.
If you don't specify a
component, the operating
system's default behavior is to
simply ignore that component.
So if, for example, your app
doesn't care about the fragment
component of a URL, you don't
need to specify it in this file.
You may have sections of your
website that are not able to be
represented in your app yet.
You can exclude such subsections
by specifying the exclude key
with a Boolean value of true.
This key has the same behavior
as a not keyword that you used
in the old paths key.
That key word is not supported
when using the component's
Here we have a few examples of
URLs that we need to pattern
I'm working on a meal ordering
app and I'm using universal
links to bring users into my app
On the left, you can see some
JSON from my server and on the
right, you can see some URLs.
First, I want to match all the
order forms on my website which
are all located on a path where
the first component can be
The second component is order
and there are no further path
This pattern will match a URL
such as these two on the right.
Time for lunch.
Next, I know a lot of my
customers are going to want to
put cheese on their tacos so I'm
going to match any URL where the
path starts with the path
component taco and where a query
item named cheese is specified.
You'll notice that I specify a
question mark and an asterisk as
the pattern from the query items
A pattern consisting of a single
asterisk matches any string,
including the empty string.
And a missing query item has a
value equivalent to the empty
So to match against the string
that's at least one character
long, I specify a question mark
and then any additional
characters are matched by the
That matches our third URL.
The fourth and fifth URLs look
very similar but there's a good
reason for that.
My website also has lots of
four-digit coupon codes that the
app can handle.
But if they start with a 1, I
want them to stay in the
Because the operating system is
going to look at the available
patterns from top to bottom,
we'll first mark coupon code
starting with the one as
This tells the system to stop
looking here if it finds a match
but not to open the URL as a
Then any other coupon will match
the fourth and final components
Before we move on to your app,
let's discuss how to support an
URLs are always ASCII coded so
component matching is done in
ASCII as well.
If you need to match Unicode
characters present and code them
like you would in your URL.
Because components are present
and coded, a Unicode character
may be represented by more than
one ASCII character, so keep
that in mind when using the
question mark in your patterns.
When you build your JSON, you
may be tempted to provide
country-specific patterns for
every country you support.
This increases the size of your
If your pattern-matching is
consistent between countries,
you can reduce the traffic to
and from your server by
simplifying you JSON.
For instance, if you separate
your content by two-letter
country code, you need to only
specify two question marks where
you are previously using those
Other more complex patterns like
you see here can also be matched
If you encounter a URL with an
invalid country code or locale
specific identifier, just treat
it like the user's current
Beginning in this release, the
operating system will prioritize
downloads based on where a user
is most likely to browse.
We'll still download them all
when an app is installed but at
The top-level domains .com,
.net, and .org, are high
priority domains because they
account for so much internet
Country code TLDs also known as
ccTLDs and internationalized
TLDs are also prioritized if
they match the user's current
For example, the average user in
China is more likely to visit a
domain under a Chinese ccTLD
than under, for example, an
Italian or Russian ccTLD.
So now your server is ready to
support universal links.
Let's update your app.
Open your project in Xcode and
navigate to your project
Add the Associated Domains
This adds a new entitlement to
the selected target.
You can modify this entitlement
directly from this view.
The value of this entitlement is
an array of strings of the form
service type: domain name.
For universal links, the service
type is applinks like it was in
The order of values in this
array is ignored by the system.
Here, we declare that your app
supports universal links for
When your app is then installed,
the operating system will visit
www.example.com looking for the
we just discussed.
If it's present and it contains
information for these apps, app
identifier, then the association
It's also possible to indicate
wildcard support for subdomains
of a given domain as shown here.
In this case, the operating
system will visit example.com.
No www at this time.
Exact domains have higher
priority during universal links
look up than wildcard domains.
In this case, that means when
the system opens a URL at
www.example.com, it will try to
match patterns from that domain
before the ones it got from the
Patterns from the parent domain
will only be matched if no match
was found at the fully qualified
Finally, here's an example of an
Since URLs are always ASCII,
your internationalized domain
names will need to be encoded
For more information on
Punycode, see RFC 3492.
Now that your app declares
support for certain domains,
you'll need to actually parse
the URLs as they come in.
Universal links are based on
foundations and as user activity
class and are handled by your
You'll need a handler for
incoming user activities.
If you already support hand-off
or other similar technologies,
you may already have this method
in your app delegate.
This method returns a bool.
Return true if you could
successfully open the user
activity and false if you
If you're using UI scene, then a
similar delegate method is
If you're using AppKit, the
signature of this method is
Just replace UI with NS like you
We'll use UI application for the
remainder of this session.
Next, we'll check that the
This helps distinguish universal
links from other incoming user
activities that your app may
Even if you don't support other
activity types now, it's a good
idea to check the activity type
in case you need to support
other types in the future.
The activity type looks good.
Let's grab the webpage URL from
the user activity object.
This will never be nil for a
universal link and let's build a
URL components draft from the
You should always parse URLs
using URL components.
Using regular expressions or
manually parsing a URL string
may leave you vulnerable to
We're past the guard statement
so let's examine the contents of
In this case I'm interested in
the query items of the URL but
you can use any of the URL's
components to root activities at
If you support universal links
from multiple domains, don't
forget to check the host
Our code is complete and our
server is configured but there
are few differences when using
universal links on macOS.
Universal links open in the
browser by default on macOS.
When they do, Safari will give
the user the option to open them
in your app.
If the user selects this option,
your links will continue to open
in your app afterward.
Unlike iOS, macOS supports
launching apps present on remote
Apps on remote volumes cannot
use universal links.
They must be installed locally.
If the user downloads your app
from the App Store, the system
will begin downloading
as soon as soon as your app is
installed or updated.
If your app is developer
ID-signed, the system will not
begin these downloads until the
user has launched your app at
Because a universal link is
backed by a secure association
with an app identifier, only one
copy of a given app will be able
to handle universal links on a
Typically, this will be the copy
of the app present in slash
Keep that in mind anytime you
need to test changes to your
associated domains entitlement.
If you are on the other end of
an operation and want to open a
universal link, UIApplication
and NSWorkspace and launch
services will all automatically
open them when available.
If you want to require opening a
universal link in an app rather
than the default browser, you
can use UIApplication or
NSWorkspace API as appropriate.
If these open operations fail,
it means that a universal link
was not available for the
If you're developing a web
browser from macOS, additional
API will be made available to
help you support universal
To help you make the best apps
and to provide the best user
experience, I've got a few final
tips to share with you.
The first is to fail gracefully.
It's possible you'll be provided
with URLs that represent
outdated, invalid, or
If you determine that a
universal link can't be opened
by your app, you can often open
it in Safari View Controller.
This keeps the user engaged in
If Safari View Controller is not
an option, consider opening the
URL in Safari, or at a minimum,
prompting with details about the
Avoid sending the user to a
If the user is visiting your
website, use the Smart App
Banner to provide a link either
to the App Store or to your
The Smart App Banner integrates
seamlessly with Safari and looks
custom URL schemes required to
Finally, if you have feedback on
how we can improve universal
links, I would love to hear it.
Please use the feedback
assistant and let us know what
we can do to make universal
links even better.
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.