---
title: "Custom Modules"
slug: "modules-overview-1"
updated: 2026-04-13T21:10:08Z
published: 2026-04-13T21:10:08Z
canonical: "ignite.ticketmaster.com/modules-overview-1"
---

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

# Custom Modules

Custom modules let you embed branded content — headers, interactive buttons, and maps — directly in the ticket detail view. You can use them to surface parking information, venue maps, exclusive offers, or any other experience relevant to an event.

Each module consists of:

- A unique identifier string
- An optional header view
- Zero to three action buttons

The SDK renders your modules alongside the prebuilt modules in the ticket detail view.

---

## Module delegate setup

Register your module delegate before presenting the SDK. The delegate method is called once per event, giving you the event data you need to build context-aware modules.

**iOS**

Assign your delegate to `TMTickets.shared.moduleDelegate`, then implement `TMTicketsModuleDelegate`:

```swift
extension MyClass: TMTicketsModuleDelegate {
    func addCustomModules(event: TMPurchasedEvent, completion: @escaping ([TMTicketsModule]?) -> Void) {
        let view = UIView()
        view.backgroundColor = .red
        let header = TMTicketsModule.HeaderDisplay(view: view)
        let button1 = TMTicketsModule.ActionButton(title: "One")
        let button2 = TMTicketsModule.ActionButton(title: "Two")
        let button3 = TMTicketsModule.ActionButton(title: "Three")
        let module = TMTicketsModule(
            identifier: "com.myDemoApp.demoModule",
            headerDisplay: header,
            actionButtons: [button1, button2, button3])
        completion([module])
    }
}
```

Call `completion` with your array of modules, or `completion(nil)` to show no custom modules for that event.

**Android**

Assign a `TicketsModuleDelegate` to `TicketsSDKSingleton.moduleDelegate`:

```kotlin
TicketsSDKSingleton.moduleDelegate = object : TicketsModuleDelegate {
    override fun getCustomModulesLiveData(order: Order): LiveData<List<TicketsSDKModule>> {
        val modules: ArrayList<TicketsSDKModule> = ArrayList()
        modules.add(getCustomModule(activity))
        return MutableLiveData(modules)
    }
    override fun userDidPressActionButton(
        buttonTitle: String?,
        callbackValue: String?,
        eventOrders: EventOrders?
    ) {
        // Handle button taps here
    }
}
```

The delegate returns `LiveData`, so you can post module updates asynchronously — for example, after fetching personalized data from your backend.

---

## Module identifier

Every module requires a unique reverse-URL identifier. The SDK uses this string to distinguish your modules and route button callbacks.

Use your app's bundle ID or a domain you control as the prefix:

- `com.ticketmaster.seatUpgrades`
- `com.nfl.broncosSeating`
- `com.myDemoApp.parkingModule`

---

## Header views

The header appears at the top of the module. It can show a solid color, a static image, or a map.

**iOS**

Pass a `UIView` to `TMTicketsModule.HeaderDisplay`. The SDK sizes the view using the aspect ratio you specify:

| Aspect ratio | Description |
| --- | --- |
| `.automatic` | Derives ratio from the view's intrinsic content size |
| `.square` | Forces a 1:1 ratio |
| `.custom(ratio)` | Uses the ratio you provide |

For an image header, pass a `UIImage` directly:

```swift
let image = UIImage(named: "venueMap")!
let header = TMTicketsModule.HeaderDisplay(image: image)
```

Use `TMTicketsModuleHeaderView` when you need text overlays on images or maps. Call `TMTicketsModuleHeaderView.build()` to get an instance, configure it, then wrap it in `TMTicketsModule.HeaderDisplay(view:)`

**Android**

Build your header using `ModuleBase` from the `TicketsSDKModule` helper class. Pass any `View` to `moduleBase.setHeader()`:

```kotlin
fun getCustomModule(context: Context): ModuleBase {
    val moduleBase = ModuleBase(context)
    val view = View(context)
    view.setBackgroundColor(ContextCompat.getColor(context, R.color.tickets_tm_brand_blue))
    (view.layoutParams as ConstraintLayout.LayoutParams).apply {
        height = context.dpToPx(200f).roundToInt()
    }
    moduleBase.setHeader(view)
    moduleBase.setLeftButtonText("One")
    moduleBase.setMiddleButtonText("Two")
    moduleBase.setRightButtonText("Three")
    return moduleBase
}
```

