YouVersion PlatformYouVersion Platform
PlatformBiblesDev Docs
CommunityPartnersSupport

YouVersion Platform

Build applications and integrate with the world's most popular Bible platform.

Platform Products

  • Platform Portal
  • Developer Documentation
  • App Management

Resources

  • Support
  • Press inquiries

Legal

  • Privacy Policy
  • Terms of Use

© 2026 YouVersion. All rights reserved.

  • Overview
  • API Reference
  • SDKs
  • Changelog
<  Back to Platform
SDK Introduction
Swift SDK
Kotlin SDK
JavaScript SDK
React SDK
    Quick StartComponentsHooks
    Guides
      Copyright & AttributionHighlightsMigration from Legacy AuthenticationSearchText directionTheming
React Native (Expo) SDK
Guides

Search

BibleReader.Toolbar includes search by default. Use this guide when you need a different layout or your own search interface.

Choose the customization that fits your application:

  • Unified search: Use useUnifiedSearch for top results across content kinds, such as verse references and related topics, in one request without pagination.
  • Move the search button: Place the SDK's search control outside the toolbar.
  • Custom search interface: Use useBibleSearch from @youversion/platform-react-hooks and render your own controls.
  • Host-owned reader search: Use your application's search interface in place of the reader's popover through onSearchPress.

Requirements

  • Get an App Key from YouVersion Platform.
  • Render search components and hooks inside YouVersionProvider. See the React quick start.

Install the UI package for reader search:

TerminalCode
npm install @youversion/platform-react-ui

If you import the search hook directly, install the hooks package:

TerminalCode
npm install @youversion/platform-react-hooks

Search verses and topics together

For a general search box, use useUnifiedSearch to retrieve top results in one request. This example selects verse references and related topics with fields.

You must supply both a Bible version ID and language preferences. The Bible version selects the text and language for verse results; the preferences select the language for topic results.

Use languageRanges: ["*"] to match all languages for topic results.

Render this component inside YouVersionProvider:

Code
import { useState } from "react"; import { useUnifiedSearch } from "@youversion/platform-react-hooks"; export function UnifiedSearch({ versionId }: { versionId: number }) { const [query, setQuery] = useState(""); const { data, loading, error, refetch } = useUnifiedSearch( { query, versionId, languageRanges: ["en"], fields: ["verses", "topics"], }, { enabled: query.length > 0 } ); return ( <section aria-label="Search"> <form onSubmit={(event) => { event.preventDefault(); const form = new FormData(event.currentTarget); setQuery(String(form.get("query") ?? "").trim()); }} > <label> Search <input name="query" required /> </label> <button type="submit">Search</button> </form> {loading && <p role="status">Searching...</p>} {error && ( <p role="alert"> Search was unavailable. <button onClick={refetch}>Retry</button> </p> )} {!loading && !error && data && ( <> <h2>Verses</h2> <ul> {data.verses.map((verse) => ( <li key={verse.id}>{verse.id}</li> ))} </ul> <h2>Topics</h2> <ul> {data.topics.map((topic) => ( <li key={topic.text}>{topic.text}</li> ))} </ul> {data.verses.length === 0 && data.topics.length === 0 && ( <p>No results found.</p> )} </> )} </section> ); }

Typing in this example does not send a request. Submitting the form updates the query; enabled prevents an empty query from loading. The hook does not manage a search field.

Other features you might want to add include input debouncing, search suggestions, and trending queries.

Omit fields to request all available result kinds; do not pass an empty array. See the core searchUnified parameters for currently supported kinds.

Use useBibleSearch for the paginated verse-search workflow. Unified search returns only top results, with no Load more action. It does not change the reader's built-in search. Verse hits contain passage IDs, not passage text; display a selected passage with BibleCard.

For options and return fields, see the useUnifiedSearch reference. For language selection, see Language ranges.

Move the search button

BibleReaderSearch renders a search icon button that opens a popover with a search field and results. Use it when your layout needs the search button outside the toolbar, such as above the passage while chapter controls remain below it.

