
Rudder Instrumentation Planning
- 1 installs
- 18 repo stars
- Updated July 17, 2026
- rudderlabs/rudder-agent-skills
Designs event taxonomies and instrumentation strategies for RudderStack from business requirements, routing to design-first or code-first workflows.
About
Guides the overall planning of what events and properties to track by moving from discovery of business questions to taxonomy, build, and integration. A developer or PM uses it when designing a tracking strategy from scratch or restructuring existing instrumentation.
- Discovery to taxonomy to build to assemble to integrate process
- Routes to design-first vs code-first specialized skills
Rudder Instrumentation Planning by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,803 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Jul 18, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rudderlabs/rudder-agent-skills --skill rudder-instrumentation-planningAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 18 |
| Last updated | July 17, 2026 |
| Repository | rudderlabs/rudder-agent-skills ↗ |
What it does
Designs event taxonomies and instrumentation strategies for RudderStack from business requirements, routing to design-first or code-first workflows.
Files
Instrumentation Planning
This skill guides you through designing an instrumentation strategy - the systematic approach to deciding what events and properties to track in your application.
Why Planning Matters
Poor instrumentation leads to:
- Data gaps - Can't answer business questions
- Data bloat - Too many events, high costs, noise
- Inconsistency - Same action tracked differently across teams
- Technical debt - Constant schema changes breaking dashboards
Good instrumentation provides:
- Complete funnel visibility - Every step from acquisition to retention
- Consistent naming - Clear conventions everyone follows
- Maintainable schema - Easy to extend, hard to break
- Actionable insights - Data that drives decisions
Choose Your Workflow
Different starting points require different approaches:
| Your Situation | Recommended Skill |
|---|---|
| Building new feature, events not yet defined | rudder-design-first-instrumentation |
| Existing product needs instrumentation | rudder-code-first-instrumentation |
| Restructuring existing tracking | rudder-code-first-instrumentation |
| General planning guidance | Continue with this skill |
Design-First vs Code-First
Design-First: Start from product requirements → define events → define properties → implement code. Best for new features where events are part of product definition.
Code-First: Start from existing code types → derive tracking plan → align with data governance. Best for existing products with domain types already defined.
This skill covers the general planning process. For workflow-specific guidance, see the specialized skills above.
The Planning Process
┌─────────────────────────────────────────────────────────────────────┐
│ INSTRUMENTATION PLANNING │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ 1. DISCOVERY │ ← What questions do we need to answer?
└────────┬────────┘
▼
┌─────────────────┐
│ 2. TAXONOMY │ ← What events and properties will answer them?
└────────┬────────┘
▼
┌─────────────────┐
│ 3. BUILD │ ← Create the YAML definitions
└────────┬────────┘
▼
┌─────────────────┐
│ 4. ASSEMBLE │ ← Group into tracking plans
└────────┬────────┘
▼
┌─────────────────┐
│ 5. INTEGRATE │ ← Generate code, implement in apps
└─────────────────┘Phase 1: Discovery
Questions to Ask Stakeholders
Business Questions:
- What KPIs do we track? (conversion rate, retention, revenue)
- What funnels do we analyze? (signup, checkout, onboarding)
- What experiments will we run? (A/B tests need specific events)
- What attribution do we need? (marketing channels, campaigns)
Product Questions:
- What are the key user journeys?
- What features do we want to measure adoption for?
- What errors/failures do we need to monitor?
Technical Questions:
- What platforms exist? (web, iOS, Android, server)
- What existing tracking is in place?
- What tools consume this data? (Amplitude, Mixpanel, warehouse)
Discovery Template
## Business Goals
- [ ] Primary KPIs: _______________
- [ ] Key funnels: _______________
- [ ] Attribution needs: _______________
## User Journeys to Track
1. _______________
2. _______________
3. _______________
## Platforms
- [ ] Web
- [ ] iOS
- [ ] Android
- [ ] Server
## Existing Tracking
- Current events: ___ events
- Issues with current: _______________Phase 2: Taxonomy Design
Step 1: Define Event Categories
Group events by business domain:
| Category | Purpose | Examples |
|---|---|---|
user-lifecycle | Account actions | Signed Up, Logged In, Profile Updated |
ecommerce | Purchase funnel | Product Viewed, Added to Cart, Order Completed |
engagement | Feature usage | Feature Used, Content Viewed, Search Performed |
errors | Failure tracking | Error Occurred, Checkout Failed |
Step 2: Map User Journeys to Events
Example: E-Commerce Funnel
User Journey Events
─────────── ──────
Browse products → Product Viewed
Add to cart → Product Added to Cart
Start checkout → Checkout Started
Complete purchase → Order CompletedExample: SaaS Onboarding
User Journey Events
─────────── ──────
Create account → Signed Up
Verify email → Email Verified
Complete profile → Profile Completed
Use first feature → Feature Used (first_time: true)
Invite teammate → Team Member InvitedStep 3: Identify Properties
For each event, list required context:
Product Viewed
- Required: product_id, product_name, product_price, product_category
- Optional: page_url, referrer_url, session_id
- Context: How did they find it? What were they looking at?
Order Completed
- Required: order_id, order_total, products, customer_email
- Optional: discount_code, shipping_method, payment_method
- Context: What did they buy? How much? What discounts?
Step 4: Identify Shared Patterns
Look for properties used across multiple events:
Shared across all events:
- session_id
- user_id (if logged in)
- timestamp (automatic)
Shared across e-commerce events:
- product object (id, name, price, category)
Shared across Order Completed:
- address object (street, city, state, zip)These become Custom Types.
Phase 3: Build the Data Catalog
Order of Creation
1. Custom Types ← Reusable validation patterns
2. Properties ← The vocabulary
3. Categories ← Organization
4. Events ← The actions (reference properties)Real-World Example: E-Commerce Store
Custom Types:
# 1. ProductType - used by multiple events
version: "rudder/v1"
kind: "custom-type"
metadata:
name: "custom-types"
spec:
name: "ProductType"
type: "object"
description: "Consolidated product information"
config:
properties:
- property: "urn:rudder:property/product_id"
required: true
- property: "urn:rudder:property/product_name"
required: true
- property: "urn:rudder:property/product_price"
required: true
- property: "urn:rudder:property/product_category"
required: true
---
# 2. AddressType - used for shipping and billing
version: "rudder/v1"
kind: "custom-type"
metadata:
name: "custom-types"
spec:
name: "AddressType"
type: "object"
description: "US mailing address"
config:
properties:
- property: "urn:rudder:property/street"
required: true
- property: "urn:rudder:property/city"
required: true
- property: "urn:rudder:property/state"
required: true
- property: "urn:rudder:property/zipcode"
required: trueProperties:
# Product properties
version: "rudder/v1"
kind: "property"
metadata:
name: "properties"
spec:
name: "product_id"
type: "string"
description: "Unique product identifier"
config:
minLength: 1
maxLength: 128
---
version: "rudder/v1"
kind: "property"
metadata:
name: "properties"
spec:
name: "product_category"
type: "string"
description: "Product category"
config:
enum:
- "Footwear"
- "Clothing"
- "Accessories"
- "Electronics"
---
# Address properties with validation
version: "rudder/v1"
kind: "property"
metadata:
name: "properties"
spec:
name: "zipcode"
type: "string"
description: "US ZIP code"
config:
pattern: "^[0-9]{5}(-[0-9]{4})?$"Events:
# The e-commerce funnel
version: "rudder/v1"
kind: "event"
metadata:
name: "events"
spec:
name: "Product Viewed"
description: "User viewed a product detail page"
category: "urn:rudder:category/ecommerce"
rules:
- property: "urn:rudder:property/product"
required: true
customType: "urn:rudder:custom-type/product-type"
- property: "urn:rudder:property/page_url"
- property: "urn:rudder:property/referrer_url"
---
version: "rudder/v1"
kind: "event"
metadata:
name: "events"
spec:
name: "Product Added to Cart"
description: "User added a product to their cart"
category: "urn:rudder:category/ecommerce"
rules:
- property: "urn:rudder:property/product"
required: true
customType: "urn:rudder:custom-type/product-type"
- property: "urn:rudder:property/quantity"
required: true
- property: "urn:rudder:property/cart_total"
---
version: "rudder/v1"
kind: "event"
metadata:
name: "events"
spec:
name: "Order Completed"
description: "Customer completed a purchase"
category: "urn:rudder:category/ecommerce"
rules:
- property: "urn:rudder:property/order_id"
required: true
- property: "urn:rudder:property/order_total"
required: true
- property: "urn:rudder:property/customer_email"
required: true
- property: "urn:rudder:property/shipping_address"
required: true
customType: "urn:rudder:custom-type/address-type"
- property: "urn:rudder:property/billing_address"
required: true
customType: "urn:rudder:custom-type/address-type"
- property: "urn:rudder:property/products"
required: trueNaming Conventions
Events
| Pattern | Example | When to Use |
|---|---|---|
| Object Action | Product Viewed | Standard user actions |
| Past Tense | Order Completed | Completed actions |
| Title Case | Product Added to Cart | Always |
Good:
Product ViewedOrder CompletedFeature Used
Bad:
productView(camelCase)PRODUCT_VIEWED(screaming snake)Click Product(wrong verb)
Properties
| Pattern | Example | When to Use |
|---|---|---|
| snake_case | product_id | Always |
| Descriptive | customer_email | Include context |
| Specific | shipping_address | Not just "address" |
Good:
product_idorder_totalcustomer_email
Bad:
productId(camelCase)id(too generic)total(ambiguous)
Categories
| Pattern | Example |
|---|---|
| kebab-case | ecommerce |
| Lowercase | user-lifecycle |
Common Event Patterns
See references/event-patterns.md for standard event taxonomy patterns (e-commerce funnel, user lifecycle, feature engagement, error tracking) and anti-patterns to avoid.
Phase 4: Assemble Tracking Plans
Group events by source/application:
# Web App - full funnel
spec:
name: "Web App Tracking Plan"
events:
- event: "urn:rudder:event/product-viewed"
- event: "urn:rudder:event/product-added-to-cart"
- event: "urn:rudder:event/checkout-started"
- event: "urn:rudder:event/order-completed"
# Mobile App - simplified
spec:
name: "Mobile App Tracking Plan"
events:
- event: "urn:rudder:event/product-viewed"
- event: "urn:rudder:event/order-completed"Phase 5: Integrate
Validate and Apply
# Validate all definitions
rudder-cli validate -l ./
# Preview changes
rudder-cli apply --dry-run -l ./
# Apply to workspace
rudder-cli apply -l ./Generate Type-Safe Code
# Initialize RudderTyper
rudder-cli typer init
# Generate SDK
rudder-cli typer generateImplement in Applications
Use generated code for type-safe tracking:
// Type-safe, IDE autocomplete, compile-time validation
analytics.productViewed(
product = ProductType(
productId = "shoes-001",
productName = "Running Shoes",
productPrice = 89.99,
productCategory = ProductCategory.FOOTWEAR
)
)Credential Security
When planning instrumentation that involves authentication or sensitive data:
- Never track passwords or tokens - exclude sensitive fields from event properties
- Hash or anonymize PII - user emails, phone numbers should be hashed if tracked
- Use RudderStack's PII masking - configure masking rules for sensitive properties
- Store workspace tokens securely - use environment variables, never commit to git
- Add `.env` to `.gitignore` - protect local development credentials
Checklist
Before finalizing your instrumentation plan:
- [ ] All business questions can be answered with planned events
- [ ] Naming conventions are documented and consistent
- [ ] Custom types created for repeated property groups
- [ ] Required vs optional clearly defined for each property
- [ ] Categories organize events logically
- [ ] Tracking plans exist for each source/platform
- [ ] Validation passes:
rudder-cli validate -l ./
References
references/event-patterns.md- Standard event taxonomy patterns and anti-patternsreferences/session-lifecycle-patterns.md- When to use identify, group, and track calls
Common Event Patterns
Reference patterns for event taxonomy design.
E-Commerce Funnel
Product Viewed → Product Added to Cart → Checkout Started → Order Completedevents:
- Product Viewed # Discovery
- Product Added to Cart # Intent
- Product Removed from Cart
- Checkout Started # Commitment
- Payment Info Entered
- Order Completed # Conversion
- Order Refunded # Post-purchaseUser Lifecycle
Signed Up → Email Verified → Profile Completed → Subscribed → Churnedevents:
- Signed Up # Acquisition
- Email Verified
- Logged In
- Profile Updated
- Password Changed
- Subscription Started # Monetization
- Subscription Cancelled
- Account Deleted # ChurnFeature Engagement
events:
- Feature Used # Generic with feature_name property
- Search Performed
- Filter Applied
- Export Requested
- Share ClickedError Tracking
events:
- Error Occurred # Generic with error_code, error_message
- Checkout Failed
- Payment Declined
- Validation FailedAnti-Patterns to Avoid
Too Granular
Bad:
- Button Clicked
- Link Clicked
- Image Clicked
- Input Focused
- Input BlurredGood:
- Feature Used # With feature_name property
- CTA Clicked # With cta_name, cta_locationInconsistent Naming
Bad:
- productView # camelCase
- Product Viewed # Title Case
- product_viewed # snake_case
- PRODUCT_VIEWED # SCREAMINGGood:
- Product Viewed # Consistent Title Case
- Order Completed
- Feature UsedMissing Context
Bad:
# Can't determine source or session
- Product Viewed
properties:
- product_idGood:
# Can attribute and analyze
- Product Viewed
properties:
- product_id
- page_url
- referrer_url
- session_id
- user_idProperty Explosion
Bad:
# 14 separate properties
- shipping_street
- shipping_city
- shipping_state
- shipping_zip
- billing_street
- billing_city
- billing_state
- billing_zipGood:
# 2 custom types
- AddressType (street, city, state, zip)
- shipping_address: AddressType
- billing_address: AddressTypeSession Lifecycle Patterns
Guidance on identify, group, and track calls for multi-tenant SaaS applications like RudderStack.
The Three-Layer Model
For applications with User → Organization → Workspace hierarchy:
┌─────────────────────────────────────────────────────────────────────┐
│ CALL SEMANTICS │
└─────────────────────────────────────────────────────────────────────┘
IDENTIFY → User traits (permanent, context-independent)
│
GROUP → Organization membership (called per org, not per workspace)
│
TRACK → Events with workspace context (operational state)| Call | Purpose | Contains | When to Call |
|---|---|---|---|
| Identify | Establish user identity | User traits only | Login, signup, profile update |
| Group | Associate with organization | Org traits (plan, is_trial) | Org join, org access, org trait changes |
| Track | Record actions | Event + workspace context | Every meaningful action |
---
Identify Call
Purpose: Establish who the user is with permanent attributes.
When to call:
- Signup (first identification)
- Login (re-establish for session)
- Profile update (email changed, name changed)
Shape
interface IdentifyTraits {
email: string;
name: string;
created_at: string; // ISO 8601
phone?: string;
avatar_url?: string;
// Attributes intrinsic to the USER
}What Does NOT Belong
workspace_id— operational context, not user traitorg_id— use group call insteadplan,is_trial— organization attributes
Example
analytics.identify(userId, {
email: user.email,
name: user.name,
created_at: user.createdAt,
});---
Group Call
Purpose: Associate user with an organization and describe that organization.
Key insight: Group establishes membership, not current operating context. A user can be a member of multiple orgs. Calling group() doesn't "switch" context — it records an association.
When to call:
- User joins an organization
- User first accesses an organization in a session
- Organization attributes change (plan upgrade, trial ends)
Shape
interface GroupTraits {
name: string; // Org name
plan: string; // Org's plan (free, starter, growth, enterprise)
is_trial: boolean; // Org's trial status
industry?: string;
employee_count?: number;
created_at: string; // When org was created
// Attributes of the ORGANIZATION
}Example
analytics.group(orgId, {
name: org.name,
plan: org.plan,
is_trial: org.isTrial,
created_at: org.createdAt,
});---
Track Call with Workspace Context
Purpose: Record an action with its properties and operational context.
Key insight: Workspace is operational context, not organizational membership. It belongs in track calls, not group calls.
Shape
interface TrackCall {
event: string;
properties: {
// Event-specific data
[key: string]: any;
};
context: {
workspace_id: string;
workspace_name?: string;
};
}Example
analytics.track('Transformation Created', {
transformation_id: 'tr_123',
language: 'javascript',
}, {
context: {
workspace_id: currentWorkspace.id,
workspace_name: currentWorkspace.name,
}
});Middleware Pattern (Recommended)
Auto-include workspace context on all track calls:
analytics.addSourceMiddleware(({ payload, next }) => {
if (payload.type() === 'track') {
payload.obj.context = {
...payload.obj.context,
workspace: {
id: getCurrentWorkspaceId(),
name: getCurrentWorkspaceName(),
},
};
}
next(payload);
});---
What to Call When
| Event | What to Call |
|---|---|
| User signs up | identify(userId, traits) |
| User logs in | identify(userId, traits) |
| User updates profile | identify(userId, updatedTraits) |
| User accesses org (first time or switch) | group(orgId, orgTraits) |
| Org plan changes | group(orgId, { plan: newPlan }) |
| User switches workspace (same org) | Update track context only — no identify or group |
| User switches to different org | group(newOrgId, newOrgTraits) + update track context |
---
Anti-Patterns
❌ Org/Workspace Attributes on Identify
// BAD - these don't belong on the user
analytics.identify(userId, {
workspace_id: "ws_123", // Operational context
is_trial: true, // Org attribute
plan: "enterprise", // Org attribute
});
// GOOD - separate concerns
analytics.identify(userId, { email, name, created_at });
analytics.group(orgId, { plan, is_trial });
// workspace_id flows through track context❌ Group Call on Workspace Switch
// BAD - group is for org membership, not workspace context
function onWorkspaceSwitch(newWorkspaceId) {
analytics.group(newWorkspaceId, { name: workspace.name });
}
// GOOD - update track context only
function onWorkspaceSwitch(newWorkspaceId) {
setCurrentWorkspaceContext(newWorkspaceId);
// Subsequent track calls will include new workspace_id
}❌ Calling Identify on Context Change
// BAD - identify is for user traits, not context
function onWorkspaceSwitch(newWorkspaceId) {
analytics.identify(userId, { workspace_id: newWorkspaceId });
}
// GOOD - context flows through track
function onWorkspaceSwitch(newWorkspaceId) {
analyticsContext.setWorkspace(newWorkspaceId);
}---
Real-World Example: Multi-Tenant Project Management App
Hierarchy
A typical B2B SaaS with User → Organization → Project structure:
User (jane@example.com)
├── Org: Acme Corp (group call when accessed)
│ ├── Project: Marketing Campaign (track context)
│ └── Project: Product Launch (track context)
└── Org: Freelance Clients (group call when accessed)
└── Project: Website Redesign (track context)Implementation
class AnalyticsService {
private currentProjectId: string | null = null;
private currentOrgId: string | null = null;
// Called on login
identifyUser(user: User) {
analytics.identify(user.id, {
email: user.email,
name: user.name,
created_at: user.createdAt,
});
}
// Called when user accesses an org
setOrganization(org: Organization) {
if (org.id !== this.currentOrgId) {
this.currentOrgId = org.id;
analytics.group(org.id, {
name: org.name,
plan: org.plan, // 'free' | 'team' | 'business' | 'enterprise'
is_trial: org.isTrial,
industry: org.industry,
});
}
}
// Called when user switches project (no group call needed if same org)
setProject(projectId: string) {
this.currentProjectId = projectId;
// No analytics call - context flows through track
}
// All track calls include project context
track(event: string, properties: Record<string, any>) {
analytics.track(event, {
...properties,
project_id: this.currentProjectId,
});
}
}Usage
const analytics = new AnalyticsService();
// User logs in
analytics.identifyUser(user);
// User accesses Acme Corp org
analytics.setOrganization(acmeCorpOrg);
// User is in Marketing Campaign project
analytics.setProject('proj_marketing_123');
// User creates a task
analytics.track('Task Created', {
task_id: 'task_456',
task_name: 'Design landing page',
priority: 'high',
});
// Event includes project_id: 'proj_marketing_123'
// User switches to Product Launch project (same org)
analytics.setProject('proj_launch_789');
// No group call needed!
// User creates another task
analytics.track('Task Created', {
task_id: 'task_790',
task_name: 'Prepare demo',
priority: 'medium',
});
// Event includes project_id: 'proj_launch_789'
// User switches to Freelance Clients org (different org!)
analytics.setOrganization(freelanceOrg); // Group call fires
analytics.setProject('proj_website_001');
// Now events include freelance org context and new project
analytics.track('Task Created', {
task_id: 'task_801',
task_name: 'Review mockups',
priority: 'high',
});---
Querying Multi-Project Data
With this pattern, you can answer questions like:
-- Users in Enterprise orgs (from group traits)
SELECT DISTINCT user_id
FROM groups
WHERE plan = 'enterprise';
-- Tasks created per project (from track context)
SELECT
context_project_id,
COUNT(*) as task_count
FROM tracks
WHERE event = 'Task Created'
GROUP BY context_project_id;
-- Users active in multiple projects
SELECT
user_id,
COUNT(DISTINCT context_project_id) as project_count
FROM tracks
GROUP BY user_id
HAVING project_count > 1;
-- Cross-org activity (users working across multiple organizations)
SELECT
u.user_id,
u.email,
COUNT(DISTINCT g.group_id) as org_count
FROM users u
JOIN groups g ON u.user_id = g.user_id
GROUP BY u.user_id, u.email
HAVING org_count > 1;---
Summary
| Data | Where It Belongs | Rationale |
|---|---|---|
user_id, email, name | Identify | User attributes |
org_id, plan, is_trial | Group | Organization attributes |
project_id (or workspace_id) | Track context | Operational context at time of event |
Internal Examples
For RudderStack-specific examples of this pattern (Workspaces, Transformations), see rudder-code-first-instrumentation/references/internal-rudderstack-examples.md.