You can also inflate a custom XML layout and pass it as the header:

```kotlin
val headerView = LayoutInflater.from(context).inflate(R.layout.custom_module, null)
moduleBase.setHeader(headerView)
// Access child views via moduleBase.findViewById(R.id.myLabel)
```

![Example custom module with colored header and three buttons](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/custom_module_example_1.jpg)

---

## Action buttons

Each module supports up to three buttons. Tapping a button either triggers a callback to your app or opens a webpage inside the SDK.

**iOS**

Create an `ActionButton` with a title and an optional `callbackValue`. The SDK passes the callback value back to `handleModuleActionButton` so you can distinguish buttons without parsing button titles:

```swift
func handleModuleActionButton(
    event: TMPurchasedEvent,
    module: TMTicketsModule,
    button: TMTicketsModule.ActionButton,
    completion: @escaping (TMTicketsModule.WebpageSettings?) -> Void
) {
    guard module.identifier == "com.myDemoApp.demoModule" else { return }
    switch button.callbackValue {
    case "MyButton1":
        completion(nil) // Dismiss the Tickets SDK
    case "MyButton2":
        let urlRequest = URLRequest(url: URL(string: "https://www.example.com")!)
        let settings = TMTicketsModule.WebpageSettings(
            pageTitle: "My Webpage",
            urlRequest: urlRequest)
        completion(settings) // Open a webpage inside the SDK
    default:
        break
    }
}
```

To open a webpage directly from the button definition instead of the callback, attach `WebpageSettings` at button creation time:

```swift
let urlRequest = URLRequest(url: URL(string: "https://www.example.com")!)
let settings = TMTicketsModule.WebpageSettings(pageTitle: "Details", urlRequest: urlRequest)
let button = TMTicketsModule.ActionButton(title: "Learn More", webpageSettings: settings)
```

**Android**

Set per-button click listeners on `ModuleBase`, or handle all taps centrally in `userDidPressActionButton`:

```kotlin
// Custom listener on a specific button
moduleBase.setLeftClickListener {
    val url = "https://www.example.com"
    context.startActivity(TmxWebUriHelper.openWebUriWebView(context, url))
}
// Or handle via the delegate callback
override fun userDidPressActionButton(
    buttonTitle: String?,
    callbackValue: String?,
    eventOrders: EventOrders?
) {
    when (callbackValue) {
        "LeftClick" -> { /* handle left button */ }
        "MiddleButton" -> { /* handle middle button */ }
        "RightClick" -> { /* handle right button */ }
    }
}
```

![Example module output showing three-button layout](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/Module_DefaultAspect.jpg)

---

## Map modules

You can surface venue and parking maps as module headers. The SDK provides two approaches: a prebuilt venue directions module and fully custom map modules you build yourself.

### Prebuilt venue directions module

The SDK includes an optional prebuilt Venue Directions module. On iOS it renders an Apple Maps thumbnail; on Android it shows a "Get Directions" button (thumbnail rendering is omitted on Android because Google charges per map tile request). Tapping either opens the native maps app with the venue pre-populated.

Enable the Venue Directions module through your module delegate if you want the default behavior without any additional setup.

![Prebuilt venue directions module](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/DefaultMapModule.jpg) ![Apple Maps thumbnail on iOS](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/AppleMaps.jpg)

### Dynamic map modules

Use a dynamic map when you want to show a specific location — for example, a parking lot or a stadium entrance — with a custom annotation.

**iOS**

`TMTicketsModuleHeaderView` renders a live `MKMapView` with an optional annotation. Configure the map region and annotation, then pass the view as the module header:

