---
title: "Feature Gating"
description: "Check premium access, show an upgrade Experience, and consume metered usage."
search:
  tags: ["sdk", "reference", "ios"]
  keywords: ["Swift", "StoreKit", "Xcode"]
---

Use a Feature to offer paid access without checking subscription products throughout your app. This guide connects `premium-export` to a Product, checks access before exporting, and shows an upgrade Experience when the customer needs it.

## Step 1: Define a feature

In **Features**, choose **Create Feature**, enter a name such as Premium export, and set the **Feature key** to `premium-export`. Select **Access (on or off)**. The key is what your app passes to the SDK.

## Step 2: Add an entitlement to a product

Open the Product's **Entitlements** tab, choose **Grant Feature**, and select `premium-export`. Confirm with **Grant Feature**. Customers with an active grant from this Product can use premium export.

See [Features and entitlements](/docs/monetization/features-and-entitlements) for grant configuration.

## Step 3: Check the feature in your app

```swift
// nuxie-doc-fragment: gate-feature-in-task harness=sync-body
Task {
    let access = try await NuxieSDK.shared.hasFeature("premium-export")
    if access.allowed {
        performExport()
    } else {
        showUpgradePrompt()
    }
}
```

`hasFeature()` uses fresh cached access and queries the server when needed. A failed request throws; handle that separately from a result with `allowed: false`.

## Step 4: Show an upgrade paywall when not entitled

<span id="approach-a-trigger-an-experience" />

### Trigger the upgrade Experience

Publish an Experience with the `feature_locked` event trigger:

```swift
// nuxie-doc-snippet: trigger-upgrade-prompt
import Nuxie

func showUpgradePrompt() {
    NuxieSDK.shared.trigger("feature_locked")
}
```

Choose **Every time** if the customer should be able to request the upgrade repeatedly. Audience and other delivery rules still apply.

### Dismissing a presented Experience

```swift
// nuxie-doc-snippet: dismiss-upgrade-prompt
import Nuxie

@MainActor
func closeAnyPresentedExperience() {
    Task {
        await NuxieSDK.shared.dismiss()
    }
}
```

Dismissal waits for an in-progress purchase or restore. It does nothing when no Experience is visible.

## Step 5: Handle the purchase and verify access

A completed checkout can update access asynchronously. Re-check access before performing the export, or observe feature changes so the UI updates when access becomes available:

```swift
// nuxie-doc-fragment: recheck-access-after-purchase harness=async-body
// After purchase completes, re-check
let access = try await NuxieSDK.shared.hasFeature("premium-export")
// Perform the export only when access.allowed is true.
```

For SwiftUI, observe feature changes directly:

```swift
// nuxie-doc-snippet: reactive-export-button
import Nuxie
import SwiftUI

struct ExportButton: View {
    @ObservedObject var features = NuxieSDK.shared.features

    var body: some View {
        Button("Export") {
            if features.isAllowed("premium-export") {
                performExport()
            } else {
                showUpgradePrompt()
            }
        }
        .disabled(features.state == .unknown)
    }
}
```

Keep the button disabled while access is `.unknown`. Boolean access can update from a verified purchase; fixed balances require server confirmation. [Purchases](/docs/ios-sdk/purchases) explains native and provider checkout.

## Metered features: track and limit usage

Use a metered feature for a fixed allowance, such as API calls. For shared [credits](/docs/monetization/credits), query and consume the credit feature itself with the action's cost.

### Create a metered feature

Create a Feature with **Metered usage** selected and the **Feature key** `api-calls`. Your app records one unit for each accepted API call.

### Add a metered entitlement

Open the Product's **Entitlements** tab and choose **Grant Feature**. Select `api-calls`, choose **Fixed units**, and enter `1000` in **Units**. The Product now grants an allowance of 1,000 calls; your app consumes that allowance as customers use it.

### Check balance for presentation

```swift
// nuxie-doc-fragment: metered-api-call-access harness=async-body
let access = try await NuxieSDK.shared.hasFeature(
    "api-calls",
    requiredBalance: 1
)
if access.allowed {
    // Show the enabled state. Authoritative consumption still happens below.
    showEnabledApiCallButton()
} else {
    // Balance exhausted -- show upgrade prompt
    showUpgradePrompt()
}
```

<span id="reserve-usage-before-the-side-effect" />

### Consume usage before the action

Wait for accepted usage before making the API call:

```swift
// nuxie-doc-fragment: consume-metered-api-call harness=async-body
do {
    let result = try await NuxieSDK.shared.useFeatureAndWait("api-calls", amount: 1)
    guard result.success else {
        showUpgradePrompt()
        return
    }
    performApiCall()
} catch {
    // Offer a retry for unavailable service; do not perform the API call.
}
```

The cached check controls the button; the usage result determines whether to run the API call. An accepted last use can leave a zero balance and still allow this call. For retries that must reuse the same operation ID, use [`consumeFeature()`](/docs/ios-sdk/features-and-entitlements#retry-an-operation).

After a native purchase, Nuxie can verify the purchase, grant its allowance, and deduct the first use together. The result's `authoritativeAccess` contains updated access when available.

<span id="next-steps" />

See [Features and entitlements](/docs/ios-sdk/features-and-entitlements) for query options and change notifications.
