exploring-autocapture-events — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited exploring-autocapture-events (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
if users opt in then posthog-js automatically captures clicks, form submissions, and page changes as $autocapture events. Each event records the clicked DOM element and its ancestors in the elements_chain column.
$autocapture is intentionally excluded from the posthog:read-data-schema taxonomy because it is only useful with autocapture-specific filters (selector, tag, text, href). This skill fills that gap.
The events table provides fast access to common element fields without parsing the full chain string.
| Column | Type | Description |
|---|---|---|
elements_chain | String | Full semicolon-separated element chain (see format reference) |
elements_chain_href | String | Last href value from the chain |
elements_chain_texts | Array(String) | All text values from elements |
elements_chain_ids | Array(String) | All id attribute values |
elements_chain_elements | Array(String) | Useful tag names: a, button, input, select, textarea, label |
Use materialized columns for exploration queries whenever possible — they avoid regex parsing.
Every $autocapture event from posthog-js ships with a fixed set of properties. Do not query the schema to "look them up" — they are these:
| Property | Examples | Notes |
|---|---|---|
$event_type | click, submit, change | the kind of interaction |
$el_text | Sign up, Submit | text of the clicked element |
$current_url | https://app.example.com/pricing | page the interaction happened on |
$elements_chain | semicolon-separated chain | parsed via the elements_chain* materialized columns above |
Standard event properties ($browser, $os, $device_type, etc.) are also present.
Run a count query before doing anything else. If the count is zero, autocapture may be disabled. There are two ways this happens:
autocapture_opt_out in PostHog project settingsinit() call can pass autocapture: falseTell the user if no data is found so they can check both settings.
SELECT count() as cnt
FROM events
WHERE event = '$autocapture'
AND timestamp > now() - INTERVAL 7 DAYStart broad using the materialized columns. The goal is to understand what users are clicking before narrowing down.
Useful explorations:
elements_chain_elements)elements_chain_texts)elements_chain_href)elements_chain values for a specific page (filtered by properties.$current_url)See example queries for all patterns.
Once the user identifies an interaction they care about, find a CSS selector that identifies it.
Priority order for selector attributes (best first):
Search with match(elements_chain, 'data-attr=') or extractAll.
attr_id) — also highly stable, queryable via elements_chain_ids.When a data-attr value is found, construct a selector like [data-attr="value"] or button[data-attr="value"].
A selector is only useful if it matches the intended interaction and not unrelated events.
Run a uniqueness check using elements_chain =~ with the regex pattern for the selector. Then sample matching events to inspect what the selector actually captures. Compare the count against total autocapture volume to understand selectivity.
A good selector matches a single logical interaction. If it matches too many distinct elements, refine it in the next step.
If the selector alone is not unique enough, layer on additional filters:
elements_chain_textsproperties.$current_urlelements_chain_hrefRe-run the uniqueness check after each refinement. Only include filters that are needed — fewer filters means more resilience to minor DOM changes.
When the user wants a funnel, trend, or other insight, the filter shape is different from HogQL. Each step in a FunnelsQuery / TrendsQuery is an EventsNode (or ActionsNode) with event: "$autocapture" and a properties array.
Two distinct property type values matter — they are not interchangeable:
selector, tag_name, text, href. Matched against the parsed elements_chain. Operator support is split:selector and tag_name only support exact and is_not — anything else raises NotImplementedError in the query compiler (posthog/hogql/property.py).text and href accept the full string operator set (exact, is_not, icontains, not_icontains, regex, not_regex, is_set, is_not_set).$event_type, $el_text, $current_url) or anything else on the event. Standard event-property operators (exact, icontains, regex, etc.).Example funnel from clicking one button to clicking another:
{
"kind": "FunnelsQuery",
"series": [
{
"kind": "EventsNode",
"event": "$autocapture",
"properties": [
{
"type": "element",
"key": "selector",
"value": ["[data-attr=\"autocapture-series-save-as-action-banner-shown\"]"],
"operator": "exact"
}
]
},
{
"kind": "EventsNode",
"event": "$autocapture",
"properties": [
{
"type": "element",
"key": "selector",
"value": ["[data-attr=\"autocapture-save-as-action\"]"],
"operator": "exact"
}
]
}
]
}Two things easy to get wrong:
value is an array even when matching a single selector[data-attr="..."] wrapper — it is a CSS selector, not a bare attribute valueDecision rule: prefer an action (ActionsNode referencing an existing action — see Step 8) when the interaction will be referenced more than once; inline type: "element" / type: "event" filters when it's a one-off insight; raw HogQL (Step 7) when joining across events or doing custom aggregations.
The discovered selector can be used directly in HogQL without creating an action.
Trends — count matching clicks over time:
SELECT
toStartOfDay(timestamp) as day,
count() as clicks
FROM events
WHERE event = '$autocapture'
AND timestamp > now() - INTERVAL 14 DAY
AND elements_chain =~ '(^|;)button.*?data-attr="checkout"'
GROUP BY day
ORDER BY dayFunnel — pageview to click conversion:
SELECT
person_id,
first_pageview,
first_click_after
FROM (
SELECT
p.person_id,
p.pageview_time as first_pageview,
min(c.click_time) as first_click_after
FROM (
SELECT person_id, min(timestamp) as pageview_time
FROM events
WHERE event = '$pageview'
AND timestamp > now() - INTERVAL 14 DAY
AND properties.$current_url ILIKE '%/pricing%'
GROUP BY person_id
) p
INNER JOIN (
SELECT person_id, timestamp as click_time
FROM events
WHERE event = '$autocapture'
AND timestamp > now() - INTERVAL 14 DAY
AND elements_chain =~ '(^|;)button.*?data-attr="signup"'
) c ON p.person_id = c.person_id AND c.click_time > p.pageview_time
GROUP BY p.person_id, p.pageview_time
)For recurring analysis, prefer creating an action (next step) or using posthog:query-trends / posthog:query-funnel with the action.
Actions are the durable version of ad-hoc selector queries. Once the criteria uniquely identify the interaction, create an action using posthog:action-create.
Construct the step with only the filters needed for uniqueness:
{
"name": "Clicked checkout button",
"steps": [
{
"event": "$autocapture",
"selector": "button[data-attr='checkout']",
"text": "Complete Purchase",
"text_matching": "exact",
"url": "/checkout",
"url_matching": "contains"
}
]
}Available step fields for $autocapture:
selector — CSS selector (e.g. button[data-attr='checkout'])tag_name — HTML tag name (e.g. button, a, input)text / text_matching — element text (exact, contains, or regex)href / href_matching — link href (exact, contains, or regex)url / url_matching — page URL (exact, contains, or regex)After creation, verify with matchesAction():
SELECT count() as matching_events
FROM events
WHERE matchesAction('Clicked checkout button')
AND timestamp > now() - INTERVAL 7 DAY$autocapture is high volumeLIMIT generously when sampling elements_chain — the strings can be longelements_chain =~ operator matches CSS selectors as regex internally;prefer materialized columns when possible for performance
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.