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

# How to display products with the Qonversion SDK

> Fetch cross-platform Qonversion Products with the SDK, read every field of the Product object, and check a user's trial and introductory offer eligibility on iOS and Android.

The Qonversion SDK manages in-app purchases across platforms so you do not store App Store or Google Play product IDs and prices on the client. To promote products from the Qonversion dashboard, first [configure Products and Entitlements](subscription-management-mode#1-configure-products--entitlements) in Qonversion.

Qonversion gives you two ways to decide which products to show:

* **Remote Config (recommended)** — a [Remote Config](remote-config) holds your product IDs, so you can change the products offered, target user segments, and [run A/B tests](launch-test-from-remote-config) without an app release. See the [recommended payload structure and code samples](migrate-offerings-to-remote-configs).
* **Qonversion Products directly** — fetch the full list of configured products with the SDK, as described in [Get the list of available products](#get-the-list-of-available-products) below.

<Check>
  ### Local cache

  The [Qonversion SDK caches product and entitlement data](offline-sdk-mode), so the list stays available when the internet connection is lost or the server is delayed. On every app launch the SDK also requests the current `SKProduct` / `storeDetails` from the App Store or Google Play to keep prices and product data up to date.
</Check>

## Get the list of available products

Call the `products` method to fetch every product configured for your project. The method returns a dictionary keyed by Qonversion Product ID; each value is a `Qonversion.Product` object.

<CodeGroup>
  ```swift Swift theme={null}
  Qonversion.shared().products { productsList, error in
      let product = productsList["main"]
      if product?.type == .trial {

      }
  }
  ```

  ```objectivec Objective-C theme={null}
  [[Qonversion sharedInstance] products:^(NSDictionary<NSString *,QONProduct *> * _Nonnull productsList, NSError * _Nullable error) {
    if (error) {
      // Handle error
    }
    QONProduct *product = productsList[@"main"];
    if (product && product.type == QONProductTypeTrial) {

    }
  }];
  ```

  ```java Java theme={null}
  Qonversion.getSharedInstance().products(new QonversionProductsCallback() {
              @Override
              public void onSuccess(@NotNull Map<String, QProduct> productsList) {
                  // handle available products here
              }

              @Override
              public void onError(@NotNull QonversionError error) {
                  // handle error here
              }
  });
  ```

  ```kotlin Kotlin theme={null}
  Qonversion.shared.products(callback = object: QonversionProductsCallback {
              override fun onSuccess(products: Map<String, QProduct>) {
                  // handle available products here
              }

              override fun onError(error: QonversionError) {
                  // handle error here
              }
  })
  ```

  ```dart Flutter theme={null}
  try {
    final Map<String, QProduct> products = await Qonversion.getSharedInstance().products();
  } catch (e) {
    print(e);
  }
  ```

  ```typescript React Native theme={null}
  const products: Map<string, Product> = await Qonversion.getSharedInstance().products();
  ```

  ```csharp Unity theme={null}
  Qonversion.GetSharedInstance().Products((products, error) =>
  {
     if (error == null)
     {
         // Display products
     }
     else
     {
         // Handle the error
         Debug.Log("Error" + error.ToString());
     }
  });
  ```

  ```typescript Cordova theme={null}
  const products = await Qonversion.getSharedInstance().products();
  ```

  ```typescript Capacitor theme={null}
  const products: Map<string, Product> = await Qonversion.getSharedInstance().products();
  ```
</CodeGroup>

The callback (or return value) provides:

* `productsList` — a dictionary of available products, keyed by Qonversion Product ID (for example, `main`). Values are `Qonversion.Product` objects.
* `error` — the error, if the request failed. On callback-based platforms the products dictionary is empty when `error` is set.

The `products` method returns only products you configured in the Qonversion dashboard. It does not create products in the stores, and it does not return App Store or Google Play products that are not linked to a Qonversion Product.

## The Product object

`Qonversion.Product` exposes the fields below. Fields marked *iOS only* or *Android only* are `nil`/`null` on the other platform.

| Property             | Type / values                                                                  | Description                                                                                                                                                                                                                                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qonversionID`       | String                                                                         | Qonversion Product ID. For example, `main`.                                                                                                                                                                                                                                                                                              |
| `storeID`            | String                                                                         | The store product ID (App Store or Google Play).                                                                                                                                                                                                                                                                                         |
| `basePlanID`         | String, *Android only*                                                         | Identifier of the base plan for a subscription product.                                                                                                                                                                                                                                                                                  |
| `type`               | Enum: `trial`, `intro`, `directSubscription`/`subscription`, `oneTime`/`inapp` | Product type. `trial` — subscription with a trial period; `intro` — subscription with an intro period; `directSubscription`/`subscription` — auto-renewable or prepaid (Android) subscription without a trial or intro; `oneTime`/`inapp` — non-recurring product.                                                                       |
| `subscriptionPeriod` | `SubscriptionPeriod` object, or `nil` if not a subscription                    | The subscription period. `SubscriptionPeriod` has `SubscriptionPeriodUnit` (`day`, `week`, `month`, or `year`) and an integer `unitCount`. For example, `unit = .month` and `unitCount = 3` means a 3-month subscription. On Android and cross-platform SDKs it also has an `iso` field in ISO 8601 format, e.g. `P3M` for that example. |
| `trialPeriod`        | `SubscriptionPeriod` object, or `nil` if not a subscription or no trial        | The trial period. Same shape as `subscriptionPeriod`. For example, `unit = .day` and `unitCount = 7` means a 7-day trial. On Android and cross-platform SDKs it also has an `iso` field, e.g. `P3M`.                                                                                                                                     |
| `skProduct`          | `SKProduct`, *iOS only*                                                        | The `SKProduct` object received from StoreKit.                                                                                                                                                                                                                                                                                           |
| `storeDetails`       | Store details object, *Android only*                                           | The Google Play store details of the product, including the offers for purchasing the base plan (specified by `basePlanID`) for a subscription. Full description: [Google Play product details](google-play-product-details).                                                                                                            |
| `prettyPrice`        | String                                                                         | A localized price with currency symbol from Apple or Google, ready to display. For example, `$99.99`.                                                                                                                                                                                                                                    |

<Accordion title="Deprecated fields" icon="triangle-exclamation">
  **These fields are deprecated. Use the replacement field listed for each — new integrations should not read these.**

  | Property        | Type / values                                                                                                                                   | Status                                     | Description                                                                                                                                                                                |
  | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
  | `duration`      | Enum: `unknown`, `weekly`, `monthly`, `3Months`, `6Months`, `annual`, *iOS only*                                                                | **Deprecated — use `subscriptionPeriod`.** | Duration of a product. `unknown` — non-renewable purchases. There is no 2-month option because Google Play does not have it.                                                               |
  | `trialDuration` | Enum: `notAvailable`, `unknown`, `threeDays`, `week`, `twoWeeks`, `month`, `twoMonths`, `threeMonths`, `sixMonths`, `year`, `other`, *iOS only* | **Deprecated — use `trialPeriod`.**        | Duration of an introductory offer. `notAvailable` — trial not available; `unknown` — no trial info; `other` — duration outside the enum range, check `skuDetails` or `skProduct` directly. |
  | `skuDetail`     | `skuDetails` object, *Android only*                                                                                                             | **Deprecated — use `storeDetails`.**       | The `skuDetails` object received from the Google Billing Client.                                                                                                                           |
</Accordion>

## How to check trial and introductory offer eligibility

You can check whether a user is eligible for an introductory offer, including a free trial, before showing a price. Eligibility is computed differently per platform:

* **iOS** — a user is eligible if they have not previously used an introductory offer for any product in the same subscription group.
* **Android** — eligibility is computed from the store details. If the Google Play Billing Library returns any trial or intro offer as a possible purchase option for a product, the user is eligible for it.

Show only the regular price to users who are not eligible for an introductory offer. Use `checkTrialIntroEligibility` to determine eligibility:

<CodeGroup>
  ```swift Swift theme={null}
  Qonversion.shared().checkTrialIntroEligibility(["main", "secondary"]) { (result, error) in
    if let mainProductIntroEligibility = result["main"],
       mainProductIntroEligibility.status == .eligible {
       // handle available trial
    }
  }
  ```

  ```objectivec Objective-C theme={null}
  [[Qonversion sharedInstance] checkTrialIntroEligibility:@[@"main", @"secondary"] completion:^(NSDictionary<NSString *,QNIntroEligibility *> * _Nonnull result, NSError * _Nullable error) {
    QONIntroEligibility *mainProductEligibility = result[@"main"];
    if (mainProductEligibility && mainProductEligibility.status == QONIntroEligibilityStatusEligible) {
      // handle available trial
    }
  }];
  ```

  ```java Java theme={null}
  List<String> productIds = Arrays.asList("main", "secondary");
  Qonversion.getSharedInstance().checkTrialIntroEligibility(productIds, new QonversionEligibilityCallback() {
      @Override
      public void onSuccess(@NotNull Map<String, QEligibility> eligibilities) {
          QEligibility mainProductEligibility = eligibilities.get("main");
          if (mainProductEligibility != null && mainProductEligibility.getStatus() == QIntroEligibilityStatus.Eligible) {
              // handle available trial
          }
      }

      @Override
      public void onError(@NotNull QonversionError error) {
          // handle error here
      }
  });
  ```

  ```kotlin Kotlin theme={null}
  Qonversion.shared.checkTrialIntroEligibility(listOf("main", "secondary"), object : QonversionEligibilityCallback {
      override fun onSuccess(eligibilities: Map<String, QEligibility>) {
          val mainProductEligibility = eligibilities["main"];
          if (mainProductEligibility != null && mainProductEligibility.status == QIntroEligibilityStatus.Eligible) {
              // handle result here
          }
      }

      override fun onError(error: QonversionError) {
          // handle error here
      }
  })
  ```

  ```dart Flutter theme={null}
  try {
    final Map<String, QEligibility> eligibility = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'premium']);
    final QEligibility mainProductStatus = eligibility['main'];
    if (mainProductStatus.status == QEligibilityStatus.eligible) {
        // handle available trial
    }
  } catch (e) {
    print(e);
  }
  ```

  ```typescript React Native theme={null}
  try {
      const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
      const mainProductEligibility = eligibilityStatuses.get('main');
      if (mainProductEligibility && mainProductEligibility.status === IntroEligibilityStatus.ELIGIBLE) {
          // handle result here
      }
  } catch (error) {
      // handle error here
  }
  ```

  ```csharp Unity theme={null}
  string[] productIds = {"premium"};

  Qonversion.GetSharedInstance().CheckTrialIntroEligibility(productIds, (eligibility, error) =>
  {
      if (error == null)
      {
          if (eligibility.TryGetValue("premium", out Eligibility premiumEligibility) && premiumEligibility.Status == EligibilityStatus.Eligible)
          {
              // handle available eligibility
          }
      }
  });
  ```

  ```typescript Cordova theme={null}
  try {
      const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
      const mainProductEligibility = eligibilityStatuses.get('main');
      if (mainProductEligibility && mainProductEligibility.status === Qonversion.IntroEligibilityStatus.ELIGIBLE) {
          // handle result here
      }
  } catch (error) {
      // handle error here
  }
  ```

  ```typescript Capacitor theme={null}
  try {
      const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
      const mainProductEligibility = eligibilityStatuses.get('main');
      if (mainProductEligibility && mainProductEligibility.status === IntroEligibilityStatus.ELIGIBLE) {
          // handle result here
      }
  } catch (error) {
      // handle error here
  }
  ```
</CodeGroup>
