Godot Setup

godot-iap is a Godot 4.x plugin for in-app purchases following the OpenIAP specification. It uses Swift GDExtension for iOS and Kotlin AAR for Android.

Prerequisites#

  • Godot 4.3 or higher
  • iOS: Xcode 16+ (Swift 6.0+) with iOS 17+ target
  • Android: Android SDK with API level 24+

Installation#

Download from Releases (Recommended)#

  1. Download the latest godot-iap-{version}.zip from GitHub Releases
  2. Extract and copy addons/godot-iap/ to your project's addons/ folder
  3. Enable the plugin in Project > Project Settings > Plugins

The zip includes pre-built binaries for both iOS and Android.

Build from Source#

If you need to build from source:

sh
git clone https://github.com/hyodotdev/openiap.git
cd openiap/libraries/godot-iap

# Build for iOS
make ios

# Build for Android
make android

# Copy addons/godot-iap/ to your project

The checked-in macOS runtime frameworks are Apple Silicon (arm64) only. Custom source builds can override MACOS_ARCHS; make macos requests arm64 x86_64 by default, and generated metadata should only include architectures that the framework binaries actually contain.

iOS Framework Toolchain (Offer Codes)#

The pre-built iOS framework locks in Apple API availability at compile time. One feature depends on this: offer code redemption only returns a verified result when the framework was built with Xcode 27 or later and runs on iOS 27, Mac Catalyst 27, or visionOS 27 or later. Older runtimes still use the non-result system-sheet path even when the framework was built with Xcode 27. A framework built with Xcode 26 also uses that older path, including on Apple 27 devices. The published godot-iap 3.0.0 framework is built with Xcode 27. If you build from source and use offer codes, build with Xcode 27 or later; the runtime requirement remains separate.

macOS: Damaged Framework Warning#

The default release zip does not include macOS runtime frameworks, so most projects can skip this section. It applies only if you build from source with macOS support or use a release or custom zip containing addons/godot-iap/bin/macos. If Godot reports that GodotIap.framework or SwiftGodotRuntime.framework is damaged, clear quarantine and repair the ad-hoc signature:

sh
# Run from your Godot project root after copying addons/godot-iap
xattr -dr com.apple.quarantine addons/godot-iap
codesign --force --deep --sign - --timestamp=none addons/godot-iap/bin/macos/SwiftGodotRuntime.framework
codesign --force --deep --sign - --timestamp=none addons/godot-iap/bin/macos/GodotIap.framework

Platform Setup#

Godot Export Presets#

For both iOS and Android, enable the plugin in your export presets:

  1. Project > Export > Add > iOS (or Android)
  2. Configure your export settings (Bundle Identifier, Team ID, etc.)
  3. Enable GodotIap in the Plugins section

iOS: Xcode Framework Embedding#

The GodotIap export plugin registers the iOS frameworks during export so they are added to Xcode's Embed Frameworks build phase automatically. Before exporting, make sure GodotIap is enabled in the iOS export preset's Plugins section.

If you exported with an older plugin version, or if Xcode still shows the frameworks as file references instead of framework bundles, run the post-export fixer from your Godot project root:

sh
IOS_EXPORT_DIR=/path/to/ios-export \
  ./addons/godot-iap/scripts/fix_ios_embed.sh

The script finds the exported .xcodeproj, embeds:

  • GodotIap.framework
  • SwiftGodotRuntime.framework

It also converts framework file references to framework bundles and restores any missing framework Info.plist files.

Manual Xcode fallback
  1. Open the exported .xcodeproj in Xcode
  2. Select your target > General tab
  3. Scroll to Frameworks, Libraries, and Embedded Content
  4. Click + and add:
    • GodotIap.framework
    • SwiftGodotRuntime.framework
  5. Set both to "Embed & Sign"

The frameworks are located at:

