Making React Native more native: bringing iOS Spotlight to Headout
Turbo Modules let us extend React Native feature, enabling iOS Spotlight Search lookup for Headout bookings. Our Swift-based module indexes bookings with key details and embeds data for seamless previews. This article showcases how Turbo Modules can unlock powerful native features in React Native.

Ever wished you could find your Headout tickets without even opening the app? That’s exactly what we set out to solve.
At Headout, we’re always looking for ways to make things faster, simpler, and more intuitive for our users. Two years ago, we switched to React Native, and it has been an absolute game-changer—helping us ship features faster, improve developer productivity, and maintain a single codebase across iOS and Android.
But while React Native is fantastic for cross-platform development, it does have its limitations—especially when dealing with platform-specific features like iOS’s Spotlight Search. Since React Native doesn’t natively support Spotlight, we had to get creative. Enter Turbo Modules—a powerful way to extend React Native’s capabilities and integrate deep system-level features.
The challenge: Making bookings instantly searchable
Spotlight Search is one of iOS’s most underrated yet powerful features. It lets users quickly search for apps, contacts, messages, and even deeply indexed content within apps. We wanted to leverage this to:
- Allow users to search for their bookings directly from Spotlight
- Show relevant ticket details like title, description, and thumbnail
- Make the experience instantaneous and seamless
The problem? React Native doesn’t support Spotlight indexing out of the box. We needed a solution that would allow us to bridge the gap between our React Native app and iOS’s native Spotlight APIs.
The solution: A Turbo Module for Spotlight search
Why Turbo Modules? 🤔
Historically, when React Native developers needed to access platform-specific features, we built Native Modules—bridges between JavaScript and native platform code. These worked well, but they had drawbacks:
- Native Modules ran on the main thread, potentially blocking UI updates.
- They required synchronous communication, making them slower and prone to performance bottlenecks.
- Each module had to be manually linked, adding complexity to the build process.
With the introduction of React Native’s new architecture, Turbo Modules solve these issues by:
- Running on a separate thread, improving app performance
- Using JSI (JavaScript Interface) for direct, synchronous access to native code
- Enabling lazy loading, so modules are initialised only when needed
Turbo Modules gave us a modern, optimised way to implement Spotlight search without compromising React Native’s performance.
Building the Turbo Module for spotlight
To bring Spotlight indexing into our React Native app, we built a Swift-based Turbo Module that acts as a bridge. This module allows us to send booking data from React Native to iOS, which then indexes the data into Spotlight.
Core function: indexing tickets in spotlight
At the heart of this module is the indexTicket function, which takes the following parameters:
- id – A unique identifier for the search result
- title – The main title displayed in Spotlight
- description – Additional details shown under the title
- thumbnailUrl – The image shown in the search result
Here’s how we implemented it in Swift:
@objc
func indexTicket(
_ id: String,
title: String,
description: String,
thumbnailUrl: String,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
if #available(iOS 14, *) {
let attributeSet = CSSearchableItemAttributeSet(contentType: .item)
let queries = ["Ticket for ", "Booking for ", "Experience ", "Headout "]
attributeSet.keywords = queries.map { "\($0) \(title)" }
attributeSet.title = title
attributeSet.contentDescription = description
attributeSet.creator = "Headout Inc"
attributeSet.subject = "Ticket"
attributeSet.displayName = title
guard let url = URL(string: thumbnailUrl) else { resolver(false); return }
do {
let data = try Data(contentsOf: url)
let thumbnail = UIImage(data: data)
attributeSet.thumbnailData = thumbnail?.pngData()
} catch {
resolver(false)
return
}
let item = CSSearchableItem(uniqueIdentifier: id, domainIdentifier: "headout_ticket", attributeSet: attributeSet)
CSSearchableIndex.default().indexSearchableItems([item]) { (error) in
resolver(error == nil)
}
} else {
resolver(false)
}
}

With this function, our React Native app can send booking data to iOS, which then indexes it into Spotlight, making tickets instantly searchable!
Enhancing the Spotlight preview
While Spotlight indexing worked beautifully, we ran into an issue:
💡 Long-pressing a search result in Spotlight opened a generic preview box with just the title and subtitle.
We wanted to display full booking details in the preview, but fetching data dynamically would mean extra API calls—not ideal for speed.
Our hack: encoding booking data in the ID
1️⃣ We created a map containing all the essential booking details:
const ticketPreviewInfo = {
bookingId: bookingVoucher.bookingID,
experienceName: bookingVoucher.tour.tourGroupName,
experienceDate: date.format('DD MMM, YYYY'),
};
2️⃣ We encoded this map as a Base64 string and used it as the id:
const encodedId = Buffer.from(JSON.stringify(ticketPreviewInfo)).toString('base64');
3️⃣ On the Swift side, we added the QuickLook framework in our app and linked it with CoreSpotlight. Inside the PreviewViewController we decoded this Base64 id string and extracted the booking details when rendering the preview!
This turned each indexed Spotlight item into a self-contained data container, making previews instantaneous and eliminating unnecessary API calls.
The result 🤩
Spotlight Demo in Headout iOS App
Final thoughts: why this matters
By leveraging Turbo Modules, we were able to bridge React Native with iOS spotlight search, unlocking a powerful, native-like search experience for our users.
This project was a perfect example of how React Native’s limitations don’t have to be blockers—with the right tools, we can extend its capabilities and build features that feel completely native.
So if you’re working on a React Native app and feel constrained by missing platform features, Turbo Modules might just be the key to unlocking your next big feature! 🚀