> For the complete documentation index, see [llms.txt](https://docs.onside.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.onside.io/sdk/advanced-and-tooling/local-testing.md).

# Local Testing with a .storekit File

Develop and QA the purchase and restore flows offline with a bundled StoreKit configuration file, without a backend or a real account.

Local testing runs OnsideKit fully offline against a bundled **`.storekit`** configuration file. Products come from the file, login is automatic and fake, and purchases and restores run against a local simulation — no backend, no registered app, and no real Onside account.

For an offline login flow, leave `Onside.externalLoginDelegate` unset and choose `.local` through `OnsideDelegate.onsideLoginMethod()`. An external delegate can start a real session request even when a `.storekit` file is configured; `.auto` can still attempt the store-app handoff. See [Authentication](/sdk/core-concepts/authentication.md).

## Enable it

1. Add a StoreKit configuration file to your app target — for example `LocalProducts.storekit`. (Xcode can create one via **File → New → File → StoreKit Configuration File**.)
2. Pass its **base name** (no extension) to `initialize`:

```swift
Onside.initialize(storeKitConfigurationName: "LocalProducts")
```

When `storeKitConfigurationName` is `nil` (the default), the SDK runs normally against the live backend. The choice is fixed at the first `initialize` call.

On success the SDK logs:

```
[Onside]: ✅ Initialized for local testing with N product(s) from LocalProducts.storekit
```

## How it behaves

* **Products** come from the `.storekit` file. [`makeProductsRequest`](/sdk/products-and-subscriptions/fetching-products.md) returns the configured products, matched by identifier.
* **Local login uses a synthetic session** with no phone verification. With external login disabled and `.local` selected, no store-app handoff or external session request is needed. The storefront country is the one you set in the file (see `_storefront` below). `logout()` still clears it.
* **Purchases** present a fake Apple-Pay-style sheet (no real charge) and complete the transaction through the normal [payment queue](/sdk/core-concepts/payment-queue.md). Restorable products (non-consumables and subscriptions) are remembered locally; re-buying one shows an "already purchased" alert.
* **Restore** replays the locally remembered purchases.

{% hint style="info" %}
The standard OnsideKit APIs are unchanged — the same queue, observers, and transaction flow. This is the recommended way to exercise the purchase UI, including the [storefront/price-change gate](/sdk/purchasing/storefront-price-changes.md), without a backend.
{% endhint %}

### Limits of the simulation

Local product responses do not apply external-profile filtering, so `unsupportedProductIdentifiers` is always empty. Local testing does not verify real external-account restrictions, server authentication, Apple Pay processing, or receipt delivery. Exercise those using a real service session.

### Not supported in local testing

Two APIs require a real account and return a dedicated error:

* `Onside.presentPaymentMethodsManager(completion:)` → `.notSupportedInLocalTesting`
* `Onside.makeSignedInAppsHistoryRequest()` → `.notSupportedInLocalTesting`

## Reset between runs

```swift
Onside.resetLocalTestingState()
```

This clears the locally remembered purchase history so you can simulate a fresh install — restorable products are forgotten and re-buying one shows the full payment sheet again. It does not clear the fake login or the parsed products.

## The .storekit file

OnsideKit reads a subset of Apple's StoreKit configuration format.

```json
{
  "products": [
    {
      "productID": "remove_ads",
      "referenceName": "Remove Ads",
      "type": "NonConsumable",
      "displayPrice": "4.99",
      "localizations": [
        { "locale": "en_US", "displayName": "Remove Ads", "description": "Removes all ads." }
      ]
    }
  ],
  "subscriptionGroups": [
    {
      "id": "21F5C0E3",
      "name": "Premium",
      "localizations": [
        { "locale": "en_US", "displayName": "Premium", "description": "" }
      ],
      "subscriptions": [
        {
          "productID": "subscription_monthly",
          "referenceName": "Premium Monthly",
          "type": "RecurringSubscription",
          "displayPrice": "9.99",
          "recurringSubscriptionPeriod": "P1M",
          "localizations": [
            { "locale": "en_US", "displayName": "Premium Monthly", "description": "Monthly premium." }
          ]
        }
      ]
    }
  ],
  "settings": {
    "_locale": "en_US",
    "_storefront": "US"
  }
}
```

**Supported fields**

* `products[]`: `productID`, `referenceName`, `type` (`Consumable` → consumable; anything else → non-consumable), `displayPrice`, `localizations[]`.
* `subscriptionGroups[]`: `id`, `name`, `localizations[]`, `subscriptions[]`.
* `subscriptions[]`: `productID`, `referenceName`, `type`, `displayPrice`, `recurringSubscriptionPeriod` (ISO-8601 duration, e.g. `P1M`), `localizations[]`.
* `localizations[]`: `locale`, `displayName`, `description`.
* `settings`: `_locale` (preferred localization, default `en_US`) and `_storefront` (the testing storefront/country code — set this explicitly).

{% hint style="warning" %}
In local testing, all prices render in **EUR** — only the numeric value comes from `displayPrice`. Set `_storefront` to the ISO country code you want to test; the storefront country drives region-dependent behavior.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.onside.io/sdk/advanced-and-tooling/local-testing.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