```swift
func buildParkingModule(event: TMPurchasedEvent) -> TMTicketsModule? {
    let mapRegion = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 34.0734, longitude: -118.2402),
        span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
    let mapAnnotation = TMTicketsModuleHeaderView.MapAnnotation(
        coordinate: CLLocationCoordinate2D(latitude: 34.0735, longitude: -118.2456),
        title: "Lot 1")
    let headerView = TMTicketsModuleHeaderView.build()
    headerView.configure(
        topLabelText: "Parking: Lot 1",
        mapCoordinateRegion: mapRegion,
        mapAnnotation: mapAnnotation)
    let header = TMTicketsModule.HeaderDisplay(view: headerView as UIView)
    let button = TMTicketsModule.ActionButton(title: "Directions to Parking")
    return TMTicketsModule(
        identifier: "com.mymlbappname.parkingModule",
        headerDisplay: header,
        actionButtons: [button])
}
```

**Android**

Use a `MapView` from the Maps SDK for Android inside a custom layout. Set the map camera position and add a marker, then pass the inflated view to `moduleBase.setHeader()`:

```kotlin
fun buildParkingModule(context: Context, activity: AppCompatActivity): ModuleBase {
    val moduleBase = ModuleBase(context)
    val mapView = MapView(context)
    mapView.layoutParams = ConstraintLayout.LayoutParams(
        ConstraintLayout.LayoutParams.MATCH_PARENT,
        context.dpToPx(200f).roundToInt())
    mapView.onCreate(null)
    mapView.getMapAsync { googleMap ->
        val lotLocation = LatLng(34.0735, -118.2456)
        googleMap.addMarker(MarkerOptions().position(lotLocation).title("Lot 1"))
        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(lotLocation, 15f))
        googleMap.uiSettings.isScrollGesturesEnabled = false
    }
    moduleBase.setHeader(mapView)
    moduleBase.setLeftButtonText("Directions to Parking")
    moduleBase.setLeftClickListener {
        val uri = Uri.parse("geo:34.0735,-118.2456?q=Dodger+Stadium+Lot+1")
        context.startActivity(Intent(Intent.ACTION_VIEW, uri))
    }
    return moduleBase
}
```

> [!WARNING]
> **Note:** Call `mapView.onResume()` and the corresponding lifecycle methods from your Fragment or Activity to ensure the map renders correctly.

![Dynamic map module showing parking lot annotation](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/DodgerStadiumParking.jpg)

### Static image map modules

When you have a pre-rendered venue map image, use it as the module header and link to a full PDF or interactive map through a button.

**iOS**

```swift
func buildVenueMapModule(event: TMPurchasedEvent) -> TMTicketsModule? {
    let mapImage = UIImage(named: "venueMap")!
    let headerView = TMTicketsModuleHeaderView.build()
    headerView.configure(backgroundImage: mapImage)
    let header = TMTicketsModule.HeaderDisplay(view: headerView as UIView)
    let urlRequest = URLRequest(url: URL(string: "https://example.com/venue-map.pdf")!)
    let webpageSettings = TMTicketsModule.WebpageSettings(
        pageTitle: "Venue Map",
        urlRequest: urlRequest)
    let button = TMTicketsModule.ActionButton(
        title: "View Full Map",
        webpageSettings: webpageSettings)
    return TMTicketsModule(
        identifier: "com.myappname.venueMap",
        headerDisplay: header,
        actionButtons: [button])
}
```

**Android**

```kotlin
fun buildVenueMapModule(context: Context): ModuleBase {
    val moduleBase = ModuleBase(context)
    val imageView = ImageView(context)
    imageView.layoutParams = ConstraintLayout.LayoutParams(
        ConstraintLayout.LayoutParams.MATCH_PARENT,
        context.dpToPx(200f).roundToInt())
    imageView.scaleType = ImageView.ScaleType.CENTER_CROP
    imageView.setImageResource(R.drawable.venue_map)
    moduleBase.setHeader(imageView)
    moduleBase.setLeftButtonText("View Full Map")
    moduleBase.setLeftClickListener {
        val url = "https://example.com/venue-map.pdf"
        context.startActivity(TmxWebUriHelper.openWebUriWebView(context, url))
    }
    return moduleBase
}
```

![Static image map module with a](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/DisneylandModule.jpg) ![Webpage opened from map module button](https://cdn.document360.io/995f0bfa-740b-4247-b341-fde1e5326a96/Images/Documentation/DisneylandWebMap.jpg)
