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_key

The key is shown once. Keep it on your server: it can change your catalog.

3. Upload products

npm install @findlane/server
import { 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/browser
import { 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
  }
})

Next steps

  • 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.