Render BibleReaderSearch inside BibleReader.Root. Set search="none" on the toolbar to avoid showing two search buttons:

Code
import { BibleReader, BibleReaderSearch } from "@youversion/platform-react-ui"; function Reader() { return ( <BibleReader.Root defaultVersionId={3034}> <BibleReaderSearch /> <BibleReader.Content /> <BibleReader.Toolbar search="none" /> </BibleReader.Root> ); }

BibleReaderSearch must remain inside BibleReader.Root, and the reader must remain inside YouVersionProvider.

To control the popover's visibility, use these optional props:

  • open: The current open state when your application controls visibility.
  • onOpenChange: A callback that receives changes to the open state.
  • defaultOpen: The initial open state when the component controls visibility.

Build a custom search interface

Import useBibleSearch from @youversion/platform-react-hooks. The hook takes a versionId and an optional bookIds filter.

Save this component as VerseSearch.tsx. It lists passage identifiers and calls onSelect when the user chooses one.

Render the component inside YouVersionProvider, and use onSelect to display the passage or navigate to it. The host-owned search example connects this component to reader navigation.

VerseSearch.tsx
import { useBibleSearch } from "@youversion/platform-react-hooks"; type VerseSearchProps = { versionId: number; onSelect: (passageId: string) => void; }; export function VerseSearch({ versionId, onSelect }: VerseSearchProps) { const search = useBibleSearch({ versionId }); const { phase } = search; return ( <section aria-label="Bible search"> <form onSubmit={(event) => { event.preventDefault(); search.submit(); }} > <label> Search the Bible <input value={search.query} onChange={(event) => search.setQuery(event.target.value)} /> </label> <button type="submit" disabled={!search.query.trim()}> Search </button> </form> {(phase.kind === "trending" || phase.kind === "suggesting") && ( <> {phase.loading && <p role="status">Loading queries...</p>} {phase.error && ( <p role="alert"> Could not load queries.{" "} <button onClick={search.retry}>Retry</button> </p> )} <ul aria-label="Search queries"> {phase.queries.map((query) => ( <li key={query.text}> <button onClick={() => search.selectSuggestion(query.text)}> {query.text} </button> </li> ))} </ul> </> )} {phase.kind === "searching" && <p role="status">Searching...</p>} {phase.kind === "empty" && <p role="status">No verses found.</p>} {phase.kind === "failed" && ( <p role="alert"> Search was unavailable. <button onClick={search.retry}>Retry</button> </p> )} {phase.kind === "results" && ( <> <ul aria-label="Search results"> {phase.verses.map((verse) => ( <li key={verse.id}> <button onClick={() => onSelect(verse.id)}>{verse.id}</button> </li> ))} </ul> {phase.nextPage === "available" && ( <button onClick={search.loadMore}>Load more</button> )} {phase.nextPage === "loading" && ( <p role="status">Loading more results...</p> )} {phase.nextPage === "failed" && ( <p role="alert"> Could not load more results.{" "} <button onClick={search.retry}>Retry</button> </p> )} </> )} </section> ); }

Query lifecycle

  • setQuery(raw) updates the input and truncates it to 100 grapheme clusters. The hook trims the text used for requests.
  • If the query is empty, the hook requests trending queries. For nonempty input, the hook waits 300 ms for typing to pause before requesting suggestions. This delay is called a debounce.
  • submit() or selectSuggestion(text) starts verse search. Typing alone does not request verse results.
  • Changing the normalized query (the trimmed input) returns to suggestions or trending queries. Changing versionId restarts the submitted search in the new Bible version.
  • Suggestions and trending queries use the Bible version's language tag. If the tag is missing or invalid, the hook uses * to match all languages. See Language ranges.

A grapheme cluster is a user-perceived character, which can contain multiple Unicode code points.

Search phases

The hook returns a phase object. Read phase.kind before accessing that phase's fields:

  • trending and suggesting expose queries, loading, and an optional error.
  • searching means the hook is waiting for the first visible results.
  • results exposes verses and nextPage. nextPage is "none", "available", "loading", or "failed".
  • empty means no results match the current search and filter.
  • failed exposes the initial search error.

