> ## Documentation Index
> Fetch the complete documentation index at: https://moengage.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracking events

> Track custom user events and their attributes in your iOS app using the MoEngage SDK.

Event tracking is used to track user behavior in an app. And later based on the same tracked behavior you can target those users for sending relevant notifications. Make sure to track all the events relevant to your business, so that your product managers and marketers can segment your app users and create targeted campaigns. For eg. You can track what a user is purchasing, whether has a user added an item to the cart etc.

<Info>
  * We track certain events by default in our SDK, so please make sure to use the default events instead of tracking a new event for the same scenarios. Find the list of default events tracked by SDK [here](/user-guide/getting-started/integration/default-ios-sdk#default-events).
  * SDK adheres to the MoEngage FUP policies. For more information, refer to the [Fair Usage Policy](/user-guide/data/key-concepts/fair-usage-policy-fup).
</Info>

# How to track events?

Every event has 2 parts to it, the "**name**" of the event and the **properties/attributes** of the event. You have to make use of [*MoEngageProperties*](https://moengage.github.io/ios-api-reference/Classes/MoEngageProperties.html) to track events and their attributes.

For eg. The following code tracks a `Successful Purchase` event. We are including attributes like the Product Name, a Brand Name that describes the event we are tracking.

<CodeGroup>
  ```swift Swift wrap theme={null}
  var eventAttrDict : Dictionary<String,Any> = Dictionary()
  eventAttrDict["ProductName"] = "iPhone XS Max"
  eventAttrDict["BrandName"] = "Apple"
  eventAttrDict["Items In Stock"] = 109

  let eventProperties = MoEngageProperties(withAttributes: eventAttrDict)

  eventProperties.addAttribute(87000.00, withName: "price")
  eventProperties.addAttribute("Rupees", withName: "currency")
  eventProperties.addAttribute(true, withName: "in_stock")
  eventProperties.addDateEpochAttribute(1439322197, withName: "Time added to cart")
  eventProperties.addDateISOStringAttribute("2020-02-22T12:37:56Z", withName: "Time of checkout")
  eventProperties.addDateAttribute(Date(), withName: "Time of purchase")

  eventProperties.addLocationAttribute(MoEngageGeoLocation.init(withLatitude: 12.23, andLongitude: 9.23), withName: "Pickup Location")

  /// JSON is supported from MoEngage-iOS-SDK v9.17.5
  eventProperties.addAttribute(["merchantId": "abcdef","business_model": [ "admin_email": "abc@email.com","admin_comment": "payment" ]],withName: "merchant")
  <br>
  eventProperties.addAttribute([["merchantId": "abcdef", "business_model": ["admin_email": "abc@email.com", "admin_comment": "first"]], ["merchantId": "ghijk", "business_model": ["admin_email": "def@email.com", "admin_comment": "second"]]], withName: "retries")
        
  MoEngageSDKAnalytics.sharedInstance.trackEvent("Successful Purchase", withProperties: eventProperties)
  ```

  ```objective-c Objective C wrap theme={null}
  // track event example
  NSMutableDictionary* eventAttrDict = [NSMutableDictionary dictionary];
  eventAttrDict[@"ProductName"] = @"iPhone XS Max";
  eventAttrDict[@"BrandName"] = @"Apple";
  eventAttrDict[@"Items In Stock"] = @109;

  MoEngageProperties* eventProperties = [[MoEngageProperties alloc] initWithAttributes:eventAttrDict];
  [eventProperties addAttribute:@(87000.00) withName:@"price"];
  [eventProperties addAttribute:@"Rupees" withName:@"currency"];
  [eventProperties addAttribute:[NSNumber numberWithBool:true] withName:@"in_stock"];

  [eventProperties addDateEpochAttribute:1439322197 withName:@"Time added to cart"];
  [eventProperties addDateISOStringAttribute:@"2020-02-22T12:37:56Z" withName:@"Time of checkout"];
  [eventProperties addDateAttribute:[NSDate date] withName:@"Time of purchase"];

  MoEngageGeoLocation* pickupLocation = [[MoEngageGeoLocation alloc] initWithLatitude:12.23 andLongitude:9.23];
  [eventProperties addLocationAttribute:pickupLocation withName:@"Pickup Location"];
        
  /// JSON is supported from MoEngage-iOS-SDK v9.17.5
  [eventProperties addAttribute:@{@"merchantId": @"abcdef",@"business_model": @{@"admin_email": @"abc@email.com",@"admin_comment": @"payment"}} withName:@"merchant"];
  <br>
  [eventProperties addAttribute:@[@{@"merchantId":@"abcdef",@"business_model":@{@"admin_email":@"abc@email.com",@"admin_comment":@"first"}},@{@"merchantId":@"ghijk",@"business_model":@{@"admin_email":@"def@email.com",@"admin_comment":@"second"}}] withName:@"retries"];

  [[MoEngageSDKAnalytics sharedInstance] trackEvent:@"Successful Purchase" withProperties:eventProperties];
  ```
</CodeGroup>

<Info>
  **Non-Interactive Events**

  Events that should not affect the session duration calculation in anyways in MoEngage Analytics should be marked as a Non-Interactive events. Refer to [this](https://www.moengage.com/docs/developer-guide/ios-sdk/data-tracking/advanced/session-and-source-tracking) for more info on the same.
</Info>

If you don’t have any attributes, just pass **nil** as the second argument. For eg.

<CodeGroup>
  ```swift Swift wrap theme={null}
  MoEngageSDKAnalytics.sharedInstance.trackEvent("Event Name", withProperties: nil)
  ```

  ```objective-c Objective C wrap theme={null}
  [[MoEngageSDKAnalytics sharedInstance] trackEvent:@"Event Name" withProperties:nil];
  ```
</CodeGroup>

## Validations and restrictions

Event attributes have two layers of validation that apply across both `DEBUG` and release builds:

* **Naming and format rules** — these always apply, regardless of build configuration. Refer to [Naming and format rules](#naming-and-format-rules) below.
* **Type validation** — invalid attribute values (unsupported types such as `null`, custom models, `NaN`, `Infinity`, empty collections, or `Date`/`MoEngageGeoLocation` nested inside an Array or Dictionary) cause a fatal exception in `DEBUG` builds and are silently dropped from the payload in release builds. The rest of the event is tracked. Refer to [Supported attribute value types](#supported-attribute-value-types) below.

<Info>
  In the iOS SDK, `DEBUG` mode means the SDK was initialized using `initializeDefaultTestInstance(_:)` (TEST workspace) and the app is running attached to Xcode. This is different from a release build that has debug symbols enabled.
</Info>

### Naming and format rules

* **Attribute names must be non-empty.** Passing an empty string (`""`) as an attribute name causes a fatal exception in `DEBUG` builds. In release builds, the attribute is dropped.
* **Reserved prefixes.** You cannot use `moe_` as a prefix when naming events, event attributes, or user attributes. It is a system prefix, and using it might result in periodic blocklisting without prior communication.

### Supported attribute value types

Attribute values must be one of: `String`, `Number`, `Date`, `MoEngageGeoLocation`, `Dictionary` (with string keys and supported values), or `Array` (of supported values). If an unsupported value is passed:

* In `DEBUG` builds, the SDK throws a fatal exception and crashes the app to surface data issues early in development.
* In Release and TestFlight builds, the SDK silently drops the specific invalid attribute and logs the issue. The rest of the event payload is still tracked.

Common triggers for the `DEBUG` exception:

* Passing `null` (`NSNull()`).
* Passing custom models or UI elements (for example, `UIColor`, `UIImage`).
* Passing invalid numbers like `NaN` or `Infinity`.
* Passing empty Arrays or Dictionaries.
* Nesting `Date` or `MoEngageGeoLocation` objects inside an Array or Dictionary. These must be passed directly as values.

<Info>
  If you are upgrading an existing app and these strict `DEBUG` validations cause disruptive crashes while you refactor your tracking code, you can temporarily opt out by calling `disableIntegrationValidator()` during SDK initialization. Use this strictly as a stopgap for phased upgrades, and aim to remove the opt-out once your attribute call sites are properly validated.
</Info>

### Filtering unsupported attribute values

If you are upgrading to a newer version of the iOS SDK and your app passes attribute values whose types are not validated at the call site, add a type-check guard before building the `MoEngageProperties` object. This prevents `DEBUG` crashes and ensures only valid data reaches the SDK in all builds.

<CodeGroup>
  ```swift Swift wrap theme={null}
  // Helper to check if a value is a supported event attribute type.
    // Call this before passing any dynamically-typed value to addAttribute(_:withName:).
    func isSupportedEventAttributeValue(_ value: Any) -> Bool {
        switch value {
        case let d as Double: return !d.isNaN && !d.isInfinite
        case let f as Float:  return !f.isNaN && !f.isInfinite
        case is String, is Bool, is Int, is Int8, is Int16, is Int32, is Int64,
             is UInt, is UInt8, is UInt16, is UInt32, is UInt64, is NSNumber:
            return true
        case let arr as [Any]:        return !arr.isEmpty
        case let dict as [String: Any]: return !dict.isEmpty
        default: return false
        }
    }

    // Usage — value comes from a server response, external model, or unknown source
    let properties = MoEngageProperties()
    if isSupportedEventAttributeValue(dynamicValue) {
        properties.addAttribute(dynamicValue, withName: "attribute_name")
    }

  ```

  ```objective-c Objective C wrap theme={null}
  // Check if a value is a supported event attribute type before passing to the SDK.
    - (BOOL)isSupportedEventAttributeValue:(id)value {
        if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]]) {
            // Guard against NaN / Infinity for floating-point NSNumbers
            if ([value isKindOfClass:[NSNumber class]]) {
                double d = [(NSNumber *)value doubleValue];
                if (isnan(d) || isinf(d)) return NO;
            }
            return YES;
        }
        if ([value isKindOfClass:[NSArray class]])      return [(NSArray *)value count] > 0;
        if ([value isKindOfClass:[NSDictionary class]]) return [(NSDictionary *)value count] > 0;
        return NO;
    }

    // Usage
    MoEngageProperties *properties = [[MoEngageProperties alloc] init];
    if ([self isSupportedEventAttributeValue:dynamicValue]) {
        [properties addAttribute:dynamicValue withName:@"attribute_name"];
    }

  ```
</CodeGroup>

<Note>
  `Date` and `MoEngageGeoLocation` values must be passed using the dedicated `addDateAttribute`, `addDateEpochAttribute`, `addDateISOStringAttribute`, and `addLocationAttribute` methods — not through the dictionary-based `withAttributes:` initializer or `addAttribute:withName:`.
</Note>

# Manual Sync

For syncing the tracked events instantaneously, use the [*flush()*](https://moengage.github.io/ios-api-reference/Classes/MoEngageSDKAnalytics.html#/c:@M@MoEngageAnalytics@objc\(cs\)MoEngageSDKAnalytics\(im\)flush) method as shown below:

<CodeGroup>
  ```swift Swift wrap theme={null}
  MoEngageSDKAnalytics.sharedInstance.flush()
  ```

  ```objective-c Objective C wrap theme={null}
  [[MoEngageSDKAnalytics sharedInstance] flush];
  ```
</CodeGroup>

# Testing events after integration

Login to the MoEngage account with the credentials provided for your app.

Look at the top left and Switch to the **Test** environment. Ensure that your testing is done on the test environment to keep the test data separate from the Live data. Ensure you have [Initialized the SDK](https://www.moengage.com/docs/developer-guide/ios-sdk/sdk-integration/basic/sdk-initialization).

After adding event tracking in the app, as shown above, you can visit Dashboard > Recent Events to check whether the events are being tracked.

*Events can take up to 20 minutes to show up in the dashboard*

While testing it is recommended to enable [logs in Debug Mode](https://www.moengage.com/docs/developer-guide/ios-sdk/troubleshooting-and-faqs/troubleshooting-and-faqs-ios).

SDK prints a list of all events which are synced in the current flush, so you can always refer to the logs to check if the events tracked by you are being sent to the backend or not. Also, logs provide info about if the sync with the backend was successful or not. (In case it is unsuccessful, SDK saves all the tracked events and attempts to sync again in the next flush attempt).
