Customer Segments
Segments allow you to group customers based on specific criteria and track those groups over time. If you know Laravel Eloquent, you can build powerful segments using familiar syntax.
What are Segments?
A segment is a group of your StickleEntity models matching specific criteria. Segments help you answer questions like:
- Who are your "active" users?
- Who are your "inactive" or "slipping away" users?
- Which customers are "high value"?
- Who are your "primary contacts"?
- Which accounts are "at risk of churning"?
You define segments "in-code" by creating a class that extends the Segment contract. Stickle then automatically:
- Tracks membership - Maintains which models are currently in the segment
- Records history - Tracks when models enter and leave segments
- Calculates statistics - Aggregates tracked attributes at the segment level
For example, if you have a "High Value Customers" segment and track mrr as an attribute, Stickle automatically calculates the total MRR of all customers in that segment over time.
Creating Segment Classes
Create a segment by extending the Segment contract. Segments should be placed in app/Segments/ (or the namespace configured in config/stickle.php).
Basic Segment Example
<?php
namespace App\Segments;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use StickleApp\Core\Attributes\StickleSegmentMetadata;
use StickleApp\Core\Contracts\Segment;
#[StickleSegmentMetadata([
'name' => 'Active Users',
'description' => 'Users who have logged in within the last 7 days',
'exportInterval' => 360, // Re-calculate every 6 hours
])]
class ActiveUsers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->where('created_at', '>=', now()->subDays(7));
}
}Required Elements:
$model- The model class this segment applies to (must useStickleEntitytrait)toBuilder()- Method that returns an Eloquent Builder defining the segment criteria
Segment Metadata
The StickleSegmentMetadata attribute provides additional context:
name- Human-readable name (defaults to class name if omitted)description- Longer description of the segment's purposeexportInterval- How often to recalculate (in minutes). Overrides the global default fromconfig('stickle.schedule.exportSegments')
Using Filters in Segments
Stickle provides powerful filters for building segments based on user behavior and attributes:
Filter by Recent Activity
use StickleApp\Core\Filters\Filter;
class ActiveUsers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->stickleWhere(
Filter::eventCount('page_view')
->count()
->betweenDates(
startDate: now()->subDays(7),
endDate: now()
)
->greaterThan(0)
);
}
}Filter by Attribute Values
class HighValueCustomers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->stickleWhere(
Filter::number('lifetime_value')
->greaterThan(10000)
);
}
}Combine Multiple Filters
class AtRiskCustomers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
// High value customers
->stickleWhere(
Filter::number('mrr')
->greaterThan(500)
)
// But low recent activity
->stickleWhere(
Filter::sessionCount()
->count()
->betweenDates(
startDate: now()->subDays(30),
endDate: now()
)
->lessThan(2)
);
}
}Mix Eloquent and Stickle Queries
class PremiumActiveUsers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
// Standard Eloquent query
->where('subscription_plan', 'premium')
->whereNotNull('email_verified_at')
// Stickle filter
->stickleWhere(
Filter::eventCount('feature_used')
->count()
->betweenDates(
startDate: now()->subDays(14),
endDate: now()
)
->greaterThan(10)
);
}
}Common Segment Examples
Active Users
Users who have logged in recently:
class ActiveUsers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->where('last_login_at', '>=', now()->subDays(7));
}
}High-Value Customers
Customers above a revenue threshold:
class HighValueCustomers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->stickleWhere(
Filter::number('lifetime_value')
->greaterThan(5000)
);
}
}At-Risk Customers
Customers showing signs of churn:
class AtRiskCustomers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
// Haven't logged in for 14 days
->where('last_login_at', '<', now()->subDays(14))
// But are paying customers
->where('subscription_status', 'active')
// With declining usage
->stickleWhere(
Filter::eventCount('page_view')
->count()
->decreased()
->betweenDateRanges(
compareToDateRange: [now()->subDays(60), now()->subDays(30)],
currentDateRange: [now()->subDays(30), now()]
)
);
}
}Trial Users by Stage
Users in trial period, segmented by engagement:
class EngagedTrialUsers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->where('subscription_status', 'trial')
->where('trial_ends_at', '>', now())
->stickleWhere(
Filter::eventCount('key_feature_used')
->count()
->betweenDates(
startDate: now()->subDays(7),
endDate: now()
)
->greaterThan(3)
);
}
}Power Users
Highly engaged customers:
class PowerUsers extends Segment
{
public string $model = User::class;
public function toBuilder(): Builder
{
return $this->model::query()
->stickleWhere(
Filter::sessionCount()
->count()
->betweenDates(
startDate: now()->subDays(30),
endDate: now()
)
->greaterThan(20)
)
->stickleWhere(
Filter::eventCount('feature_used')
->count()
->betweenDates(
startDate: now()->subDays(30),
endDate: now()
)
->greaterThan(100)
);
}
}Ideal Customer Profile (ICP)
Customers matching your ideal profile:
class IdealCustomerProfile extends Segment
{
public string $model = Company::class;
public function toBuilder(): Builder
{
return $this->model::query()
// Company size
->where('employee_count', '>=', 50)
->where('employee_count', '<=', 500)
// Industry
->whereIn('industry', ['technology', 'saas', 'software'])
// Usage patterns
->stickleWhere(
Filter::number('monthly_active_users')
->greaterThan(10)
)
->stickleWhere(
Filter::number('mrr')
->greaterThan(1000)
);
}
}How Stickle Tracks Segments
Once you create a segment, Stickle automatically tracks:
Current Membership
Which models are currently in the segment:
// Get all users in the Active Users segment
$activeUsers = User::stickleWhere(
Filter::segment('ActiveUsers')->isInSegment()
)->get();Membership History
When models enter and leave segments:
// Check if user has ever been in this segment
$everActive = User::stickleWhere(
Filter::segment('ActiveUsers')->hasBeenInSegment()
)->exists();Aggregate Statistics
Stickle calculates aggregate values of tracked attributes for each segment over time. For example, if you track mrr:
- Total MRR in segment (sum)
- Average MRR per customer (avg)
- Highest/lowest MRR (max/min)
- Number of customers (count)
This data is available in StickleUI and via API endpoints.
Recalculation Schedule
Segments are recalculated on a schedule (default: every 6 hours). Configure globally:
STICKLE_FREQUENCY_EXPORT_SEGMENTS=360 # minutesOr per-segment using metadata:
#[StickleSegmentMetadata([
'exportInterval' => 60, # Recalculate hourly
])]
class CriticalSegment extends Segment
{
// ...
}Querying by Segment
Stickle provides convenient Eloquent scopes for filtering by segment membership:
Current Membership
// Users currently in segment
User::stickleWhere(
Filter::segment('ActiveUsers')->isInSegment()
)->get();
// Users NOT in segment
User::stickleWhere(
Filter::segment('InactiveUsers')->isNotInSegment()
)->get();Historical Membership
// Users who have ever been in segment
User::stickleWhere(
Filter::segment('PowerUsers')->hasBeenInSegment()
)->get();
// Users who have never been in segment
User::stickleWhere(
Filter::segment('PowerUsers')->neverBeenInSegment()
)->get();Combining Segment Filters
// High value customers who are currently at risk
User::stickleWhere(
Filter::segment('HighValueCustomers')->isInSegment()
)->stickleWhere(
Filter::segment('AtRiskCustomers')->isInSegment()
)->get();Viewing Segments in StickleUI
After creating segments, view them in the dashboard:
- Segment List -
/stickle/{model}/segments- See all segments for a model - Segment Details -
/stickle/{model}/segments/{segmentId}- View members and statistics - Segment Charts - Historical trends of aggregate attributes
- Export - Download segment members as CSV
Manual Segment Export
Force an immediate recalculation of all segments:
php artisan stickle:export-segmentsFor a specific segment:
php artisan stickle:export-segment App\\Segments\\ActiveUsersPerformance Considerations
Segment-Based Filters vs Direct Filters
Segment-based filtering uses pre-calculated data:
// Uses pre-calculated segment (fast, but may be slightly stale)
User::stickleWhere(
Filter::segment('ActiveUsers')->isInSegment()
)->get();Direct filtering calculates in real-time:
// Calculates in real-time (slower, but always current)
User::stickleWhere(
Filter::sessionCount()
->count()
->betweenDates(now()->subDays(7), now())
->greaterThan(0)
)->get();Use segments when:
- You query the same criteria frequently
- Real-time accuracy isn't critical
- You want to track trends over time
Use direct filters when:
- You need real-time results
- The criteria is used rarely
- The criteria changes frequently
Best Practices
- Start with 5-10 key segments - Don't create too many initially
- Name segments clearly - Use descriptive names like "HighValueCustomers" not "HVC"
- Document complex logic - Add comments explaining sophisticated filters
- Set appropriate intervals - Critical segments can refresh hourly, others every 6-12 hours
- Test segment queries - Use tinker to verify your segment logic returns expected results
- Monitor performance - Complex segments with many filters may need optimization
Next Steps
Now that you understand segments:
- Filters - Master all available filter types
- Event Listeners - Respond when users enter/exit segments
- Recipes - See real-world segment examples
- API Endpoints - Query segment data programmatically