Skip to content
SearchJet
Updated October 1, 2026

Building Search Queries

SearchJet doesn't have a query-builder library. Instead, you build a search query from URL parameters on GET /api/v1/search, and you write filters in Meilisearch filter syntax. This page shows how to combine those parameters into precise queries, and how to wrap them in a small helper so your own code reads like a builder.

The parameters

Parameter What it does Example
q Search text (required) q=api integration
filters Meilisearch filter expression, at most 1,000 characters filters=type = "post" AND price < 50
sort Up to 5 comma-separated attr:asc or attr:desc pairs sort=date:desc
limit Results per request, 1 to 100 (default 20) limit=10
page / offset Which slice of results to return. offset wins if you send both. page=2
attributes Fields to return, within your plan's field list attributes=id,title,url
attributesToHighlight Highlight matches in _formatted (Pro and above) attributesToHighlight=title,excerpt
semantic, semanticRatio Hybrid semantic search (Pro and above) semantic=true&semanticRatio=0.7

Send each request to https://app.searchjetengine.com/api/v1/search with Authorization: Bearer and Accept: application/json. See the REST API Reference for the full list.

What you can filter and sort on

  • Free: filter on id, title, type, price, currency and sku. Sort on a single field: id, title or price.
  • Pro, Growth and Enterprise: filter and sort on any attribute you've marked filterable or sortable in Index Settings – General Attributes.

Anything else returns 400 INVALID_FILTER or 400 INVALID_SORT. For example:

{
  "error": "Filter references an attribute that is not filterable. Add it to filterableAttributes in your index settings.",
  "code": "INVALID_FILTER"
}

Filter syntax

Put string values in double quotes. Parentheses and quotes must be balanced, or the API returns INVALID_FILTER.

# Equality
type = "post"

# Numeric comparison
price < 50
price >= 10 AND price <= 100

# Either value
type = "post" OR type = "page"

# Grouping (paid plans: any filterable attributes)
type = "post" AND (tags = "wordpress" OR tags = "search")

# Exclude
NOT type = "archive"

Combining filters, sorting and limits

This query searches posts for "api integration", sorts them newest first, and returns 10 results. It sorts on date, which needs a paid plan with date marked sortable.

curl -G "https://app.searchjetengine.com/api/v1/search" \
  -H "Authorization: Bearer $SEARCHJET_PUBLIC_KEY" \
  -H "Accept: application/json" \
  --data-urlencode "q=api integration" \
  --data-urlencode 'filters=type = "post"' \
  --data-urlencode "sort=date:desc" \
  --data-urlencode "limit=10"
const params = new URLSearchParams({
  q: 'api integration',
  filters: 'type = "post"',
  sort: 'date:desc',
  limit: '10',
});
const res = await fetch(`https://app.searchjetengine.com/api/v1/search?${params}`, {
  headers: { Authorization: 'Bearer YOUR_PUBLIC_KEY', Accept: 'application/json' },
});
const { results, total } = await res.json();
use Illuminate\Support\Facades\Http;

$hits = Http::withToken(config('services.searchjet.public_key'))
    ->acceptJson()
    ->get('https://app.searchjetengine.com/api/v1/search', [
        'q' => 'api integration',
        'filters' => 'type = "post"',
        'sort' => 'date:desc',
        'limit' => 10,
    ])
    ->throw()
    ->json('results');

Writing your own small query helper

If you build queries in many places, a few lines of your own code keep things tidy. You own this code, so change it however you need.

function buildSearchParams({ q, where = {}, sort = [], limit = 10, page = 1 }) {
  const quote = (v) => (typeof v === 'number' ? v : `"${String(v).replace(/"/g, '\\"')}"`);

  const clauses = Object.entries(where).map(([attr, value]) =>
    Array.isArray(value)
      ? `(${value.map((v) => `${attr} = ${quote(v)}`).join(' OR ')})`
      : `${attr} = ${quote(value)}`
  );

  const params = new URLSearchParams({ q, limit: String(limit), page: String(page) });
  if (clauses.length) params.set('filters', clauses.join(' AND '));
  if (sort.length) params.set('sort', sort.join(','));
  return params;
}

// type is "post" OR "page"
const params = buildSearchParams({ q: 'search plugin', where: { type: ['post', 'page'] } });
function searchjetFilters(array $where): string
{
    $quote = fn ($v) => is_numeric($v) ? $v : '"' . str_replace('"', '\"', $v) . '"';

    return collect($where)->map(function ($value, $attr) use ($quote) {
        if (is_array($value)) {
            return '(' . collect($value)->map(fn ($v) => "$attr = " . $quote($v))->implode(' OR ') . ')';
        }
        return "$attr = " . $quote($value);
    })->implode(' AND ');
}

$hits = Http::withToken(config('services.searchjet.public_key'))
    ->acceptJson()
    ->get('https://app.searchjetengine.com/api/v1/search', [
        'q' => 'search plugin',
        'filters' => searchjetFilters(['type' => ['post', 'page']]),
    ])
    ->throw()
    ->json('results');

Highlighting matches

On Pro and above, ask the API to highlight matches. Each hit then gets a _formatted object:

attributesToHighlight=title,excerpt&highlightPreTag=__sj__&highlightPostTag=__/sj__

Read hit._formatted.title instead of hit.title. The rest of the text in _formatted isn't HTML-escaped, so it's safest to use plain-text marker tags like the ones above. Escape the value, then replace the markers with and .

On Free, highlighting parameters are ignored and hits contain no _formatted. Highlight on the client instead: escape the text first, then wrap the matches:

function highlight(text, query) {
  const escaped = String(text ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
  const terms = query.split(/\s+/).filter(Boolean).map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
  if (!terms.length) return escaped;
  return escaped.replace(new RegExp(`(${terms.join('|')})`, 'gi'), '$1');
}

Was this article helpful?

Ready to add search to your project?

Start free. No credit card required. Deploy in under 10 minutes.