---
title: Privacy compliance
description: >-
  Collect visitor consent and pass it to Checkout Kit so Shopify applies the
  right tracking and data collection rules in checkout.
source_url:
  html: 'https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance'
  md: >-
    https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance.md
---

# Privacy compliance

Privacy regulations like the General Data Protection Regulation (GDPR) and the California Consumer Privacy Act (CCPA) require consent before tracking visitors or collecting personal data. Checkout Kit passes visitor consent from your app to Shopify, which applies the right tracking and data collection rules during checkout. Collect consent in your app and pass it through the Storefront API.

***

## How it works

When you create a cart, include a `visitorConsent` parameter in the Storefront API's [`@inContext` directive](https://shopify.dev/docs/api/storefront#directives). Shopify encodes the consent into the [`checkoutUrl`](https://shopify.dev/docs/api/storefront/latest/objects/Cart#field-Cart.fields.checkoutUrl) and applies it during checkout.

The Storefront API's [`VisitorConsent`](https://shopify.dev/docs/api/storefront/latest/input-objects/VisitorConsent) input object has four fields:

* **`analytics`**: Data collection to understand buyer behavior.
* **`preferences`**: Storing visitor preferences for their shopping experience.
* **`marketing`**: Promotional communications and advertising.
* **`saleOfData`**: Sharing or selling personal data to third parties.

Each consent type is optional. If you omit a type, then Shopify treats it as not provided.

***

## Requirements

* [Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit) installed in your app.
* [Storefront API access](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/getting-started) configured for your app.
* Storefront API version `2025-10` or later, which is required for `visitorConsent` on the `@inContext` directive.

***

## Step 1: Collect visitor consent

