analytics-events
Skill8 starsChanged 27 days agoMITView on GitHub
Add product analytics events to track user interactions in the Metabase frontend
2 other projects use a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- metabase/metabaseedited, first seen Oct 8, 2026
- charanjit-singh/lovable-skillstheir own version, first seen May 22, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 1
- metabase/metabase★ 50k50% same
Wrote their own version · 1
- charanjit-singh/lovable-skills★ 10same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository99 other files it gives its agents.
- skills/react-hook-form-patterns/AGENTS.mdAGENTS.md
- skills/zod-schema-validation/AGENTS.mdAGENTS.md
- Add Admin API EndpointSkill
- add-malli-schemasSkill
- add-uint-supportSkill
- analyze-ciSkill
- asyncio-patternsSkill
- axum-web-patternsSkill
- bash-defensive-patternsSkill
- bats-shell-testingSkill
- bazel-build-optimizationSkill
- beautifulsoup-parsingSkill
- changelog-automationSkill
- cheerio-parsingSkill
- clap-cli-patternsSkill
- clojure-writeSkill
- cobra-cli-developmentSkill
- creating-financial-modelsSkill
- dbt-transformation-patternsSkill
- distributed-tracingSkill
- django-patternsSkill
- drizzle-orm-patternsSkill
- fastapi-patternsSkill
- fastify-patternsSkill
- fixSkill
- github-actions-templatesSkill
- gitlab-ci-patternsSkill
- gitops-workflowSkill
- go-cache-hotSkill
- go-concurrency-patternsSkill
- go-errors-oopsSkill
- go-fx-diSkill
- go-monads-moSkill
- go-swagger-codegenSkill
- go-testify-patternsSkill
- grafana-dashboardsSkill
- graphql-schema-designSkill
- grpc-service-definitionSkill
- hono-patternsSkill
- implementing-agent-modesSkill
- implementing-jsc-classes-zigSkill
- istio-traffic-managementSkill
- jest-advanced-patternsSkill
- junit5-testingSkill
- k8s-manifest-generatorSkill
- kysely-query-builderSkill
- langsmith-fetchSkill
- linkerd-patternsSkill
- llm-evaluationSkill
- mcp-builderSkill
- mypy-type-checkingSkill
- networkx-graph-algorithmsSkill
- nx-workspace-patternsSkill
- pandas-dataframe-patternsSkill
- pillow-image-processingSkill
- polars-dataframeSkill
- prisma-schema-patternsSkill
- prometheus-configurationSkill
- prompt-engineering-patternsSkill
- pydicom-dicom-parsingSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 6 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
When adding a new analytics event:
- Very specific: line 35
Use SimpleEventSchema for straightforward tracking.
- Distinctive: line 52
Consider adding new event schema only in very special cases.
- Very specific: line 62
Add event type definitions to frontend/src/metabase-types/analytics/event.ts:
- Distinctive: line 111
Import and call the tracking function at the interaction point:
- Very specific: line 451
Check browser console: - When SNOWPLOW_ENABLED=true in dev, events are logged
- Distinctive: line 500
Group related events: - Create feature-specific event union types
- Distinctive: line 501
Track meaningful actions: - Not every click needs tracking
- Distinctive: line 503
Stay consistent: - Follow existing naming patterns in the codebase
- Very specific: line 504
Document context: - Use triggered_from to track where the action happened
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Frontend Analytics Events Skill
This skill helps you add product analytics (Snowplow) events to track user interactions in the Metabase frontend codebase.
Quick Reference
Analytics events in Metabase use Snowplow with typed event schemas. All events must be defined in TypeScript types before use.
Key Files:
frontend/src/metabase-types/analytics/event.ts- Event type definitionsfrontend/src/metabase-types/analytics/schema.ts- Schema registryfrontend/src/metabase/lib/analytics.ts- Core tracking functions- Feature-specific
analytics.tsfiles - Tracking function wrappers
Quick Checklist
When adding a new analytics event:
- Define event type in
frontend/src/metabase-types/analytics/event.ts - Add event to appropriate union type (e.g.,
DataStudioEvent,SimpleEvent) - Create tracking function in feature's
analytics.tsfile - Import and call tracking function at the interaction point
- Use
trackSimpleEvent()for basic events (most common)
Event Schema Types
1. Simple Events (Most Common)
Use SimpleEventSchema for straightforward tracking. It supports these standard fields:
type SimpleEventSchema = {
event: string; // Required: Event name (snake_case)
target_id?: number | null; // Optional: ID of affected entity
triggered_from?: string | null; // Optional: UI location/context
duration_ms?: number | null; // Optional: Duration in milliseconds
result?: string | null; // Optional: Outcome (e.g., "success", "failure")
event_detail?: string | null; // Optional: Additional detail/variant
};When to use: 90% of events fit this schema. Use for clicks, opens, closes, creates, deletes, etc.
2. Custom Schemas (legacy, no events are being added)
Consider adding new event schema only in very special cases.
Examples: DashboardEventSchema, CleanupEventSchema, QuestionEventSchema
Step-by-Step: Adding a Simple Event
Example: Track when a user applies filters in a table picker
Step 1: Define Event Types
Add event type definitions to frontend/src/metabase-types/analytics/event.ts:
export type DataStudioTablePickerFiltersAppliedEvent = ValidateEvent<{
event: "data_studio_table_picker_filters_applied";
}>;
export type DataStudioTablePickerFiltersClearedEvent = ValidateEvent<{
event: "data_studio_table_picker_filters_cleared";
}>;Step 2: Add to Union Type
Find or create the appropriate union type and add your events:
export type DataStudioEvent =
| DataStudioLibraryCreatedEvent
| DataStudioTablePublishedEvent
| DataStudioGlossaryCreatedEvent
| DataStudioGlossaryEditedEvent
| DataStudioGlossaryDeletedEvent
| DataStudioTablePickerFiltersAppliedEvent // <- Add here
| DataStudioTablePickerFiltersClearedEvent; // <- Add hereStep 3: Create Tracking Functions
In your feature's analytics.ts file (e.g., enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts):
import { trackSimpleEvent } from "metabase/lib/analytics";
export const trackDataStudioTablePickerFiltersApplied = () => {
trackSimpleEvent({
event: "data_studio_table_picker_filters_applied",
});
};
export const trackDataStudioTablePickerFiltersCleared = () => {
trackSimpleEvent({
event: "data_studio_table_picker_filters_cleared",
});
};Step 4: Use in Components
Import and call the tracking function at the interaction point:
import {
trackDataStudioTablePickerFiltersApplied,
trackDataStudioTablePickerFiltersCleared,
} from "metabase-enterprise/data-studio/analytics";
function FilterPopover({ filters, onSubmit }) {
const handleReset = () => {
trackDataStudioTablePickerFiltersCleared(); // <- Track here
onSubmit(emptyFilters);
};
return (
<form
onSubmit={(event) => {
event.preventDefault();
trackDataStudioTablePickerFiltersApplied(); // <- Track here
onSubmit(form);
}}
>
{/* form content */}
</form>
);
}Using SimpleEventSchema Fields
Example: Event with target_id
// Type definition
export type DataStudioLibraryCreatedEvent = ValidateEvent<{
event: "data_studio_library_created";
target_id: number | null;
}>;
// Tracking function
export const trackDataStudioLibraryCreated = (id: CollectionId) => {
trackSimpleEvent({
event: "data_studio_library_created",
target_id: Number(id),
});
};
// Usage
trackDataStudioLibraryCreated(newLibrary.id);Example: Event with triggered_from
// Type definition
export type NewButtonClickedEvent = ValidateEvent<{
event: "new_button_clicked";
triggered_from: "app-bar" | "empty-collection";
}>;
// Tracking function
export const trackNewButtonClicked = (location: "app-bar" | "empty-collection") => {
trackSimpleEvent({
event: "new_button_clicked",
triggered_from: location,
});
};
// Usage
<Button onClick={() => {
trackNewButtonClicked("app-bar");
handleCreate();
}}>
New
</Button>Example: Event with event_detail
// Type definition
export type MetadataEditEvent = ValidateEvent<{
event: "metadata_edited";
event_detail: "type_casting" | "semantic_type_change" | "visibility_change";
triggered_from: "admin" | "data_studio";
}>;
// Tracking function
export const trackMetadataChange = (
detail: "type_casting" | "semantic_type_change" | "visibility_change",
location: "admin" | "data_studio"
) => {
trackSimpleEvent({
event: "metadata_edited",
event_detail: detail,
triggered_from: location,
});
};
// Usage
trackMetadataChange("semantic_type_change", "data_studio");Example: Event with result and duration
// Type definition
export type MoveToTrashEvent = ValidateEvent<{
event: "moved-to-trash";
target_id: number | null;
triggered_from: "collection" | "detail_page" | "cleanup_modal";
duration_ms: number | null;
result: "success" | "failure";
event_detail: "question" | "model" | "metric" | "dashboard";
}>;
// Tracking function
export const trackMoveToTrash = (params: {
targetId: number | null;
triggeredFrom: "collection" | "detail_page" | "cleanup_modal";
durationMs: number | null;
result: "success" | "failure";
itemType: "question" | "model" | "metric" | "dashboard";
}) => {
trackSimpleEvent({
event: "moved-to-trash",
target_id: params.targetId,
triggered_from: params.triggeredFrom,
duration_ms: params.durationMs,
result: params.result,
event_detail: params.itemType,
});
};
// Usage with timing
const startTime = Date.now();
try {
await moveToTrash(item);
trackMoveToTrash({
targetId: item.id,
triggeredFrom: "collection",
durationMs: Date.now() - startTime,
result: "success",
itemType: "question",
});
} catch (error) {
trackMoveToTrash({
targetId: item.id,
triggeredFrom: "collection",
durationMs: Date.now() - startTime,
result: "failure",
itemType: "question",
});
}Naming Conventions
Event Names (snake_case)
// Good
"data_studio_library_created"
"table_picker_filters_applied"
"metabot_chat_opened"
// Bad
"DataStudioLibraryCreated" // Wrong case
"tablePickerFiltersApplied" // Wrong case
"filters-applied" // Use underscore, not hyphenEvent Type Names (PascalCase with "Event" suffix)
// Good
DataStudioLibraryCreatedEvent
TablePickerFiltersAppliedEvent
MetabotChatOpenedEvent
// Bad
dataStudioLibraryCreated // Wrong case
DataStudioLibraryCreated // Missing "Event" suffixTracking Function Names (camelCase with "track" prefix)
// Good
trackDataStudioLibraryCreated
trackTablePickerFiltersApplied
trackMetabotChatOpened
// Bad
DataStudioLibraryCreated // Missing "track" prefix
track_library_created // Wrong case
logLibraryCreated // Use "track" prefixCommon Patterns
Pattern 1: Feature-Specific Union Types
Group related events together:
export type DataStudioEvent =
| DataStudioLibraryCreatedEvent
| DataStudioTablePublishedEvent
| DataStudioGlossaryCreatedEvent;
export type MetabotEvent =
| MetabotChatOpenedEvent
| MetabotRequestSentEvent
| MetabotFixQueryClickedEvent;
// Then add to SimpleEvent union
export type SimpleEvent =
| /* other events */
| DataStudioEvent
| MetabotEvent
| /* more events */;Pattern 2: Conditional Tracking
Track different events based on user action:
const handleSave = async () => {
if (isNewItem) {
await createItem(data);
trackItemCreated(newItem.id);
} else {
await updateItem(id, data);
trackItemUpdated(id);
}
};Common Pitfalls
Don't: Add custom fields to SimpleEvent
// WRONG - SimpleEvent doesn't support custom fields
export const trackFiltersApplied = (filters: FilterState) => {
trackSimpleEvent({
event: "filters_applied",
data_layer: filters.dataLayer, // ❌ Not in SimpleEventSchema
data_source: filters.dataSource, // ❌ Not in SimpleEventSchema
with_owner: filters.hasOwner, // ❌ Not in SimpleEventSchema
});
};
// RIGHT - Use only standard SimpleEventSchema fields
export const trackFiltersApplied = () => {
trackSimpleEvent({
event: "filters_applied",
});
};
// Or use event_detail for a single variant
export const trackFilterApplied = (filterType: string) => {
trackSimpleEvent({
event: "filter_applied",
event_detail: filterType, // ✓ "data_layer", "data_source", etc.
});
};Don't: Forget to add event to union type
// Define the event
export type NewFeatureClickedEvent = ValidateEvent<{
event: "new_feature_clicked";
}>;
// ❌ WRONG - Forgot to add to SimpleEvent union
// Event won't be recognized by TypeScript
// ✓ RIGHT - Add to appropriate union
export type SimpleEvent =
| /* other events */
| NewFeatureClickedEvent;Don't: Mix up event name formats
// WRONG
event: "dataStudioLibraryCreated" // camelCase
event: "data-studio-library-created" // kebab-case
event: "Data_Studio_Library_Created" // Mixed case
// RIGHT
event: "data_studio_library_created" // snake_caseDon't: Track PII or sensitive data
// WRONG - Don't track user emails, names, or sensitive data
trackSimpleEvent({
event: "user_logged_in",
event_detail: user.email, // ❌ PII
});
// RIGHT - Track non-sensitive identifiers only
trackSimpleEvent({
event: "user_logged_in",
target_id: user.id, // ✓ Just the ID
});Don't: Forget to track both success and failure
// WRONG - Only tracking success
try {
await saveData();
trackDataSaved();
} catch (error) {
// ❌ No tracking for failure case
}
// RIGHT - Track both outcomes
try {
await saveData();
trackDataSaved({ result: "success" });
} catch (error) {
trackDataSaved({ result: "failure" });
}Testing Analytics Events
While developing, you can verify events are firing:
- Check browser console - When
SNOWPLOW_ENABLED=truein dev, events are logged - Use shouldLogAnalytics - Set in
metabase/envto see all analytics in console - Check Snowplow debugger - Browser extension for Snowplow events
Example console output:
[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_appliedFile Organization
Where to put tracking functions:
Feature-specific analytics functions:
frontend/src/metabase/{feature}/analytics.ts
enterprise/frontend/src/metabase-enterprise/{feature}/analytics.ts
Event type definitions (all in one place):
frontend/src/metabase-types/analytics/event.ts
Core tracking utilities:
frontend/src/metabase/lib/analytics.tsReal-World Examples
See these files for reference:
- Simple events:
enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts - Events with variants:
frontend/src/metabase/dashboard/analytics.ts - Complex events:
frontend/src/metabase/query_builder/analytics.js - Event type examples:
frontend/src/metabase-types/analytics/event.ts
Workflow Summary
- Identify the user interaction to track
- Decide on event name (snake_case, descriptive)
- Define event type in
event.tsusingValidateEvent - Add to union type (create feature union if needed)
- Create tracking function in feature's
analytics.ts - Import and call at the interaction point
- Test that events fire correctly
Tips
- Be specific -
filters_appliedis better thanaction_performed - Use past tense -
library_creatednotcreate_library - Group related events - Create feature-specific event union types
- Track meaningful actions - Not every click needs tracking
- Consider the data - What would you want to analyze later?
- Stay consistent - Follow existing naming patterns in the codebase
- Document context - Use
triggered_fromto track where the action happened
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

