{"id":1425,"date":"2026-06-12T13:00:00","date_gmt":"2026-06-12T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1425"},"modified":"2026-03-07T03:49:13","modified_gmt":"2026-03-07T08:49:13","slug":"apple-spotlight-indexing","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/apple-spotlight-indexing\/","title":{"rendered":"Apple Spotlight Indexing for iOS Apps"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Apple Spotlight is the search system built into every iOS device. When users swipe down on their home screen and type a query, Spotlight searches across apps, contacts, messages, files, and the web. If your app indexes its content with Spotlight, your app&#39;s content appears in these results, giving users a direct path back into your app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike Google App Indexing (which surfaces content in web search results), Spotlight indexing is on-device. The index lives locally, which means it is fast, private, and works offline. This guide covers the three indexing methods Apple provides and how to implement each one. For the broader app indexing strategy, see <a href=\"https:\/\/tolinku.com\/blog\/app-indexing-seo-mobile-apps\/\">app indexing and SEO for mobile apps<\/a>. For Google&#39;s equivalent, see <a href=\"https:\/\/tolinku.com\/blog\/google-app-indexing-setup\/\">Google App Indexing setup<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Three Indexing Methods<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Apple provides three complementary methods for indexing app content in Spotlight:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>Best For<\/th>\n<th>Scope<\/th>\n<th>Requires Web<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td><a href=\"https:\/\/developer.apple.com\/documentation\/corespotlight\" rel=\"nofollow noopener\" target=\"_blank\">CoreSpotlight<\/a><\/td>\n<td>App-specific content (products, articles, messages)<\/td>\n<td>On-device only<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/nsuseractivity\" rel=\"nofollow noopener\" target=\"_blank\">NSUserActivity<\/a><\/td>\n<td>Content the user has viewed or interacted with<\/td>\n<td>On-device + Apple Search<\/td>\n<td>Yes (for public indexing)<\/td>\n<\/tr>\n<tr>\n<td>Web Markup<\/td>\n<td>Content that exists on your website<\/td>\n<td>Apple Search (cross-device)<\/td>\n<td>Yes<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For maximum coverage, use all three. CoreSpotlight indexes content the user has not viewed yet, NSUserActivity indexes content the user has engaged with (and optionally makes it public), and web markup indexes content from your website.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Method 1: CoreSpotlight<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CoreSpotlight lets you create a searchable index of your app&#39;s content. Each item you index has a title, description, thumbnail, and a unique identifier that your app uses to route the user to the correct content.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Basic Implementation<\/h3>\n\n\n\n<pre><code class=\"language-swift\">import CoreSpotlight\nimport MobileCoreServices\n\nfunc indexProduct(_ product: Product) {\n    let attributeSet = CSSearchableItemAttributeSet(contentType: .content)\n    attributeSet.title = product.name\n    attributeSet.contentDescription = product.description\n    attributeSet.thumbnailData = product.thumbnailData\n\n    \/\/ Optional: add additional metadata\n    attributeSet.rating = NSNumber(value: product.averageRating)\n    attributeSet.ratingDescription = &quot;\\(product.reviewCount) reviews&quot;\n\n    let item = CSSearchableItem(\n        uniqueIdentifier: &quot;product-\\(product.id)&quot;,\n        domainIdentifier: &quot;com.yourapp.products&quot;,\n        attributeSet: attributeSet\n    )\n\n    \/\/ Items expire after 30 days by default\n    item.expirationDate = Calendar.current.date(byAdding: .day, value: 30, to: Date())\n\n    CSSearchableIndex.default().indexSearchableItems([item]) { error in\n        if let error = error {\n            print(&quot;Indexing failed: \\(error.localizedDescription)&quot;)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Handling Spotlight Taps<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a user taps a Spotlight result, your app receives the unique identifier. Handle it in your AppDelegate or SceneDelegate:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ SceneDelegate\nfunc scene(_ scene: UIScene, continue userActivity: NSUserActivity) {\n    if userActivity.activityType == CSSearchableItemActionType {\n        guard let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else {\n            return\n        }\n\n        \/\/ Parse the identifier and navigate\n        if identifier.hasPrefix(&quot;product-&quot;) {\n            let productId = String(identifier.dropFirst(&quot;product-&quot;.count))\n            navigateToProduct(productId)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Batch Indexing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For large catalogs, index items in batches:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func indexAllProducts(_ products: [Product]) {\n    let items = products.map { product -&gt; CSSearchableItem in\n        let attributeSet = CSSearchableItemAttributeSet(contentType: .content)\n        attributeSet.title = product.name\n        attributeSet.contentDescription = product.description\n\n        return CSSearchableItem(\n            uniqueIdentifier: &quot;product-\\(product.id)&quot;,\n            domainIdentifier: &quot;com.yourapp.products&quot;,\n            attributeSet: attributeSet\n        )\n    }\n\n    \/\/ Index in batches of 100\n    let batchSize = 100\n    for batchStart in stride(from: 0, to: items.count, by: batchSize) {\n        let batchEnd = min(batchStart + batchSize, items.count)\n        let batch = Array(items[batchStart..&lt;batchEnd])\n\n        CSSearchableIndex.default().indexSearchableItems(batch) { error in\n            if let error = error {\n                print(&quot;Batch indexing failed: \\(error.localizedDescription)&quot;)\n            }\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Removing Items<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When content is deleted from your app, remove it from the Spotlight index:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ Remove a single item\nCSSearchableIndex.default().deleteSearchableItems(\n    withIdentifiers: [&quot;product-123&quot;]\n) { error in\n    \/\/ Handle error\n}\n\n\/\/ Remove all items in a domain\nCSSearchableIndex.default().deleteSearchableItems(\n    withDomainIdentifiers: [&quot;com.yourapp.products&quot;]\n) { error in\n    \/\/ Handle error\n}\n\n\/\/ Remove everything\nCSSearchableIndex.default().deleteAllSearchableItems { error in\n    \/\/ Handle error\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Method 2: NSUserActivity<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/nsuseractivity\" rel=\"nofollow noopener\" target=\"_blank\">NSUserActivity<\/a> was originally designed for Handoff (continuing activities across Apple devices) but also powers Spotlight indexing and Siri suggestions. When you mark an activity as eligible for search, it appears in Spotlight results.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Basic Implementation<\/h3>\n\n\n\n<pre><code class=\"language-swift\">class ProductViewController: UIViewController {\n    var product: Product!\n\n    override func viewDidAppear(_ animated: Bool) {\n        super.viewDidAppear(animated)\n\n        let activity = NSUserActivity(activityType: &quot;com.yourapp.viewProduct&quot;)\n        activity.title = product.name\n        activity.isEligibleForSearch = true\n        activity.isEligibleForPublicIndexing = true \/\/ Makes it visible to all users\n\n        \/\/ Required for public indexing: associate with a web URL\n        activity.webpageURL = URL(string: &quot;https:\/\/www.yourapp.com\/products\/\\(product.id)&quot;)\n\n        \/\/ Search attributes\n        let attributes = CSSearchableItemAttributeSet(contentType: .content)\n        attributes.contentDescription = product.description\n        attributes.thumbnailData = product.thumbnailData\n        activity.contentAttributeSet = attributes\n\n        \/\/ Keywords for search\n        activity.keywords = Set(product.tags)\n\n        \/\/ Link to the app content\n        activity.userInfo = [&quot;productId&quot;: product.id]\n\n        self.userActivity = activity\n        activity.becomeCurrent()\n    }\n\n    override func viewDidDisappear(_ animated: Bool) {\n        super.viewDidDisappear(animated)\n        userActivity?.invalidate()\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Public Indexing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When <code>isEligibleForPublicIndexing<\/code> is <code>true<\/code>, Apple may include your content in search results for all users (not just the user who viewed it). This is Apple&#39;s equivalent of Google App Indexing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Requirements for public indexing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The activity must have a <code>webpageURL<\/code> that points to a real web page.<\/li>\n<li>The web page must have a matching <a href=\"https:\/\/developer.apple.com\/documentation\/bundleresources\/applinks\" rel=\"nofollow noopener\" target=\"_blank\">Universal Links<\/a> configuration.<\/li>\n<li>The <code>apple-app-site-association<\/code> file must be correctly configured on your domain.<\/li>\n<li>Apple must verify that the content is high-quality and relevant.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">See the <a href=\"https:\/\/tolinku.com\/blog\/universal-links-everything-you-need-to-know\/\">Universal Links guide<\/a> for the full configuration.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Handling NSUserActivity Taps<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a user taps an NSUserActivity-based Spotlight result:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {\n    switch userActivity.activityType {\n    case &quot;com.yourapp.viewProduct&quot;:\n        if let productId = userActivity.userInfo?[&quot;productId&quot;] as? String {\n            navigateToProduct(productId)\n        }\n    case CSSearchableItemActionType:\n        \/\/ CoreSpotlight result (handled separately)\n        break\n    default:\n        \/\/ Check for Universal Link\n        if let url = userActivity.webpageURL {\n            handleUniversalLink(url)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Method 3: Web Markup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If your content exists on a website, you can use <a href=\"https:\/\/developer.apple.com\/library\/archive\/documentation\/General\/Conceptual\/AppSearch\/WebContent.html\" rel=\"nofollow noopener\" target=\"_blank\">Apple&#39;s web markup<\/a> to associate web pages with your app. This requires no code changes in your app (only website changes), but it depends on having Universal Links configured.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Smart App Banner<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Add a Smart App Banner meta tag to your web pages:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;meta name=&quot;apple-itunes-app&quot; content=&quot;app-id=YOUR_APP_STORE_ID, app-argument=https:\/\/www.yourapp.com\/products\/123&quot;&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>app-argument<\/code> is the URL that your app receives when the user taps &quot;Open&quot; on the banner. Your app must handle this URL via Universal Links.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Open Graph and Schema.org<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Apple&#39;s crawler reads standard web markup:<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;head&gt;\n  &lt;meta property=&quot;og:title&quot; content=&quot;Product Name&quot; \/&gt;\n  &lt;meta property=&quot;og:description&quot; content=&quot;Product description&quot; \/&gt;\n  &lt;meta property=&quot;og:image&quot; content=&quot;https:\/\/www.yourapp.com\/images\/product-123.jpg&quot; \/&gt;\n  &lt;meta property=&quot;og:url&quot; content=&quot;https:\/\/www.yourapp.com\/products\/123&quot; \/&gt;\n&lt;\/head&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Combined with a working Universal Links setup, this markup helps Apple associate your web content with your app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Best Practices<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Index Strategically<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do not index everything. Focus on content that users are likely to search for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Product names and descriptions.<\/li>\n<li>Article titles and summaries.<\/li>\n<li>Contact names (for messaging apps).<\/li>\n<li>Location names (for maps\/travel apps).<\/li>\n<li>Media titles (for entertainment apps).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid indexing transient content (notifications, loading screens, settings pages).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Keep the Index Fresh<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Stale results frustrate users. Update the index when content changes:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func updateProduct(_ product: Product) {\n    \/\/ Re-index with updated data\n    indexProduct(product)\n}\n\nfunc deleteProduct(_ productId: String) {\n    CSSearchableIndex.default().deleteSearchableItems(\n        withIdentifiers: [&quot;product-\\(productId)&quot;]\n    )\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Use Rich Attributes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CoreSpotlight supports many <a href=\"https:\/\/developer.apple.com\/documentation\/corespotlight\/cssearchableitemattributeset\" rel=\"nofollow noopener\" target=\"_blank\">attribute types<\/a>:<\/p>\n\n\n\n<pre><code class=\"language-swift\">let attributes = CSSearchableItemAttributeSet(contentType: .content)\nattributes.title = &quot;Restaurant Name&quot;\nattributes.contentDescription = &quot;Italian restaurant in downtown&quot;\nattributes.thumbnailData = thumbnailData\nattributes.rating = 4.5\nattributes.ratingDescription = &quot;4.5 stars (230 reviews)&quot;\nattributes.phoneNumbers = [&quot;+1-555-123-4567&quot;]\nattributes.supportsPhoneCall = true\nattributes.latitude = 37.7749\nattributes.longitude = -122.4194\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Rich attributes make your Spotlight results more informative and actionable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Test on Device<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Spotlight indexing only works on physical devices (not the simulator for some features). Test by:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Running your app on a device.<\/li>\n<li>Navigating to content you want indexed.<\/li>\n<li>Going to the home screen and swiping down to open Spotlight.<\/li>\n<li>Searching for the content title or keywords.<\/li>\n<li>Tapping the result and verifying navigation.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku and Spotlight Indexing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> handles the Universal Links infrastructure that powers Apple&#39;s public indexing. When you configure your <a href=\"https:\/\/tolinku.com\/docs\/developer\/universal-links\/\">custom domain<\/a> with Tolinku, the platform automatically serves the <code>apple-app-site-association<\/code> file, enabling both Universal Links and the web-based Spotlight indexing path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For Google&#39;s equivalent, see <a href=\"https:\/\/tolinku.com\/blog\/google-app-indexing-setup\/\">Google App Indexing setup<\/a>. For the broader strategy, see <a href=\"https:\/\/tolinku.com\/blog\/app-indexing-seo-mobile-apps\/\">app indexing and SEO for mobile apps<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Index your app content in Apple Spotlight search. Use CoreSpotlight, NSUserActivity, and web markup to surface content in on-device search results.<\/p>\n","protected":false},"author":2,"featured_media":1424,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Apple Spotlight Indexing for iOS Apps","rank_math_description":"Index your app content in Apple Spotlight search. Use CoreSpotlight, NSUserActivity, and web markup to surface content in on-device search.","rank_math_focus_keyword":"Apple Spotlight indexing","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-apple-spotlight-indexing.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-apple-spotlight-indexing.png","footnotes":""},"categories":[16],"tags":[64,357,20,24,69,63,356,31],"class_list":["post-1425","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-marketing","tag-app-indexing","tag-core-spotlight","tag-deep-linking","tag-ios","tag-mobile-development","tag-seo","tag-spotlight","tag-swift"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1425","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1425"}],"version-history":[{"count":4,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1425\/revisions"}],"predecessor-version":[{"id":2599,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1425\/revisions\/2599"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1424"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1425"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1425"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1425"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}