iOS provides [App Tracking Transparency (ATT)](https://developer.apple.com/app-store/user-privacy-and-data-use/) and Android provides the [User Messaging Platform (UMP)](https://support.google.com/admob/answer/10107561). On React Native, libraries like [react-native-tracking-transparency](https://github.com/mrousavy/react-native-tracking-transparency) wrap ATT for iOS, but no comparable cross-platform wrapper exists for UMP on Android. For Android on React Native, use a [custom consent UI](#custom-consent-collection) instead. Use these frameworks to prompt buyers, then map their responses to Shopify's consent categories.

### Use platform-native consent collection

Collect consent using each platform's built-in framework:

## Platform-native consent collection

##### Swift

```swift
import AppTrackingTransparency
import AdSupport

class ConsentManager {
    func requestConsent() async -> VisitorConsent {
        // Request iOS App Tracking Transparency permission
        let status = await ATTrackingManager.requestTrackingAuthorization()
        return mapATTToVisitorConsent(status: status)
    }

    private func mapATTToVisitorConsent(status: ATTrackingManager.AuthorizationStatus) -> VisitorConsent {
        // Map ATT response to your specific consent requirements
        // ATT covers cross-app tracking - you decide how this maps to your data practices

        switch status {
        case .authorized:
            // Buyer allowed cross-app tracking
            return VisitorConsent(
                analytics: true,    // Consider: Does this cover your analytics use?
                preferences: true,  // Consider: Are preferences affected by ATT?
                marketing: true,    // Consider: Does this cover your marketing practices?
                saleOfData: true    // Consider: Does this cover your data sharing?
            )
        case .denied, .restricted:
            // Buyer denied cross-app tracking
            return VisitorConsent(
                analytics: true,    // Consider: Can you still do first-party analytics?
                preferences: true,  // Consider: Are preferences still allowed?
                marketing: false,   // Consider: What marketing is still allowed?
                saleOfData: false   // Consider: What data sharing is affected?
            )
        case .notDetermined:
            // Handle undetermined state based on your requirements
            return getDefaultConsent()
        @unknown default:
            return getDefaultConsent()
        }
    }

    private func getDefaultConsent() -> VisitorConsent {
        // Define your default consent state
        return VisitorConsent(
            analytics: false,
            preferences: true,  // Usually considered essential
            marketing: false,
            saleOfData: false
        )
    }
}

struct VisitorConsent {
    let analytics: Bool
    let preferences: Bool
    let marketing: Bool
    let saleOfData: Bool

    static let defaultConsent = VisitorConsent(
        analytics: false,
        preferences: true,
        marketing: false,
        saleOfData: false
    )
}

// Usage example
let consentManager = ConsentManager()

func collectConsentAndCreateCart() async {
    let consent = await consentManager.requestConsent()
    createCartWithConsent(consent: consent)
}
```

##### Kotlin

```kotlin
import com.google.android.ump.*

class ConsentManager(private val context: Context) {

    fun requestConsent(completion: (VisitorConsent) -> Unit) {
        val consentInformation = UserMessagingPlatform.getConsentInformation(context)

        consentInformation.requestConsentInfoUpdate(
            ConsentRequestParameters.Builder().build(),
            {
                // Load consent form if needed
                UserMessagingPlatform.loadConsentForm(context) { form, error ->
                    if (form != null) {
                        form.show(context as Activity) {
                            val consent = mapUMPToVisitorConsent(consentInformation)
                            completion(consent)
                        }
                    } else {
                        // Handle form load error
                        completion(getDefaultConsent())
                    }
                }
            },
            { error ->
                // Handle consent update error
                completion(getDefaultConsent())
            }
        )
    }

    private fun mapUMPToVisitorConsent(consentInfo: ConsentInformation): VisitorConsent {
        // Map UMP response to your specific consent requirements
        // UMP scope depends on what YOU configured in your AdMob setup

        return when (consentInfo.consentStatus) {
            ConsentInformation.ConsentStatus.OBTAINED -> {
                // Buyer provided consent based on YOUR UMP configuration
                VisitorConsent(
                    analytics = true,    // Consider: What did you ask consent for?
                    preferences = true,  // Consider: Did UMP cover preferences?
                    marketing = true,    // Consider: What marketing was included?
                    saleOfData = true    // Consider: Did you ask about data sales?
                )
            }
            ConsentInformation.ConsentStatus.NOT_REQUIRED -> {
                // Consent not required for buyer's location
                VisitorConsent(
                    analytics = true,
                    preferences = true,
                    marketing = true,
                    saleOfData = true
                )
            }
            else -> {
                // Required but not obtained, or unknown status
                getDefaultConsent()
            }
        }
    }

    private fun getDefaultConsent(): VisitorConsent {
        return VisitorConsent(
            analytics = false,
            preferences = true,  // Usually considered essential
            marketing = false,
            saleOfData = false
        )
    }
}

data class VisitorConsent(
    val analytics: Boolean,
    val preferences: Boolean,
    val marketing: Boolean,
    val saleOfData: Boolean
)
```

##### React Native

```javascript
import { ATTrackingManager } from 'react-native-tracking-transparency';

class ConsentManager {

  async requestConsent() {
    try {
      // Request iOS ATT permission
      const trackingStatus = await ATTrackingManager.requestTrackingAuthorization();
      return this.mapPlatformToVisitorConsent(trackingStatus);

    } catch (error) {
      console.error('Consent request failed:', error);
      return this.getDefaultConsent();
    }
  }

  mapPlatformToVisitorConsent(platformStatus) {
    // Map platform response to your specific consent requirements
    // Consider what each platform permission actually covers for YOUR app

    switch (platformStatus) {
      case 'authorized':
        // Buyer allowed tracking
        return {
          analytics: true,    // Consider: Your analytics practices
          preferences: true,  // Consider: Preference handling
          marketing: true,    // Consider: Your marketing scope
          saleOfData: true    // Consider: Your data sharing
        };

      case 'denied':
      case 'restricted':
        // Buyer denied or restricted tracking
        return {
          analytics: true,    // Consider: First-party analytics
          preferences: true,  // Consider: Essential preferences
          marketing: false,   // Consider: Limited marketing
          saleOfData: false   // Consider: No data sharing
        };

      case 'notDetermined':
      default:
        return this.getDefaultConsent();
    }
  }

  getDefaultConsent() {
    // Define your app's default consent state
    return {
      analytics: false,
      preferences: true,  // Usually essential for app function
      marketing: false,
      saleOfData: false
    };
  }
}

// Usage example
const consentManager = new ConsentManager();

const collectConsentAndCreateCart = async () => {
  const consent = await consentManager.requestConsent();
  createCartWithConsent(consent);
};
```

### Custom consent collection

Only implement custom consent UIs if platform-native solutions don't meet your specific requirements, for example:

* Your app needs more granular consent options than platform frameworks provide.
* You're targeting enterprise/B2B scenarios with specific compliance requirements.
* Platform-native solutions don't cover your specific use case or jurisdiction.
* You need to collect consent outside platform advertising frameworks.

## Custom consent collection

##### Swift

```swift
struct CustomConsentView: View {
    @State private var analyticsConsent = false
    @State private var preferencesConsent = true  // Often essential
    @State private var marketingConsent = false
    @State private var saleOfDataConsent = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Privacy Preferences")
                .font(.headline)

            Toggle("Analytics - Help us improve our services", isOn: $analyticsConsent)
            Toggle("Preferences - Remember your shopping preferences", isOn: $preferencesConsent)
            Toggle("Marketing - Receive promotional offers", isOn: $marketingConsent)
            Toggle("Sale of data - Allow sharing data with partners", isOn: $saleOfDataConsent)

            Button("Continue to Checkout") {
                let consent = VisitorConsent(
                    analytics: analyticsConsent,
                    preferences: preferencesConsent,
                    marketing: marketingConsent,
                    saleOfData: saleOfDataConsent
                )
                createCartWithConsent(consent: consent)
            }
        }
        .padding()
    }
}
```

##### Kotlin

```kotlin
class CustomConsentFragment : Fragment() {
    private var analyticsConsent = false
    private var preferencesConsent = true  // Often essential
    private var marketingConsent = false
    private var saleOfDataConsent = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        val view = inflater.inflate(R.layout.fragment_custom_consent, container, false)

        view.findViewById<CheckBox>(R.id.analytics_checkbox).apply {
            setOnCheckedChangeListener { _, isChecked -> analyticsConsent = isChecked }
        }

        view.findViewById<CheckBox>(R.id.preferences_checkbox).apply {
            isChecked = true  // Often essential
            setOnCheckedChangeListener { _, isChecked -> preferencesConsent = isChecked }
        }

        view.findViewById<CheckBox>(R.id.marketing_checkbox).apply {
            setOnCheckedChangeListener { _, isChecked -> marketingConsent = isChecked }
        }

        view.findViewById<CheckBox>(R.id.sale_of_data_checkbox).apply {
            setOnCheckedChangeListener { _, isChecked -> saleOfDataConsent = isChecked }
        }

        view.findViewById<Button>(R.id.continue_button).setOnClickListener {
            val consent = VisitorConsent(analyticsConsent, preferencesConsent, marketingConsent, saleOfDataConsent)
            createCartWithConsent(consent)
        }

        return view
    }
}
```

##### React Native

```javascript
import React, { useState } from 'react';
import { View, Text, Switch, Button } from 'react-native';

function CustomConsentCollection({ onConsentCollected }) {
  const [analyticsConsent, setAnalyticsConsent] = useState(false);
  const [preferencesConsent, setPreferencesConsent] = useState(true); // Often essential
  const [marketingConsent, setMarketingConsent] = useState(false);
  const [saleOfDataConsent, setSaleOfDataConsent] = useState(false);

  const handleContinue = () => {
    onConsentCollected({
      analytics: analyticsConsent,
      preferences: preferencesConsent,
      marketing: marketingConsent,
      saleOfData: saleOfDataConsent
    });
  };

  return (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold' }}>Privacy Preferences</Text>

      <View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 16 }}>
        <Switch value={analyticsConsent} onValueChange={setAnalyticsConsent} />
        <Text>Analytics - Help us improve our services</Text>
      </View>

      <View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 12 }}>
        <Switch value={preferencesConsent} onValueChange={setPreferencesConsent} />
        <Text>Preferences - Remember your shopping preferences</Text>
      </View>

      <View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 12 }}>
        <Switch value={marketingConsent} onValueChange={setMarketingConsent} />
        <Text>Marketing - Receive promotional offers</Text>
      </View>

      <View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 12 }}>
        <Switch value={saleOfDataConsent} onValueChange={setSaleOfDataConsent} />
        <Text>Sale of data - Allow sharing data with partners</Text>
      </View>

      <Button title="Continue to Checkout" onPress={handleContinue} />
    </View>
  );
}
```

***

## Step 2: Pass consent in the cart mutation

Add the `@inContext` directive with `visitorConsent` to your `cartCreate` mutation. Set each consent type to `true` or `false` based on the consent you collected:

## GraphQL mutation with consent

```graphql
mutation CreateCartWithConsent($lines: [CartLineInput!]!)
@inContext(visitorConsent: {
  analytics: true,
  preferences: true,
  marketing: false,
  saleOfData: false
}) {
  cartCreate(input: {lines: $lines}) {
    cart {
      id
      checkoutUrl
    }
    userErrors {
      field
      message
    }
  }
}
```

Pass the cart line items as variables:

## Variables

```json
{
  "lines": [
    {
      "merchandiseId": "gid://shopify/ProductVariant/12345",
      "quantity": 1
    }
  ]
}
```

The response includes a `checkoutUrl` with a `_cs` parameter that contains the encoded consent. Pass this URL to Checkout Kit:

## Example response

```json
{
  "data": {
    "cartCreate": {
      "cart": {
        "id": "gid://shopify/Cart/hWN1xFGGsrQ4tpR9nSGZtlsv",
        "checkoutUrl": "https://{shop}.myshopify.com/cart/c/hWN1xFGGsrQ4tpR9nSGZtlsv?_cs=3AmPs."
      },
      "userErrors": []
    }
  }
}
```

***

## Step 3: Present checkout

The `checkoutUrl` from Step 2 includes a `_cs` parameter that contains the encoded consent. Pass the full URL to Checkout Kit without modifying it:

**Caution:**

Pass the full URL to Checkout Kit. Don't strip or modify the `_cs` parameter appended to `checkoutUrl` in Step 2. Shopify reads it to apply the buyer's consent choices during checkout.

## Present checkout

##### Swift

```swift
import ShopifyCheckoutSheetKit

func presentCheckout(with checkoutUrl: URL) {
    ShopifyCheckoutSheetKit.present(
        checkout: checkoutUrl,
        from: self,
        delegate: self
    )
}
```

##### Kotlin

```kotlin
import com.shopify.checkoutsheetkit.ShopifyCheckoutSheetKit

fun presentCheckout(checkoutUrl: String) {
    ShopifyCheckoutSheetKit.present(checkoutUrl, this, checkoutEventProcessor)
}
```

##### React Native

```javascript
import {useShopifyCheckoutSheet} from '@shopify/checkout-sheet-kit';

function CheckoutButton({ checkoutUrl }) {
  const shopifyCheckout = useShopifyCheckoutSheet();

  const handlePress = () => {
    shopifyCheckout.present(checkoutUrl);
  };

  return (
    <Button title="Complete Checkout" onPress={handlePress} />
  );
}
```

If a buyer updates their consent, then request a new `checkoutUrl` from the Storefront API before presenting checkout. Each URL encodes the preferences from the mutation that created it, so stale URLs won't reflect the change.

***

## Preloaded checkouts

A preloaded `checkoutUrl` encodes the consent from the time you called `preload()`. If a buyer updates their preferences after preloading, the cached checkout won't reflect the change. Either collect consent before you preload, or call `ShopifyCheckoutSheetKit.invalidate()` and request a new URL when preferences change.

For more on preloading, see [Preload checkout](https://shopify.dev/docs/storefronts/mobile/checkout-kit/preloading).

***

## Next steps

[Checkout Kit for Web\
\
](https://shopify.dev/docs/storefronts/mobile/checkout-kit/web)

[Embed Shopify checkout in a web app with the JavaScript SDK.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/web)

[About accelerated checkouts\
\
](https://shopify.dev/docs/storefronts/mobile/checkout-kit/accelerated-checkouts-overview)

[Add Shop Pay and Apple Pay buttons for one-tap purchases on product and cart pages.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/accelerated-checkouts-overview)

***
