NAME

Typesense::Client::Analytics - popular queries, no-hits queries and click events

SYNOPSIS

my $a = $ts->analytics;

# Aggregate what people search for into its own collection.
$a->upsert_rule('popular_products', {
    type   => 'popular_queries',
    params => {
        source      => { collections => ['products'] },
        destination => { collection  => 'product_queries' },
        limit       => 1000,
        expand_query => \1,           # store the full term, not the prefix
    },
});

# And what they search for and do not find.
$a->upsert_rule('nohits_products', {
    type   => 'nohits_queries',
    params => {
        source      => { collections => ['products'] },
        destination => { collection  => 'product_nohits' },
        limit       => 1000,
    },
});

# Feed real popularity back into ranking.
$a->upsert_rule('product_popularity', {
    type   => 'counter',
    params => {
        source => { collections => ['products'],
                    events => [ { type => 'click', weight => 1,
                                  name => 'product_click' } ] },
        destination => { collection => 'products', counter_field => 'popularity' },
    },
});

## The other half of the pairing: the same id on the search itself.
$ts->search('products', { q => 'laptop', query_by => 'name' },
            headers => { 'x-typesense-user-id' => $session_id });

$a->click('product_click', { doc_id => '1024', user_id => $session_id });

my $top = $a->top_queries('product_queries', limit => 20);

DESCRIPTION

Typesense can aggregate search traffic on its own. That matters most for a type-ahead, where logging every keystroke server-side is both expensive and useless: Typesense only counts a query after a four-second pause, so what lands in the destination collection is what the person actually finished typing, not the prefixes on the way there.

The server has to be started for it

None of this works unless the server runs with:

--enable-search-analytics=true
--analytics-dir=/data/analytics
--analytics-flush-interval=300      # seconds; the default is 3600

Without those flags rules can be created and will simply never produce anything, which is a confusing way to fail. Check $ts->debug or the server log if a destination collection stays empty.

Reading the results

There is no endpoint that returns aggregated analytics. The rules write into ordinary collections, which you search like any other - that is all "top_queries" does.

Attributing events to people

Pass the same identifier as user_id on events and as the x-typesense-user-id header (or X-TYPESENSE-USER-ID parameter) on searches. Without it Typesense falls back to the client IP, and behind a reverse proxy that makes every visitor look like one very busy person.

METHODS

rules

GET /analytics/rules.

rule

GET /analytics/rules/{name}.

upsert_rule

PUT /analytics/rules/{name}. type is popular_queries, nohits_queries or counter.

delete_rule

DELETE /analytics/rules/{name}. Not an error if absent.

event

$a->event($type, $name, \%data);

POST /analytics/events. $name must match the event name declared in a counter rule for it to be counted.

click, conversion, visit

Shorthands for the three event types.

top_queries

my $r = $a->top_queries($destination_collection, limit => 20);

Searches a popular_queries or nohits_queries destination collection, ordered by count. Convenience only - it is a plain search.

SEE ALSO

Typesense::Client

https://typesense.org/docs/latest/api/analytics-query-suggestions.html

AUTHOR

SeHarrys

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by SeHarrys.

This is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.