Search & Filters Flow
Build a dynamic product search with filters, sorting, and pagination.
What you'll build
A search experience powered by the v2 filter catalog: load all available filter dimensions, perform a text search, combine multiple filters (category + type + price range), sort results by price, and paginate through the result set.
Prerequisites
- A Store UUID — e.g.
e87437f2-3e35-4738-af5e-6307e368255c(staging) - cURL or any HTTP client
No authentication is required. All endpoints in this flow use the
jwt_optional_authenticatedpipeline.
Step 1: Load the Filter Catalog (v2)
The v2 filters endpoint returns everything you need to build a filter UI: price ranges, weight options, THC/CBD ranges, on-sale count, and relationships to types, categories, and brands (sideloaded in included).
cURL
curl -X GET https://ecom-api.staging.blaze.me/api/v2/products/filters \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
JavaScript (fetch)
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const headers = {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"X-Store": STORE_UUID,
};
const filterRes = await fetch(`${BASE_URL}/api/v2/products/filters`, { headers });
const { data: filters, included } = await filterRes.json();
// Price range — use for a slider
const { min, max } = filters.attributes.price_ranges;
console.log(`Price: $${min} – $${max}`);
// Weights — use for weight-picker buttons
console.log("Weights:", filters.attributes.weights); // [1.0, 3.5, 7.0, 14.0, 28.0]
// THC/CBD ranges — use for range sliders
console.log("THC:", filters.attributes.thc_ranges); // { min: 0, max: 35, unit: "%" }
console.log("CBD:", filters.attributes.cbd_ranges); // { min: 0, max: 20, unit: "%" }
// On-sale badge
console.log(`${filters.attributes.on_sale.count} products on sale`);
// Types, categories, brands from included
const types = included.filter((r) => r.type === "product_types");
const categories = included.filter((r) => r.type === "product_categories");
const brands = included.filter((r) => r.type === "product_brands");
console.log(`${types.length} types, ${categories.length} categories, ${brands.length} brands`);
Response
{
"data": {
"id": "filters",
"type": "filters",
"attributes": {
"price_ranges": { "min": 5.0, "max": 120.0 },
"weights": [1.0, 3.5, 7.0, 14.0, 28.0],
"thc_ranges": { "min": 0, "max": 35, "unit": "%" },
"cbd_ranges": { "min": 0, "max": 20, "unit": "%" },
"on_sale": { "count": 14 }
},
"relationships": {
"types": {
"data": [
{ "id": "Hybrid", "type": "product_types" },
{ "id": "Indica", "type": "product_types" },
{ "id": "Sativa", "type": "product_types" }
]
},
"categories": {
"data": [
{ "id": "cat-flower-001", "type": "product_categories" }
]
},
"brands": {
"data": [
{ "id": "brand-001", "type": "product_brands" }
]
}
}
},
"included": [
{
"id": "Hybrid",
"type": "product_types",
"attributes": { "name": "Hybrid", "count": 25 }
},
{
"id": "Indica",
"type": "product_types",
"attributes": { "name": "Indica", "count": 18 }
},
{
"id": "Sativa",
"type": "product_types",
"attributes": { "name": "Sativa", "count": 12 }
}
]
}
v2 vs v1: The v2 filter catalog adds
weights,thc_ranges,cbd_ranges,on_sale, and sideloads related resources inincluded. Prefer v2 for building filter UIs.
Step 2: Search by Text
The user types "blue dream" in the search bar.
cURL
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?search=blue+dream&limit=10" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
JavaScript (fetch)
const searchQuery = "blue dream";
const searchParams = new URLSearchParams({
search: searchQuery,
limit: "10",
});
const searchRes = await fetch(`${BASE_URL}/api/v1/products?${searchParams}`, { headers });
const { data: results, meta } = await searchRes.json();
console.log(`"${searchQuery}" → ${meta.total} results`);
results.forEach((p) => {
console.log(` ${p.attributes.name} — $${p.attributes.unit_price}`);
});
Response
{
"data": [
{
"id": "b5c8d1e2-f3a4-5678-9012-abcdef123456",
"type": "store_products",
"attributes": {
"name": "Blue Dream",
"slug": "blue-dream",
"type": "Hybrid",
"in_stock": true,
"unit_price": 45.00
}
}
],
"meta": {
"total": 3,
"limit": 10,
"offset": 0
}
}
Step 3: Combine Multiple Filters
The user narrows results: category = flower, type = Hybrid, price $20–$60.
cURL
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&product_type=Hybrid&min_price=20&max_price=60&limit=10" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
JavaScript (fetch)
const filterParams = new URLSearchParams({
category: "flower",
product_type: "Hybrid",
min_price: "20",
max_price: "60",
limit: "10",
});
const filteredRes = await fetch(`${BASE_URL}/api/v1/products?${filterParams}`, { headers });
const { data: filtered, meta: filteredMeta } = await filteredRes.json();
console.log(`Flower + Hybrid + $20–$60 → ${filteredMeta.total} products`);
Response
{
"data": [
{
"id": "b5c8d1e2-f3a4-5678-9012-abcdef123456",
"type": "store_products",
"attributes": {
"name": "Blue Dream",
"slug": "blue-dream",
"type": "Hybrid",
"in_stock": true,
"unit_price": 45.00
}
},
{
"id": "a4d7c9e3-b2f1-4a56-8901-fedcba654321",
"type": "store_products",
"attributes": {
"name": "Wedding Cake",
"slug": "wedding-cake",
"type": "Hybrid",
"in_stock": true,
"unit_price": 55.00
}
}
],
"meta": {
"total": 12,
"limit": 10,
"offset": 0
}
}
Filter parameters:
category,subcategory,product_type,brand,tags,min_price,max_price, andsearchcan all be combined freely.
Step 4: Sort Results
Add sorting to show cheapest products first.
cURL
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&product_type=Hybrid&min_price=20&max_price=60&order=price_asc&limit=10" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
JavaScript (fetch)
const sortedParams = new URLSearchParams({
category: "flower",
product_type: "Hybrid",
min_price: "20",
max_price: "60",
order: "price_asc",
limit: "10",
});
const sortedRes = await fetch(`${BASE_URL}/api/v1/products?${sortedParams}`, { headers });
const { data: sorted } = await sortedRes.json();
sorted.forEach((p) => {
console.log(`$${p.attributes.unit_price} — ${p.attributes.name}`);
});
// $25.00 — Girl Scout Cookies
// $35.00 — Gelato
// $45.00 — Blue Dream
// ...
Sort options:
name_asc,name_desc,price_asc,price_desc. The exact set of supported values depends on the store configuration.
Step 5: Paginate Through Results
Navigate through larger result sets page by page.
cURL
# Page 1
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&order=price_asc&limit=5&offset=0" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
# Page 2
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&order=price_asc&limit=5&offset=5" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
JavaScript (fetch)
async function searchProducts({ category, type, minPrice, maxPrice, order, search, page = 1, pageSize = 10 }) {
const params = new URLSearchParams();
if (category) params.set("category", category);
if (type) params.set("product_type", type);
if (minPrice) params.set("min_price", String(minPrice));
if (maxPrice) params.set("max_price", String(maxPrice));
if (order) params.set("order", order);
if (search) params.set("search", search);
params.set("limit", String(pageSize));
params.set("offset", String((page - 1) * pageSize));
const res = await fetch(`${BASE_URL}/api/v1/products?${params}`, { headers });
const { data, meta } = await res.json();
return {
products: data,
total: meta.total,
page,
pageSize,
totalPages: Math.ceil(meta.total / pageSize),
};
}
// Usage
const page1 = await searchProducts({
category: "flower",
type: "Hybrid",
minPrice: 20,
maxPrice: 60,
order: "price_asc",
page: 1,
pageSize: 5,
});
console.log(`Page ${page1.page} of ${page1.totalPages} (${page1.total} total)`);
// Load next page
if (page1.page < page1.totalPages) {
const page2 = await searchProducts({
category: "flower",
type: "Hybrid",
minPrice: 20,
maxPrice: 60,
order: "price_asc",
page: 2,
pageSize: 5,
});
console.log(`Page ${page2.page}: ${page2.products.length} products`);
}
Pagination response
{
"meta": {
"total": 48,
"limit": 5,
"offset": 0
}
}
Pagination math:
total_pages = ceil(meta.total / limit). Current page =(offset / limit) + 1. There are no more results whenoffset + limit >= total.
Putting It All Together
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const headers = {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"X-Store": STORE_UUID,
};
// 1. Load filter catalog to build the UI
const { data: filters, included } = await (
await fetch(`${BASE_URL}/api/v2/products/filters`, { headers })
).json();
const types = included.filter((r) => r.type === "product_types");
console.log("Available types:", types.map((t) => `${t.attributes.name} (${t.attributes.count})`));
console.log(`Price: $${filters.attributes.price_ranges.min}–$${filters.attributes.price_ranges.max}`);
// 2. User searches for "gummies"
const { data: searchResults, meta: searchMeta } = await (
await fetch(`${BASE_URL}/api/v1/products?search=gummies&limit=10`, { headers })
).json();
console.log(`Search "gummies" → ${searchMeta.total} results`);
// 3. User applies filters: Edibles + $10–$30 + sorted by price
const filterParams = new URLSearchParams({
search: "gummies",
category: "edibles",
min_price: "10",
max_price: "30",
order: "price_asc",
limit: "10",
});
const { data: filtered, meta: filteredMeta } = await (
await fetch(`${BASE_URL}/api/v1/products?${filterParams}`, { headers })
).json();
console.log(`Filtered → ${filteredMeta.total} results`);
filtered.forEach((p) => {
console.log(` $${p.attributes.unit_price} — ${p.attributes.name}`);
});
What's Next?
- Catalog browsing without search: See the Browsing Flow example
- Sponsored results mixed with organic: See the Ads & Sponsored Flow example
- Add to cart: See the Cart & Checkout guide