[exported_project]/addons/godot-iap/bin/ios/GodotIap.framework
[exported_project]/addons/godot-iap/bin/ios/SwiftGodotRuntime.framework

Then confirm the runpath:

  1. Go to the Build Settings tab
  2. Search for "Runpath Search Paths" (LD_RUNPATH_SEARCH_PATHS)
  3. Add @executable_path/Frameworks if not already present

iOS: Missing Info.plist Fallback#

Due to a Godot export bug, some exports may omit Info.plist files inside embedded frameworks. The fix_ios_embed.sh script above copies the missing files automatically.

If you still need an Xcode build phase fallback:

  1. Select your target > Build Phases tab
  2. Click + > New Run Script Phase
  3. Name it "Copy Framework Info.plist"
  4. Paste the following script:
sh
# Copy missing Info.plist files for GodotIap frameworks
ADDONS_DIR="${PROJECT_DIR}"
FRAMEWORKS_DIR="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"

if [ -f "${ADDONS_DIR}/addons/godot-iap/bin/ios/GodotIap.framework/Info.plist" ]; then
    cp "${ADDONS_DIR}/addons/godot-iap/bin/ios/GodotIap.framework/Info.plist" \
       "${FRAMEWORKS_DIR}/GodotIap.framework/" 2>/dev/null || true
fi

if [ -f "${ADDONS_DIR}/addons/godot-iap/bin/ios/SwiftGodotRuntime.framework/Info.plist" ]; then
    cp "${ADDONS_DIR}/addons/godot-iap/bin/ios/SwiftGodotRuntime.framework/Info.plist" \
       "${FRAMEWORKS_DIR}/SwiftGodotRuntime.framework/" 2>/dev/null || true
fi
  1. Drag this script phase before the "Embed Frameworks" phase

Prefer the post-export fixer when possible; the build phase is only a fallback for projects that cannot run the script after export.

Scene Setup#

The recommended way to use GodotIap is to add GodotIapWrapper as a child node:

  1. Open your main scene (or create an autoload scene for IAP management)
  2. Add a new node: Add Child Node > Node
  3. Attach the GodotIapWrapper script: Load > addons/godot-iap/godot_iap.gd
  4. Name it GodotIapWrapper
  5. Reference it in your script using @onready var iap = $GodotIapWrapper

Or create the wrapper node programmatically:

gd
extends Node

const Types = preload("res://addons/godot-iap/types.gd")

var iap

func _ready():
    # Create wrapper node dynamically
    var wrapper = preload("res://addons/godot-iap/godot_iap.gd").new()
    wrapper.name = "GodotIapWrapper"
    add_child(wrapper)
    iap = wrapper

    # Now use iap as normal
    iap.connected.connect(_on_connected)
    iap.init_connection()

Verify Installation#

With the GodotIapWrapper node from Scene Setup in place, attach this script to confirm the plugin loads and the store connects:

gd
extends Node

const Types = preload("res://addons/godot-iap/types.gd")

@onready var iap = $GodotIapWrapper

func _ready():
    print("Godot IAP is available!")

    # Connect signals
    iap.connected.connect(_on_connected)
    iap.purchase_updated.connect(_on_purchase_updated)
    iap.purchase_error.connect(_on_purchase_error)

    # Initialize connection
    var success = await iap.init_connection()
    print("Init result: ", success)

func _on_connected():
    print("Store connected!")

func _on_purchase_updated(purchase: Dictionary):
    print("Purchase: %s" % purchase.get("product_id", ""))

func _on_purchase_error(error: Dictionary):
    print("Error: ", error)

Usage#

The typical flow is init_connection → connect the purchase_updated and purchase_error signals → fetch_products request_purchase finish_transaction. See the Purchase Guide for the complete flow.

The examples below use the iap reference created in Scene Setup (@onready var iap = $GodotIapWrapper).

GDScript uses snake_case for all function names (init_connection, fetch_products, request_purchase). Return types use Array for lists and Variant for platform-specific single results.

