Querying Signals
$snipform->signals() is a query builder. Chain a period and any number of clauses, then call a terminal: sessions(), metrics(), graph() or live().
use SnipForm\Query\SessionField;
$rows = $snipform->signals() ->last28Days() ->where(SessionField::COUNTRY, 'US') ->whereStartsWith(SessionField::ENTRY_PATH, '/blog') ->whereGte(SessionField::TIME_ON_SITE, 60) ->sessions();
foreach ($rows as $session) { echo "{$session->entryPath} from {$session->source}\n";}Each call to signals() starts a fresh chain. The builder posts the same JSON the Signals Analytics API accepts, so everything documented for the API applies here.
Periods
Section titled “Periods”The default period is last_7. Use the typed shorthands, or period() with a Period case or string.
->today()->yesterday()->last7Days()->last28Days()->monthToDate()->yearToDate()->last12Months()
// Explicit Y-m-d range (sets the period to custom)->between('2026-01-01', '2026-01-31')->customPeriod('2026-01-01', '2026-01-31')->customPeriod()->fromDate('2026-01-01')->toDate('2026-01-31')
// By enum or stringuse SnipForm\Query\Period;->period(Period::LAST_28)->period('last_28')Unknown period strings throw InvalidPeriodException before any request is made. The valid set is today, yesterday, last_7, last_28, month_to_date, year_to_date, last_12_months and custom.
Clauses
Section titled “Clauses”The first argument to every where*() method is a field id from the Field Catalog. Pass it as a SessionField enum case or as a plain string.
| Method | Operator | Notes |
|---|---|---|
where($field, $value) | equals | An array value means “any of” |
orWhere($field, $value) | equals | Joined with OR |
whereNot($field, $value) | equals, negated | |
orWhereNot($field, $value) | equals, negated, OR | |
whereStartsWith($field, $prefix) | starts_with | Keyword fields |
whereContains($field, $needle) | contains | Keyword fields |
whereRegex($field, $pattern) | regex | Keyword fields |
whereGt / whereGte / whereLt / whereLte($field, $n) | gt / gte / lt / lte | Numeric fields |
whereBetween($field, $min, $max) | between | Numeric fields, inclusive |
whereExists($field) | exists | Field has a value |
whereNotExists($field) | exists, negated | Field is absent |
$snipform->signals() ->last7Days() ->where('device', ['mobile', 'tablet']) // IN ->whereNot('channel', 'direct') ->whereExists('utm_campaign') ->whereBetween('views', 3, 10) ->sessions();Enum cases catch mistakes early
Section titled “Enum cases catch mistakes early”With a SessionField case the SDK knows the field’s type and rejects an impossible operator before the request leaves:
$snipform->signals()->whereBetween(SessionField::COUNTRY, 0, 10);// IncompatibleFieldOperator: Operator `between` is not valid for field// `country` (type: keyword). Valid ops: equals, contains, starts_with, regex, exists.Plain strings skip that check (the server still rejects an unknown field with a 403). Strings are the escape hatch for fields the server adds before the enum catches up.
The enum covers every id in the catalog: entry and exit page, referrer, pages, tags, geo, browser, device, OS, bot, channel and UTM attribution, source, acquisition money, short links, forms, events and the session metrics (BOUNCED, ENTRY_TS, LAST_TS, TIME_ON_SITE, AVG_MAX_SCROLL, VIEWS, SCREEN_WIDTH, SCREEN_HEIGHT, LAST_PAGE).
How clauses combine
Section titled “How clauses combine”Clauses are sent as { id, op, value, where, not } and combined inside one group: property AND period AND (clause1 OP clause2 OP clause3). An orWhere can widen the match within your clauses but never escape the property or the period. The full semantics, including nested fields like tags_key and event_name, are in Query Language.
Sessions
Section titled “Sessions”sessions(int $perPage = 50) returns a PaginatedCollection of SessionRow objects. Iterating walks every page; first(), count(), all() and page($n) are covered under Pagination.
$rows = $snipform->signals()->where('device', 'mobile')->sessions();
foreach ($rows as $session) { ... }$rows->first();$rows->count();$rows->page(2); // a Page with meta + next()/prev()SessionRow fields:
| Property | Type | From |
|---|---|---|
id | string | Session id |
entryTs, lastTs | int, ?int | Unix seconds |
country, countryCode, city | ?string | Geo |
device, os, browser | ?string | Device class, platform, browser name |
entryPath, exitPath | ?string | |
referrerDomain, source, channel | ?string | channel is the channel category |
utmSource, utmMedium, utmCampaign, utmContent, utmTerm | ?string | |
views | int | Page views |
timeOnSite | int | Seconds |
bounced | bool | |
tags | array | Session tags as {key, value} rows |
Use ->asRaw()->sessions() to receive the full session document as an array.
Metrics
Section titled “Metrics”metrics(bool $withDevices = false) returns a MetricsResult with the current-period headline numbers.
$m = $snipform->signals()->last28Days()->where('utm_content', 'pub_12345')->metrics();
$m->sessions; // int$m->views; // int$m->viewsPerSession; // float$m->bounceRate; // float, 0-100$m->duration; // int, seconds$m->avgScroll; // float, 0-100$m->showing; // human-readable span, e.g. "Jul 25 - Aug 21, 2026"$m->tookMs; // server query timeThe API also returns the previous period, the difference and the trend for every metric. The typed object keeps only the current values; switch to raw mode for the rest:
$raw = $snipform->signals()->last28Days()->asRaw()->metrics();
$raw['analytics']['period_metrics']['summary']['sessions']['previous'];$raw['analytics']['period_metrics']['summary']['sessions']['percent'];$raw['meta']['showing'];graph(string $metric = 'sessions', ?string $interval = null, bool $compare = false) returns a GraphResult: one series for the chain’s period, bucketed by interval.
$g = $snipform->signals()->last28Days()->where('country', 'US')->graph('sessions');$g = $snipform->signals()->last28Days()->graph('bounce', 'day', compare: true);
$g->metric; // 'bounce'$g->metricLabel; // 'Bounce rate'$g->series; // GraphPoint[] - one per bucket: ->label, ->value$g->previousSeries; // GraphPoint[] - the preceding period, only when compare is true$g->projectedSeries; // GraphPoint[] - projection for the still-running bucket$g->showing; // human-readable span$g->tookMs;| Metric | Meaning |
|---|---|
sessions | Sessions |
views | Page views |
views_session | Views per session |
bounce | Bounce rate |
duration | Time on site, minutes |
scroll | Average scroll depth |
Intervals are hour, day, week and month. Leave the interval null and the server picks one that fits the period.
live(string $metric = 'sessions') is the same shape for the last five minutes. The chain’s period is ignored; its clauses still apply.
$now = $snipform->signals()->where('device', 'mobile')->live(); // sessions$now = $snipform->signals()->live('views');
$now->showing; // 'Live - last 5 minutes'Returning a chain directly
Section titled “Returning a chain directly”A builder is JsonSerializable and serialises as if sessions() had been called, so return $snipform->signals()->last7Days(); from a controller yields page 1 of sessions. End the chain with an explicit terminal when you mean something else.
The wire payload
Section titled “The wire payload”For debugging, buildPayload() shows exactly what a chain will post:
$snipform->signals()->last28Days()->where('country', 'US')->buildPayload();// ['period' => 'last_28', 'clauses' => [['id' => 'country', 'op' => 'equals', 'value' => 'US']]]where and not are omitted from a clause when they hold their defaults (and, false). The endpoints are POST /v2/property/signals/sessions, /analytics/metrics, /analytics/graph and /analytics/live; see Signals Analytics.