Middleware for iOS
End-of-Support for Analytics-iOS in March 2026
End-of-support (EoS) for the Analytics-iOS SDK is scheduled for March 2026. Segment's future development efforts concentrate on the new Analytics-Swift SDK. If you'd like to migrate to Analytics-Swift, see the migration guide.
Middlewares are simple functions invoked by the Segment libraries, which give you a way to add information to the events you collect using the Segment SDKs. They can be used to monitor, modify, or reject events. Source Middlewares are available on analytics-ios 3.6.0 and later.
You can access the middleware API in both Objective-C and Swift.
Info
Note: Destination Middlewares are not available for iOS.
Middleware is any Objective-C class that conforms to the following protocol.
1@protocol SEGMiddleware2@required3- (void)context:(SEGContext *_Nonnull)context next:(S4EGMiddlewareNext _Nonnull)next;5@end
Segment also provides a block-centric helper class to make it easier to create middlewares using anonymous functions on the fly. (See examples below)
1typedef void (^SEGMiddlewareBlock)(SEGContext *_Nonnull context, SEGMiddlewareNext _Nonnull next);23@interface SEGBlockMiddleware : NSObject <SEGMiddleware>4@property (nonnull, nonatomic, readonly) SEGMiddlewareBlock block;56- (instancetype _Nonnull)initWithBlock:(SEGMiddlewareBlock _Nonnull)block;78@end
The context object encapsulates everything about an event in the stream. You invoke the next callback function when the current middleware is done processing the event, and can pass the processed event down to the next middleware in the chain.
The SEGContext object is not very information rich by itself. Typically you must use eventType and payload to get more information about an event.
1@interface SEGContext : NSObject <NSCopying>23@property (nonatomic, readonly, nonnull) SEGAnalytics *_analytics;4@property (nonatomic, readonly) SEGEventType eventType;56@property (nonatomic, readonly, nullable) NSString *userId;7@property (nonatomic, readonly, nullable) NSString *anonymousId;8@property (nonatomic, readonly, nullable) NSError *error;9@property (nonatomic, readonly, nullable) SEGPayload *payload;10@property (nonatomic, readonly) BOOL debug;1112- (instancetype _Nonnull)initWithAnalytics:(SEGAnalytics *_Nonnull)analytics;1314- (SEGContext *_Nonnull)modify:(void (^_Nonnull)(id<SEGMutableContext> _Nonnull ctx))modify;1516@end
Look at the SEGEventType carefully, and notice that middleware can handle track , identify and other Segment analytics APIs. Even calls like reset , flush and openURL go through and can be processed by the middleware pipeline.
1typedef NS_ENUM(NSInteger, SEGEventType) {2// Should not happen, but default state3SEGEventTypeUndefined,4// Core Tracking Methods5SEGEventTypeIdentify,6SEGEventTypeTrack,7SEGEventTypeScreen,8SEGEventTypeGroup,9SEGEventTypeAlias,1011// General utility12SEGEventTypeReset,13SEGEventTypeFlush,1415// Remote Notification16SEGEventTypeReceivedRemoteNotification,17SEGEventTypeFailedToRegisterForRemoteNotifications,18SEGEventTypeRegisteredForRemoteNotifications,19SEGEventTypeHandleActionWithForRemoteNotification,2021// Application Lifecycle22SEGEventTypeApplicationLifecycle,2324// Misc.25SEGEventTypeContinueUserActivity,26SEGEventTypeOpenURL,27};
There are almost as many SEGPayload subclasses as there are SEGEventType enums. Subclassed payloads may contain call specific information, For example, the SEGTrackPayload contains event as well as properties .
1@interface SEGTrackPayload : SEGPayload23@property (nonatomic, readonly) NSString *event;45@property (nonatomic, readonly, nullable) NSDictionary *properties;67@end
Finally, to use a middleware, you must provide it to the SEGAnalyticsConfiguration object prior to the initialization of SEGAnalytics.
1@interface SEGAnalyticsConfiguration : NSObject23/**4* Set custom source middleware. Will be run before all integrations5*/6@property (nonatomic, strong, nullable) NSArray<id<SEGMiddleware>> *sourceMiddleware;78// ...9@end
Once initialized, the list of middleware used in SEGAnalytics cannot be changed.
The following examples are written in Swift to show that the middleware API works just as well in Swift as in Objective-C.
The following example shows how to initialize middleware.
1let mixpanelIntegration = SEGMixpanelIntegrationFactory.instance()2let amplitudeIntegration = SEGAmplitudeIntegrationFactory.instance()3let config = AnalyticsConfiguration(writeKey: "YOUR_WRITEKEY_HERE")45config.trackApplicationLifecycleEvents = true6config.trackDeepLinks = true7config.recordScreenViews = true89config.use(mixpanelIntegration)10config.use(amplitudeIntegration)1112config.sourceMiddleware = [13turnScreenIntoTrack,14enforceEventTaxonomy,15customizeAllTrackCalls,16dropSpecificEvents,17blockScreenCallsToAmplitude,18]1920Analytics.setup(with: config)
The following examples show how to changing event names, and add custom attributes.
1let customizeAllTrackCalls = BlockMiddleware { (context, next) in2if context.eventType == .track {3next(context.modify { ctx in4guard let track = ctx.payload as? TrackPayload else {5return6}7let newEvent = "[New] \(track.event)"8var newProps = track.properties ?? [:]9newProps["customAttribute"] = "Hello"10ctx.payload = TrackPayload(11event: newEvent,12properties: newProps,13context: track.context,14integrations: track.integrations15)16})17} else {18next(context)19}20}
The following example turns one kind call into another. NOTE: This is only applicable to Source Middleware.
1let turnScreenIntoTrack = BlockMiddleware { (context, next) in2if context.eventType == .screen {3next(context.modify { ctx in4guard let screen = ctx.payload as? ScreenPayload else {5return6}7let event = "\(screen.name) Screen Tracked"8ctx.payload = TrackPayload(9event: event,10properties: screen.properties,11context: screen.context,12integrations: screen.integrations13)14ctx.eventType = .track15})16} else {17next(context)18}19}
The following example completely blocks specific events from a list.
1let dropSpecificEvents = BlockMiddleware { (context, next) in2let validEvents = [3"Application Opened",4"Order Completed",5"Home Screen Tracked",6"AnalyticsIOSTestApp. Screen Tracked",7]8if let track = context.payload as? TrackPayload {9if !validEvents.contains(track.event) {10print("Dropping Rogue Event '\(track.event)'")11// not calling next results in an event being discarded12return13}14}15next(context)16}
The following example blocks only screen calls from reaching the Amplitude destination.
1let blockScreenCallsToAmplitude = BlockMiddleware { (context, next) in2if let screen = context.payload as? ScreenPayload {3next(context.modify { ctx in4ctx.payload = ScreenPayload(5name: screen.name,6properties: screen.properties,7context: screen.context,8integrations: ["Amplitude": false]9)10})11return12}13next(context)14}
If you use the Braze (Appboy) destination in either cloud or device mode you can save Braze costs by "debouncing" duplicate Identify calls from Segment by adding the open-source Middleware tool to your implementation. More information about this tool and how it works is available in the project's README.