Signal-Based Architecture#

Godot IAP uses signals for purchase events, following Godot's native event pattern:

gd
const Types = preload("res://addons/godot-iap/types.gd")

func _ready():
    # Connect signals
    iap.purchase_updated.connect(_on_purchase_updated)
    iap.purchase_error.connect(_on_purchase_error)

    # Initialize
    if not await iap.init_connection():
        push_error("Store connection failed")
        return

func _on_purchase_updated(purchase):
    # Validate receipt with your backend or IAPKit, then:
    # (second argument: true only for consumable products)
    await iap.finish_transaction(purchase, false)
    print("Purchased: ", purchase.product_id)

func _on_purchase_error(error):
    print("Error: ", error.code, " - ", error.message)

Each call here has a full reference — see the purchase_updated and purchase_error signals, init_connection, and finish_transaction for parameters and per-store behavior, and Error Codes for the error.code values.

Fetching Products#

Query store metadata with a ProductRequest; results are platform-typed (ProductAndroid on Android, ProductIOS on iOS):

gd
func _load_products():
    var request = Types.ProductRequest.new()
    request.skus = ["premium", "coins_100"]
    request.type = Types.ProductQueryType.InApp

    var products: Array = await iap.fetch_products(request)
    for product in products:
        # On Android: Types.ProductAndroid
        # On iOS: Types.ProductIOS
        print(product.id, " - ", product.display_price)

See fetch_products for parameters and per-store behavior.

Making a Purchase#

Configure both platforms in a single request — the plugin picks the branch matching the store the game is running on, so one purchase call works everywhere:

gd
func _purchase(sku: String):
    var platforms = Types.RequestPurchasePropsByPlatforms.new()
    platforms.apple = Types.RequestPurchaseIosProps.new()
    platforms.apple.sku = sku
    platforms.google = Types.RequestPurchaseAndroidProps.new()
    var skus: Array[String] = [sku]
    platforms.google.skus = skus
    var props = Types.RequestPurchaseProps.in_app(platforms)
    # Returns Variant (PurchaseAndroid or PurchaseIOS, or null)
    var purchase = await iap.request_purchase(props)

See request_purchase for parameters and per-store behavior.

Troubleshooting#

Products not found#

  • Ensure all agreements are signed in App Store Connect / Google Play Console
  • Verify banking, legal, and tax information is complete and approved
  • Check that bundle ID / package name matches exactly
  • Products must be in "Ready to Submit" status (Apple) or "Active" (Google)
  • Wait 15-30 minutes after creating products before testing

App crashes on launch (iOS)#

A crash with Library not loaded: @rpath/GodotIap.framework/GodotIap means the frameworks were not embedded — see iOS: Xcode Framework Embedding and run fix_ios_embed.sh.

GDExtension errors in the desktop editor#

The current release zip ships an iOS-only GDExtension, so on Godot 4.8-dev3 and older the editor on every desktop — Windows, Linux, and macOS alike — logs No GDExtension library found for current OS and architecture each time the project is scanned. The messages stop nothing: Android loads the AAR plugin from addons/godot-iap/android/, and iOS exports still embed and load the frameworks.

The zip's .gdextension declares include_tags = ["ios"] so Godot can skip it silently. Engine support for that filter (godotengine/godot#121575) first shipped in 4.8-dev4; on 4.8-dev3 and older — including 4.3 through 4.7 — the messages cannot be suppressed (godotengine/godot#105615). To silence them while you are not exporting for iOS, rename the file:

sh
mv addons/godot-iap/bin/godot_iap.gdextension \
   addons/godot-iap/bin/godot_iap.gdextension.disabled

Restore the name before an iOS export, on any machine: Godot discovers and loads the native extension through that file. Skip the rename if your copy carries bin/macos — a source build, or the 3.3.2 and 3.3.3 zips — since a macOS editor loads that library.

Next Steps#