The suggesting phase also exposes debouncing. This field describes pending input, not a network request. Use loading, not debouncing, for a network-loading indicator.

If a later page fails, the hook keeps the existing results and sets nextPage to "failed". retry() retries failed query suggestions, the initial search, or the failed results page.

Results and book filters

Each result contains a passage id in Unified Standard Format Markers (USFM) reference format, a book, a chapter, and an array of verse numbers. The hook removes duplicate passage IDs and omits chapter-only hits.

The hook does not return passage text. Fetch text with usePassage, or display a selected result with BibleCard, which includes attribution.

The hook requests pages of 20 hits and accumulates results when you call loadMore().

Pass bookIds, such as ["JHN"], to filter by book. Filtering happens in the client, not in the search request.

If no matching results are visible and bookIds contains at least one book, the hook automatically requests more pages until it finds a match or reaches the end.

Once matching results are visible, a loadMore() call can add no visible results if the next page contains no matches. If nextPage is "available", call loadMore() again to request another page.

  • To include all books, omit bookIds.
  • To include no books, pass [].

useBibleSearch does not expose topic search, search intent, or query-correction metadata; use useUnifiedSearch for those. For direct requests, use useSearchClient() inside the provider and call the methods in the TypeScript search guide. Manage loading, results, and errors in your interface for these direct calls.

Connect host-owned search to the reader

If your application owns the search interface, pass onSearchPress to BibleReader.Root. This callback replaces the built-in popover and receives the reader's current versionId, book, and chapter. Returning a value from the callback does not navigate the reader.

Connect your interface to reader navigation:

  1. Create one BibleReaderNavigation instance and retain it across renders.
  2. Pass that instance to BibleReader.Root through its navigation prop.
  3. When the user selects a result, call focusReference({ versionId, passageId }) on the navigation instance.

The example below reuses VerseSearch.tsx from the custom search section. Replace YOUR_APP_KEY with your App Key:

App.tsx
import { useState } from "react"; import { BibleReader, BibleReaderNavigation, YouVersionProvider, } from "@youversion/platform-react-ui"; import { VerseSearch } from "./VerseSearch"; export default function App() { const [navigation] = useState(() => new BibleReaderNavigation()); const [searchVersionId, setSearchVersionId] = useState<number | null>(null); return ( <YouVersionProvider appKey="YOUR_APP_KEY"> {searchVersionId !== null && ( <section aria-label="Reader search"> <button onClick={() => setSearchVersionId(null)}>Close search</button> <VerseSearch key={searchVersionId} versionId={searchVersionId} onSelect={(passageId) => { navigation.focusReference({ versionId: searchVersionId, passageId, }); setSearchVersionId(null); }} /> </section> )} <div style={{ height: "100svh" }}> <BibleReader.Root defaultVersionId={3034} navigation={navigation} onSearchPress={({ versionId }) => setSearchVersionId(versionId)} > <BibleReader.Content /> <BibleReader.Toolbar /> </BibleReader.Root> </div> </YouVersionProvider> ); }

focusReference({ versionId, passageId }) opens the full chapter, scrolls to the passage, and temporarily emphasizes its verses. It does not create a saved highlight. If you request navigation before the reader mounts, the connection retains the newest pending request for mounting.

Pass the original result id as passageId to preserve a verse range. For example, JHN.3.16-18 focuses verses 16 through 18, not only verse 16. You do not need to parse the identifier before calling focusReference.

To check this integration:

  1. Open Search from the reader toolbar. Your application's search interface should appear instead of the built-in popover.
  2. Submit a query.
  3. Select a result. Your search interface should close, and the reader should show the selected passage in its chapter.
Last modified on October 28, 2026
Migration from Legacy AuthenticationText direction
On this page
  • Requirements
  • Search verses and topics together
  • Move the search button
  • Build a custom search interface
    • Query lifecycle
    • Search phases
    • Results and book filters
  • Connect host-owned search to the reader
React
React
React
React