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.
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
}
})