---
title: Build a mobile storefront
description: >-
  Fetch products, create a cart, and present Shopify checkout in your mobile app
  using the Storefront API and Checkout Kit.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/mobile/build-mobile-storefront?extension=swift
  md: >-
    https://shopify.dev/docs/storefronts/mobile/build-mobile-storefront.md?extension=swift
---

# Build a mobile storefront

Build a mobile app that fetches products from your store, creates a cart, and presents checkout. You'll use the [Storefront API](https://shopify.dev/docs/api/storefront) to fetch products and create a cart, and [Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit) to present Shopify's checkout inside your app.

**Skip ahead:**

If you already have a checkout URL, then go to [Embed Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit) for a quick start.

## What you'll learn

In this tutorial, you'll:

* Query the Storefront API to fetch products and their variant IDs.
* Build a native product list with images, prices, and an add-to-cart action.
* Get a `checkoutUrl` using the `cartCreate` mutation or a cart permalink.
* Install Checkout Kit and present Shopify checkout inside your app.
* Test the complete purchase flow from browsing to order confirmation.

## Requirements

[Create a development store](https://shopify.dev/docs/apps/build/stores/development-stores)

A Shopify development store with at least one product. Select **Generate test data** during creation so your store has products to work with.

[Storefront API access token](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/getting-started#step-1-enable-storefront-api-access)

The Headless channel installed in your store with a public access token.

[Set up your development environment](https://developer.apple.com/xcode/)

Xcode 14 or later, targeting iOS 13 or later.

## Project

[View on GitHub](https://github.com/Shopify/example-mobile--storefront--swift)

## Fetch products

Use the Storefront API to fetch products from your store. Each product includes variant IDs that you'll need when creating a cart.

### Query products from the Storefront API

Send a GraphQL query to `https://{shop}.myshopify.com/api/{api-version}/graphql.json` with your public access token in the `X-Shopify-Storefront-Access-Token` header. Replace `{shop}` with your store's myshopify.com subdomain.

This query fetches the first 10 products with their titles, images, and variant IDs. Each product's variant includes an `id` (for example, `gid://shopify/ProductVariant/44217898279174`) and a `price`. You'll use the variant `id` when creating a cart.

***

**Tip:**

If you receive a 401 error, then check that your access token is correct and that you're using a public Storefront API token. The token goes in the `X-Shopify-Storefront-Access-Token` header, not the `Authorization` header.

[Storefront API](https://shopify.dev/docs/api/storefront) [Products query](https://shopify.dev/docs/api/storefront/latest/queries/products)

## /StorefrontClient.swift

```swift
import Foundation


class StorefrontClient {
    let shopDomain: String
    let accessToken: String
    let apiVersion: String


    init(shopDomain: String, accessToken: String, apiVersion: String = "2026-01") {
        self.shopDomain = shopDomain
        self.accessToken = accessToken
        self.apiVersion = apiVersion
    }


    func fetchProducts() async throws -> [Product] {
        let url = URL(string: "https://\(shopDomain)/api/\(apiVersion)/graphql.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(accessToken, forHTTPHeaderField: "X-Shopify-Storefront-Access-Token")


        let query = """
        query Products {
          products(first: 10) {
            edges {
              node {
                id
                title
                description
                featuredImage { url }
                variants(first: 1) {
                  edges {
                    node {
                      id
                      title
                      price { amount currencyCode }
                    }
                  }
                }
              }
            }
          }
        }
        """


        let body = ["query": query]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)


        let (data, response) = try await URLSession.shared.data(for: request)


        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw StorefrontError.requestFailed
        }


        let result = try JSONDecoder().decode(ProductsResponse.self, from: data)
        return result.data.products.edges.map { $0.node }
    }


    func createCart(variantId: String, quantity: Int = 1) async throws -> Cart {
        let url = URL(string: "https://\(shopDomain)/api/\(apiVersion)/graphql.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(accessToken, forHTTPHeaderField: "X-Shopify-Storefront-Access-Token")


        let mutation = """
        mutation CartCreate($input: CartInput!) {
          cartCreate(input: $input) {
            cart {
              id
              checkoutUrl
            }
            userErrors {
              field
              message
            }
          }
        }
        """


        let variables: [String: Any] = [
            "input": [
                "lines": [
                    ["merchandiseId": variantId, "quantity": quantity]
                ]
            ]
        ]


        let body: [String: Any] = ["query": mutation, "variables": variables]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)


        let (data, response) = try await URLSession.shared.data(for: request)


        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw StorefrontError.requestFailed
        }


        let result = try JSONDecoder().decode(CartCreateResponse.self, from: data)


        if let error = result.data.cartCreate.userErrors.first {
            throw CartError.userError(error.message)
        }


        guard let cart = result.data.cartCreate.cart else {
            throw CartError.cartNotCreated
        }


        return cart
    }


    func buildCartPermalink(variantId: String, quantity: Int = 1) -> URL {
        let numericVariantId = variantId.split(separator: "/").last.map(String.init) ?? variantId
        return URL(string: "https://\(shopDomain)/cart/\(numericVariantId):\(quantity)")!
    }
}
```

### Define response types

Create `Decodable` structs to parse the GraphQL JSON response. The Storefront API wraps results in `edges` and `node` objects, so these types unwrap that structure into Swift types you can use throughout your app.

## /Models.swift

```swift
import Foundation


struct ProductsResponse: Decodable {
    let data: ProductsData
}


struct ProductsData: Decodable {
    let products: ProductConnection
}


struct ProductConnection: Decodable {
    let edges: [ProductEdge]
}


struct ProductEdge: Decodable {
    let node: Product
}


struct Product: Decodable, Identifiable {
    let id: String
    let title: String
    let description: String?
    let featuredImage: FeaturedImage?
    let variants: VariantConnection


    var firstVariantId: String? {
        variants.edges.first?.node.id
    }
}


struct FeaturedImage: Decodable {
    let url: String
}


struct VariantConnection: Decodable {
    let edges: [VariantEdge]
}


struct VariantEdge: Decodable {
    let node: ProductVariant
}


struct ProductVariant: Decodable {
    let id: String
    let title: String
    let price: Price
}


struct Price: Decodable {
    let amount: String
    let currencyCode: String
}


struct CartCreateResponse: Decodable {
    let data: CartCreateData
}


struct CartCreateData: Decodable {
    let cartCreate: CartCreatePayload
}


struct CartCreatePayload: Decodable {
    let cart: Cart?
    let userErrors: [UserError]
}


struct Cart: Decodable {
    let id: String
    let checkoutUrl: String
}


struct UserError: Decodable {
    let field: [String]?
    let message: String
}


enum StorefrontError: Error {
    case requestFailed
    case decodingFailed
}


enum CartError: Error {
    case userError(String)
    case cartNotCreated
}
```

## Display products in your UI

Build a product list that renders the fetched products with an **Add to Cart** button for each product.

### Build the product list

Create a SwiftUI view that loads products on appear and renders each one with a title, price, image, and **Add to Cart** button. The view model calls `fetchProducts()` from the Storefront client and stores the results.

***

Your implementation depends on your app's design, so treat this as a starting point. When the buyer taps **Add to Cart**, the handler pulls the variant ID from the selected product and creates a cart.

## /ProductListView\.swift

```swift
import SwiftUI


struct ProductListView: View {
    @StateObject private var viewModel = ProductListViewModel()


    var body: some View {
        NavigationView {
            List(viewModel.products) { product in
                ProductRow(
                    product: product,
                    onAddToCart: { viewModel.addToCart(product: product) }
                )
            }
            .navigationTitle("Products")
            .overlay {
                if viewModel.isLoading {
                    ProgressView()
                }
            }
        }
        .task {
            await viewModel.loadProducts()
        }
    }
}


struct ProductRow: View {
    let product: Product
    let onAddToCart: () -> Void


    var body: some View {
        HStack {
            AsyncImage(url: URL(string: product.featuredImage?.url ?? "")) { image in
                image.resizable().scaledToFit()
            } placeholder: {
                Color.gray.opacity(0.3)
            }
            .frame(width: 60, height: 60)
            .cornerRadius(8)


            VStack(alignment: .leading) {
                Text(product.title)
                    .font(.headline)
                if let price = product.variants.edges.first?.node.price {
                    Text("$\(price.amount) \(price.currencyCode)")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
            }


            Spacer()


            Button("Add to Cart") {
                onAddToCart()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}


@MainActor
class ProductListViewModel: ObservableObject {
    @Published var products: [Product] = []
    @Published var isLoading = false


    private let client = StorefrontClient(
        shopDomain: "{shop}.myshopify.com",
        accessToken: "your-storefront-access-token"
    )


    func loadProducts() async {
        isLoading = true
        defer { isLoading = false }


        do {
            products = try await client.fetchProducts()
        } catch {
            print("Failed to load products: \(error)")
        }
    }


    func addToCart(product: Product) {
        guard let variantId = product.firstVariantId else { return }
        Task {
            do {
                let cart = try await client.createCart(variantId: variantId)
                print("Checkout URL: \(cart.checkoutUrl)")
            } catch {
                print("Failed to create cart: \(error)")
            }
        }
    }
}
```

## Get a checkout URL

You need a `checkoutUrl` to present to the buyer. You can create a cart through the Storefront API, or build a URL directly from variant IDs using a cart permalink.

### Create a cart with the Storefront API

Send a [`cartCreate`](https://shopify.dev/docs/api/storefront/latest/mutations/cartCreate) mutation with the variant ID as the `merchandiseId` in a [`CartInput`](https://shopify.dev/docs/api/storefront/latest/input-objects/CartInput) line item. The response includes a `checkoutUrl` that you pass to Checkout Kit.

***

Build a `checkoutUrl` without the API by using a [cart permalink](https://shopify.dev/docs/apps/build/checkout/create-cart-permalinks). The format is `https://{shop}.myshopify.com/cart/{variant_id}:{quantity}`. Cart permalinks require numeric variant IDs (for example, `12345678901234`), not the GraphQL format (`gid://shopify/ProductVariant/12345678901234`).

**Caution:**

The cart ID includes a secret key in the format `gid://shopify/Cart/{token}?key={secret}`. Always store the complete ID including the `?key=` parameter. Without it, queries strip buyer data and mutations fail.

[cart​Create mutation](https://shopify.dev/docs/api/storefront/latest/mutations/cartCreate) [Manage carts with the Storefront API](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/cart/manage) [Cart permalinks](https://shopify.dev/docs/apps/build/checkout/create-cart-permalinks)

## /StorefrontClient.swift

```swift
import Foundation


class StorefrontClient {
    let shopDomain: String
    let accessToken: String
    let apiVersion: String


    init(shopDomain: String, accessToken: String, apiVersion: String = "2026-01") {
        self.shopDomain = shopDomain
        self.accessToken = accessToken
        self.apiVersion = apiVersion
    }


    func fetchProducts() async throws -> [Product] {
        let url = URL(string: "https://\(shopDomain)/api/\(apiVersion)/graphql.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(accessToken, forHTTPHeaderField: "X-Shopify-Storefront-Access-Token")


        let query = """
        query Products {
          products(first: 10) {
            edges {
              node {
                id
                title
                description
                featuredImage { url }
                variants(first: 1) {
                  edges {
                    node {
                      id
                      title
                      price { amount currencyCode }
                    }
                  }
                }
              }
            }
          }
        }
        """


        let body = ["query": query]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)


        let (data, response) = try await URLSession.shared.data(for: request)


        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw StorefrontError.requestFailed
        }


        let result = try JSONDecoder().decode(ProductsResponse.self, from: data)
        return result.data.products.edges.map { $0.node }
    }


    func createCart(variantId: String, quantity: Int = 1) async throws -> Cart {
        let url = URL(string: "https://\(shopDomain)/api/\(apiVersion)/graphql.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(accessToken, forHTTPHeaderField: "X-Shopify-Storefront-Access-Token")


        let mutation = """
        mutation CartCreate($input: CartInput!) {
          cartCreate(input: $input) {
            cart {
              id
              checkoutUrl
            }
            userErrors {
              field
              message
            }
          }
        }
        """


        let variables: [String: Any] = [
            "input": [
                "lines": [
                    ["merchandiseId": variantId, "quantity": quantity]
                ]
            ]
        ]


        let body: [String: Any] = ["query": mutation, "variables": variables]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)


        let (data, response) = try await URLSession.shared.data(for: request)


        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw StorefrontError.requestFailed
        }


        let result = try JSONDecoder().decode(CartCreateResponse.self, from: data)


        if let error = result.data.cartCreate.userErrors.first {
            throw CartError.userError(error.message)
        }


        guard let cart = result.data.cartCreate.cart else {
            throw CartError.cartNotCreated
        }


        return cart
    }


    func buildCartPermalink(variantId: String, quantity: Int = 1) -> URL {
        let numericVariantId = variantId.split(separator: "/").last.map(String.init) ?? variantId
        return URL(string: "https://\(shopDomain)/cart/\(numericVariantId):\(quantity)")!
    }
}
```

## Present checkout with Checkout Kit

Install Checkout Kit and present Shopify checkout inside your app. Your app receives callbacks when checkout completes, fails, or the buyer cancels.

### Install Checkout Kit

Add the `checkout-sheet-kit-swift` package to your project using Swift Package Manager, or add it through Xcode by going to **File** > **Add Package Dependencies**.

***

## /Package.swift

```swift
// swift-tools-version: 5.7
import PackageDescription


let package = Package(
    name: "MobileStorefront",
    platforms: [
        .iOS(.v15)
    ],
    dependencies: [
        .package(url: "https://github.com/Shopify/checkout-sheet-kit-swift", from: "3.0.0")
    ],
    targets: [
        .executableTarget(
            name: "MobileStorefront",
            dependencies: [
                .product(name: "ShopifyCheckoutSheetKit", package: "checkout-sheet-kit-swift")
            ]
        )
    ]
)
```

### Present checkout and handle events

Call `present()` with your `checkoutUrl`. Checkout Kit presents the store's checkout, including branding and all configured payment methods.

Implement the `CheckoutDelegate` protocol to respond when the buyer completes checkout, cancels, encounters an error, or taps an external link.

***

**Tip:**

Request the `checkoutUrl` when the buyer is ready to check out. Checkout URLs can go stale, especially for [authenticated checkouts](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts). If the URL is stale, then query the cart again to get a new one.

[Monitor checkout lifecycle](https://shopify.dev/docs/storefronts/mobile/checkout-kit/monitor-checkout-lifecycle) [Preload checkout](https://shopify.dev/docs/storefronts/mobile/checkout-kit/preloading)

## /CartViewController.swift

```swift
import UIKit
import ShopifyCheckoutSheetKit


class CartViewController: UIViewController {
    private var checkoutUrl: URL?
    private let storefrontClient = StorefrontClient(
        shopDomain: "{shop}.myshopify.com",
        accessToken: "your-storefront-access-token"
    )


    func addToCart(variantId: String) async {
        do {
            let cart = try await storefrontClient.createCart(variantId: variantId)
            checkoutUrl = URL(string: cart.checkoutUrl)
        } catch {
            print("Failed to create cart: \(error)")
        }
    }


    @IBAction func checkoutTapped(_ sender: UIButton) {
        guard let url = checkoutUrl else {
            print("No checkout URL available")
            return
        }


        ShopifyCheckoutSheetKit.present(
            checkout: url,
            from: self,
            delegate: self
        )
    }
}


extension CartViewController: CheckoutDelegate {
    func checkoutDidComplete(event: CheckoutCompletedEvent) {
        let orderId = event.orderDetails.id
        print("Order completed: \(orderId)")
        checkoutUrl = nil
    }


    func checkoutDidCancel() {
        print("Checkout canceled")
    }


    func checkoutDidFail(error: CheckoutError) {
        print("Checkout failed: \(error.localizedDescription)")


        let alert = UIAlertController(
            title: "Checkout Error",
            message: error.localizedDescription,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }


    func checkoutDidClickLink(url: URL) {
        UIApplication.shared.open(url)
    }
}
```

## Test the complete flow

Verify the full flow by browsing products, completing a purchase, and checking the order in the Shopify admin.

### Run the app and complete a test purchase

Run your app on a simulator, emulator, or device. Browse the product list and confirm that titles, prices, and images load from your store.

Tap **Add to Cart** on a product, then complete checkout. To process test payments, enable [Shopify's Bogus Gateway](https://help.shopify.com/en/manual/checkout-settings/test-orders/payments-test-mode#bogus-gateway) in your development store by going to **Settings** > **Payments**.

After checkout completes, open your Shopify admin and go to **Orders**. The new order appears with the products you selected.

***

If products don't load, then check that your Storefront API public access token is correct and that you enabled **Products and Collections** permissions in the Headless channel. If checkout doesn't open, then verify that your `checkoutUrl` is valid and that the cart hasn't expired.

## /StorefrontClient.swift

```swift
import Foundation


class StorefrontClient {
    let shopDomain: String
    let accessToken: String
    let apiVersion: String


    init(shopDomain: String, accessToken: String, apiVersion: String = "2026-01") {
        self.shopDomain = shopDomain
        self.accessToken = accessToken
        self.apiVersion = apiVersion
    }


    func fetchProducts() async throws -> [Product] {
        let url = URL(string: "https://\(shopDomain)/api/\(apiVersion)/graphql.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(accessToken, forHTTPHeaderField: "X-Shopify-Storefront-Access-Token")


        let query = """
        query Products {
          products(first: 10) {
            edges {
              node {
                id
                title
                description
                featuredImage { url }
                variants(first: 1) {
                  edges {
                    node {
                      id
                      title
                      price { amount currencyCode }
                    }
                  }
                }
              }
            }
          }
        }
        """


        let body = ["query": query]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)


        let (data, response) = try await URLSession.shared.data(for: request)


        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw StorefrontError.requestFailed
        }


        let result = try JSONDecoder().decode(ProductsResponse.self, from: data)
        return result.data.products.edges.map { $0.node }
    }


    func createCart(variantId: String, quantity: Int = 1) async throws -> Cart {
        let url = URL(string: "https://\(shopDomain)/api/\(apiVersion)/graphql.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(accessToken, forHTTPHeaderField: "X-Shopify-Storefront-Access-Token")


        let mutation = """
        mutation CartCreate($input: CartInput!) {
          cartCreate(input: $input) {
            cart {
              id
              checkoutUrl
            }
            userErrors {
              field
              message
            }
          }
        }
        """


        let variables: [String: Any] = [
            "input": [
                "lines": [
                    ["merchandiseId": variantId, "quantity": quantity]
                ]
            ]
        ]


        let body: [String: Any] = ["query": mutation, "variables": variables]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)


        let (data, response) = try await URLSession.shared.data(for: request)


        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw StorefrontError.requestFailed
        }


        let result = try JSONDecoder().decode(CartCreateResponse.self, from: data)


        if let error = result.data.cartCreate.userErrors.first {
            throw CartError.userError(error.message)
        }


        guard let cart = result.data.cartCreate.cart else {
            throw CartError.cartNotCreated
        }


        return cart
    }


    func buildCartPermalink(variantId: String, quantity: Int = 1) -> URL {
        let numericVariantId = variantId.split(separator: "/").last.map(String.init) ?? variantId
        return URL(string: "https://\(shopDomain)/cart/\(numericVariantId):\(quantity)")!
    }
}
```

## /Models.swift

```swift
import Foundation


struct ProductsResponse: Decodable {
    let data: ProductsData
}


struct ProductsData: Decodable {
    let products: ProductConnection
}


struct ProductConnection: Decodable {
    let edges: [ProductEdge]
}


struct ProductEdge: Decodable {
    let node: Product
}


struct Product: Decodable, Identifiable {
    let id: String
    let title: String
    let description: String?
    let featuredImage: FeaturedImage?
    let variants: VariantConnection


    var firstVariantId: String? {
        variants.edges.first?.node.id
    }
}


struct FeaturedImage: Decodable {
    let url: String
}


struct VariantConnection: Decodable {
    let edges: [VariantEdge]
}


struct VariantEdge: Decodable {
    let node: ProductVariant
}


struct ProductVariant: Decodable {
    let id: String
    let title: String
    let price: Price
}


struct Price: Decodable {
    let amount: String
    let currencyCode: String
}


struct CartCreateResponse: Decodable {
    let data: CartCreateData
}


struct CartCreateData: Decodable {
    let cartCreate: CartCreatePayload
}


struct CartCreatePayload: Decodable {
    let cart: Cart?
    let userErrors: [UserError]
}


struct Cart: Decodable {
    let id: String
    let checkoutUrl: String
}


struct UserError: Decodable {
    let field: [String]?
    let message: String
}


enum StorefrontError: Error {
    case requestFailed
    case decodingFailed
}


enum CartError: Error {
    case userError(String)
    case cartNotCreated
}
```

## /ProductListView\.swift

```swift
import SwiftUI


struct ProductListView: View {
    @StateObject private var viewModel = ProductListViewModel()


    var body: some View {
        NavigationView {
            List(viewModel.products) { product in
                ProductRow(
                    product: product,
                    onAddToCart: { viewModel.addToCart(product: product) }
                )
            }
            .navigationTitle("Products")
            .overlay {
                if viewModel.isLoading {
                    ProgressView()
                }
            }
        }
        .task {
            await viewModel.loadProducts()
        }
    }
}


struct ProductRow: View {
    let product: Product
    let onAddToCart: () -> Void


    var body: some View {
        HStack {
            AsyncImage(url: URL(string: product.featuredImage?.url ?? "")) { image in
                image.resizable().scaledToFit()
            } placeholder: {
                Color.gray.opacity(0.3)
            }
            .frame(width: 60, height: 60)
            .cornerRadius(8)


            VStack(alignment: .leading) {
                Text(product.title)
                    .font(.headline)
                if let price = product.variants.edges.first?.node.price {
                    Text("$\(price.amount) \(price.currencyCode)")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
            }


            Spacer()


            Button("Add to Cart") {
                onAddToCart()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}


@MainActor
class ProductListViewModel: ObservableObject {
    @Published var products: [Product] = []
    @Published var isLoading = false


    private let client = StorefrontClient(
        shopDomain: "{shop}.myshopify.com",
        accessToken: "your-storefront-access-token"
    )


    func loadProducts() async {
        isLoading = true
        defer { isLoading = false }


        do {
            products = try await client.fetchProducts()
        } catch {
            print("Failed to load products: \(error)")
        }
    }


    func addToCart(product: Product) {
        guard let variantId = product.firstVariantId else { return }
        Task {
            do {
                let cart = try await client.createCart(variantId: variantId)
                print("Checkout URL: \(cart.checkoutUrl)")
            } catch {
                print("Failed to create cart: \(error)")
            }
        }
    }
}
```

## /Package.swift

```swift
// swift-tools-version: 5.7
import PackageDescription


let package = Package(
    name: "MobileStorefront",
    platforms: [
        .iOS(.v15)
    ],
    dependencies: [
        .package(url: "https://github.com/Shopify/checkout-sheet-kit-swift", from: "3.0.0")
    ],
    targets: [
        .executableTarget(
            name: "MobileStorefront",
            dependencies: [
                .product(name: "ShopifyCheckoutSheetKit", package: "checkout-sheet-kit-swift")
            ]
        )
    ]
)
```

## /CartViewController.swift

```swift
import UIKit
import ShopifyCheckoutSheetKit


class CartViewController: UIViewController {
    private var checkoutUrl: URL?
    private let storefrontClient = StorefrontClient(
        shopDomain: "{shop}.myshopify.com",
        accessToken: "your-storefront-access-token"
    )


    func addToCart(variantId: String) async {
        do {
            let cart = try await storefrontClient.createCart(variantId: variantId)
            checkoutUrl = URL(string: cart.checkoutUrl)
        } catch {
            print("Failed to create cart: \(error)")
        }
    }


    @IBAction func checkoutTapped(_ sender: UIButton) {
        guard let url = checkoutUrl else {
            print("No checkout URL available")
            return
        }


        ShopifyCheckoutSheetKit.present(
            checkout: url,
            from: self,
            delegate: self
        )
    }
}


extension CartViewController: CheckoutDelegate {
    func checkoutDidComplete(event: CheckoutCompletedEvent) {
        let orderId = event.orderDetails.id
        print("Order completed: \(orderId)")
        checkoutUrl = nil
    }


    func checkoutDidCancel() {
        print("Checkout canceled")
    }


    func checkoutDidFail(error: CheckoutError) {
        print("Checkout failed: \(error.localizedDescription)")


        let alert = UIAlertController(
            title: "Checkout Error",
            message: error.localizedDescription,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }


    func checkoutDidClickLink(url: URL) {
        UIApplication.shared.open(url)
    }
}
```

## Tutorial complete!

You've built a mobile storefront that fetches products, creates a cart, and presents Shopify checkout inside your app. This tutorial uses direct HTTP calls to keep things simple. For production apps, we recommend using [Apollo](https://www.apollographql.com/) for typed queries, caching, and `@defer` support. See the sample apps below for production-ready examples.

### Next steps

[Example apps\
\
](https://github.com/Shopify/checkout-sheet-kit-swift/tree/main/Samples)

[Sample apps with production-quality UI and Checkout Kit integration.](https://github.com/Shopify/checkout-sheet-kit-swift/tree/main/Samples)

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

[Add Shop Pay and Apple Pay buttons for accelerated checkouts.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/accelerated-checkouts-overview)

[Authenticate buyers\
\
](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts)

[Prefill checkout with a signed-in buyer's address and payment methods.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts)

[Preload checkout\
\
](https://shopify.dev/docs/storefronts/mobile/checkout-kit/preloading)

[Fetch checkout in the background so it's ready when the buyer taps checkout.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/preloading)
