Get started
Quick start
Create an index, upload products from your server, choose what to search, and search from a web page.
You need a Findlane account (start free), and Node.js 18 or later, Bun, or Deno on your server.
1. Create an index
In the dashboard, choose New index and name it, for example products. Names use lowercase letters, digits, _, and -, and start with a letter.
A new index searches title and brand.name and counts values of brand.name as a facet, so a catalog with those fields searches well before you change anything.
2. Create an API key
Open API keys, choose Create key, and give it the search and write scopes. Put the key and the Workspace ID shown on that page in your server's environment:
FINDLANE_WORKSPACE_ID=your-workspace-id
FINDLANE_API_KEY=tsk_your_api_keyThe key is shown once. Keep it on your server: it can change your catalog.
3. Upload products
npm install @findlane/serverimport { SearchServerClient } from "@findlane/server"
const client = new SearchServerClient({
workspaceId: process.env.FINDLANE_WORKSPACE_ID!,
apiKey: process.env.FINDLANE_API_KEY!,
})
type Product = {
objectID: string
title: string
brand: { name: string }
price: number
inStock: boolean
}
const products = client.index<Product>("products")
await products.upsert([
{
objectID: "sku-1",
title: "Trail running shoe",
brand: { name: "Acme" },
price: 129,
inStock: true,
},
{
objectID: "sku-2",
title: "Waterproof rain jacket",
brand: { name: "Northwind" },
price: 189,
inStock: false,
},
])upsert adds or replaces up to 1,000 records a call, matched by objectID, and they are searchable when it returns. To load or refresh a whole catalog, use replaceAllStaged. You can also upload a JSON file on the index's Records page.
4. Choose what to search and filter
Settings refer to fields your records have, so save them after uploading. Fetch the current settings, change them, and save them whole:
const settings = await products.getSettings()
await products.replaceSettings({
...settings,
searchableAttributes: [
{ field: "title", weight: 8 },
{ field: "brand.name", weight: 4 },
],
facetFields: ["brand.name", "inStock"],
sorts: [
{ id: "price-asc", label: "Price: low to high", field: "price", direction: "asc", thenBy: [] },
],
})
const results = await products.search({ q: "trail sho", facets: ["brand.name"] })
console.log(
results.total,
results.hits.map((hit) => hit.record.title)
)The index's Search settings page does the same, and its Playground tries searches. Search settings covers every option.
5. Search from the browser
On the index's Public access page, add the origins your storefront runs on under Allowed origins, such as https://shop.example.com and http://localhost:5173. Then copy the Public ID, and add the browser SDK with npm or a script tag.
With npm
npm install @findlane/browserimport { SearchBrowserClient } from "@findlane/browser"
const client = new SearchBrowserClient({ publicId: "pub_your_public_id" })
const results = await client.search<Product>({
q: "trail sho",
filters: { inStock: true },
facets: ["brand.name"],
highlight: true,
limit: 12,
})
for (const hit of results.hits) {
// Highlights are HTML-escaped, with the matched words in <mark> tags.
console.log(hit.highlights?.title ?? hit.record.title, hit.record.price)
}
console.log(results.facets["brand.name"]) // { Acme: 1 }For search as you type, cancel the previous request when the query changes:
const input = document.querySelector<HTMLInputElement>("#search")!
let pending: AbortController | undefined
input.addEventListener("input", async () => {
pending?.abort()
pending = new AbortController()
try {
const { hits } = await client.search<Product>(
{ q: input.value, limit: 8, highlight: true },
{ signal: pending.signal }
)
renderHits(hits)
} catch (error) {
if ((error as Error).name !== "AbortError") throw error
}
})With a script tag
No build step needed. This page is a working search box: the script defines a global Findlane, and results update as you type.
<input id="search" type="search" placeholder="Search products" autocomplete="off" />
<ul id="results"></ul>
<script src="https://cdn.findlane.dev/browser/0/findlane.min.js"></script>
<script>
const client = new Findlane.SearchBrowserClient({ publicId: "pub_your_public_id" })
const input = document.getElementById("search")
const results = document.getElementById("results")
let pending
input.addEventListener("input", async () => {
pending?.abort()
pending = new AbortController()
try {
const { hits } = await client.search(
{ q: input.value, limit: 8, highlight: true },
{ signal: pending.signal }
)
results.replaceChildren(
...hits.map((hit) => {
const item = document.createElement("li")
// Highlights are HTML-escaped, with matched words in <mark> tags; other text goes in as text.
if (hit.highlights?.title) item.innerHTML = hit.highlights.title
else item.textContent = hit.record.title
return item
})
)
} catch (error) {
if (error.name !== "AbortError") throw error
}
})
</script>browser/0/ always serves the latest 0.x release. To pin a version and check its integrity, see Script tag.
Next steps
- Set the index up for what shoppers will type, and keep those searches working: Set up search.
- Keep the index in step with your store: Records.
- Build filters, facets, sorting, and paging: Searching.
- Decide what browsers can read before you launch: Security.