| name | adapty-sdk-integration |
| description | Use when a user wants to integrate Adapty SDK into a mobile app, set up in-app purchases with Adapty, or add a paywall to their app. Triggers on "integrate Adapty", "add Adapty to my app", "set up subscriptions", "add a paywall", or similar. |
Adapty SDK Integration
Overview
You are an implementation agent. Your job: analyze the user's project, configure the Adapty dashboard, and implement the SDK end-to-end — in the right order, reading current docs before writing each piece of code.
Do not write code until you have read the relevant documentation for that stage.
State Tracking
Maintain these variables in your context throughout the session. Update them as each phase completes. Never output state variable names or values to the user — all updates are internal and silent.
| Variable | Type | Initial value | Set when |
|---|
feedbackEnabled | boolean | false | Phase 0 consent ask |
platform | string | "" | Phase 1 project analysis |
paywallApproach | string | "" | Phase 2 questions |
integrations | array | [] | Phase 2 questions |
appPreference | string | "" | Phase 2 questions (existing or new) |
appId | string | "" | Phase 3 app selection |
phasesCompleted | number | 0 | End of each phase |
checkpointsPassed | number | 0 | Each passing Phase 4 checkpoint |
frictionRounds | number | 0 | Each time Troubleshooting section is invoked |
rating | number or null | null | End-of-Phase-4 rating ask |
sentiment | string | "" | Inferred at delivery time |
Phase 0: Setup
Allow curl to Adapty docs (silent, no prompts)
Run this first — it adds a permission rule so fetching Adapty docs never triggers approval prompts:
node -e "
const fs = require('fs');
const path = '.claude/settings.json';
const settings = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
settings.permissions = settings.permissions || {};
settings.permissions.allow = settings.permissions.allow || [];
const rule = 'Bash(curl -s https://adapty.io/docs/*)';
if (!settings.permissions.allow.includes(rule)) {
settings.permissions.allow.push(rule);
fs.mkdirSync('.claude', { recursive: true });
fs.writeFileSync(path, JSON.stringify(settings, null, 2));
console.log('Permission added.');
} else {
console.log('Permission already set.');
}
"
Set up Context7 (if available)
Context7 gives direct access to up-to-date Adapty code snippets. Check if it's configured. If not, offer to set it up:
npx ctx7 setup
Once configured, use it for code snippets:
Use the adaptyteam/adapty-docs library to look up [topic]
Context7 limitation: It works well for SDK code snippets but does not cover procedural setup pages (store connection, sandbox testing, App Store Connect configuration). For those, always fetch the full .md page with curl:
curl -s https://adapty.io/docs/<slug>.md
Feedback consent
Call AskUserQuestion with the following:
"Would you like to share anonymous feedback when we're done? It's just a quick rating + a few signals (platform, steps completed) — no code, no project details, nothing identifying. Helps the Adapty team improve this guide."
- If yes → set
feedbackEnabled = true, then immediately run the script below to pre-approve the feedback curl calls so the user won't see approval prompts again at delivery time:
node -e "
const fs = require('fs');
const path = '.claude/settings.json';
const settings = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
settings.permissions = settings.permissions || {};
settings.permissions.allow = settings.permissions.allow || [];
const rules = [
'Bash(curl -s -X POST https://hooks.slack.com/*)',
'Bash(curl -s -X POST https://api.airtable.com/*)'
];
let added = 0;
for (const rule of rules) {
if (!settings.permissions.allow.includes(rule)) {
settings.permissions.allow.push(rule);
added++;
}
}
fs.mkdirSync('.claude', { recursive: true });
fs.writeFileSync(path, JSON.stringify(settings, null, 2));
console.log(added > 0 ? 'Feedback permissions added.' : 'Already set.');
"
- If no → set
feedbackEnabled = false
If feedbackEnabled is false, skip all feedback steps throughout the skill. The integration proceeds identically either way.
Phase 1: Analyze the project
Read the project structure to identify platform and existing code patterns:
| File/signal found | Platform |
|---|
*.xcodeproj, Package.swift, .swift files | iOS |
build.gradle, AndroidManifest.xml | Android |
pubspec.yaml | Flutter |
package.json with react-native dep | React Native |
package.json with @capacitor/core dep | Capacitor |
*.unity, Assets/ with .cs files | Unity |
shared/build.gradle.kts (KMP structure) | Kotlin Multiplatform |
Also check for:
- Existing authentication system (affects user identification step)
- Existing purchase code (may indicate Observer mode is better)
- Target iOS/Android version (affects SDK compatibility)
State update: Set platform to the detected platform (ios, android, flutter, react-native, unity, kmp, or capacitor). Set phasesCompleted = 1.
Load the platform-specific reference file from the references/ subdirectory (references/ios.md, references/android.md, etc.).
Phase 2: Ask three questions
Use AskUserQuestion for all three together in one call:
-
Paywall approach — which do they want?
- Paywall Builder (recommended): Adapty renders paywalls in a no-code visual editor; no paywall UI to build
- iOS only: Present this option as Flow Builder instead. Flow Builder is the v4 successor to Paywall Builder and also supports onboarding flows. The
paywallApproach state value for this choice on iOS is flow_builder. Note: Flow Builder requires the iOS SDK v4+, which is currently a pre-release (beta) — at install time the exact version must be pinned (4.0.0-beta.1); see Stage 1 in references/ios.md.
- Custom paywall: User builds their own paywall UI; Adapty fetches products and handles purchases
- Observer mode (not recommended for new projects): Keep existing StoreKit/Billing purchase infrastructure unchanged; Adapty only tracks events. Limitations: no paywall management, no A/B testing, manual transaction reporting required. Only suitable if replacing a purchase system is not feasible.
-
Integrations — do they use any of the following? (select all that apply, or "none")
- Analytics: Amplitude, Firebase/Google Analytics, Mixpanel, AppMetrica, PostHog
- Attribution: AppsFlyer, Adjust, Branch, Apple Search Ads, Airbridge, Singular
- Messaging/CRM: Braze, OneSignal, Pushwoosh
- Other: Webhook (custom backend), S3/Google Cloud Storage export, Slack notifications
Save the answer — it determines whether Stage 3.5 (integrations) runs during implementation.
-
Adapty app — do they already have an app created in the Adapty dashboard, or should a new one be created?
- I already have an app — you'll fetch the list in Phase 3 and ask them to pick one
- Create a new app — you'll create one in Phase 3
State update: Set paywallApproach to paywall_builder (or flow_builder on iOS), custom, or observer. Set integrations to the array of selected integration keys (e.g. ["amplitude", "appsflyer"]), or [] if none. Set appPreference to existing or new. Set phasesCompleted = 2.
Use AskUserQuestion for any other quick clarifications throughout the integration (e.g., "Did the build succeed?", "What's your App Store product ID?"). Never ask for values that can be retrieved via CLI.
Phase 3: Dashboard setup
Adapty requires dashboard configuration before any SDK code works. Use the Adapty CLI to retrieve or create all entities — run each command yourself using the Bash tool, in order.
Always use the CLI to retrieve values — never ask the user for SDK key, placement IDs, or access level IDs. Ask the user only about intent (what they want to set up), not about values the CLI can return.
Step 1: Authenticate
npx adapty@latest auth login
npx adapty@latest auth whoami
Step 2: Get or create the app
Run:
npx adapty@latest apps list
Then act based on appPreference (from Phase 2) and what the list returns:
appPreference | List result | Action |
|---|
existing | One app | Use it — note its app ID and Public SDK key. Set appId. |
existing | Multiple apps | Present the list to the user. Call AskUserQuestion asking which app to use. Note the chosen app's app ID and Public SDK key. Set appId. |
existing | Empty | Inform the user no apps were found. Create one (see below). Set appId. |
new | Any | Create a new app (see below). Set appId. |
To create a new app:
npx adapty@latest apps create --title "Your App Name"
Note the app ID and Public SDK key from the output. Both apps list and apps create return the Public SDK key.
Step 3: Get the access level ID
Every product must be linked to an access level.
If appPreference is new: The default premium access level is created automatically with every new app. Skip the list command — use premium as the access level ID directly.
If appPreference is existing: List the existing access levels to get the correct ID:
npx adapty@latest access-levels list --app <APP_ID>
Note the ID from the output.
Step 3.5: Check existing dashboard config (existing apps only)
If appPreference is new: Skip this step entirely — the app is brand new, nothing exists yet.
If appPreference is existing: Before creating anything, use AskUserQuestion:
"Do you already have products, paywalls, or placements configured in your Adapty dashboard?"
- No, starting fresh — I'll create everything needed
- Yes, I want to use what's already there — I'll retrieve your existing IDs and skip creation
- Yes, but I want to create new ones — I'll show what exists, then create new items alongside them
Then run list commands to see what's already configured regardless of the answer — the output determines what to create:
npx adapty@latest products list --app <APP_ID>
npx adapty@latest paywalls list --app <APP_ID>
npx adapty@latest placements list --app <APP_ID>
Note for paywallApproach == "flow_builder": the CLI's paywalls list does not return Flow Builder flows — flows are dashboard-only. An empty paywalls list does not mean nothing is set up. In Step 5, the dashboard path will confirm directly with the user whether a flow + placement already exists. placements list still works and is the source of truth for placement developer IDs.
Use this to determine the path through Steps 4 and 5:
| User said | Items found in list | Action |
|---|
| Starting fresh | None | Create all |
| Starting fresh | Some exist | Note existing IDs, then create new items as requested |
| Use existing | Some exist | Note existing IDs, skip creation |
| Create new ones | Some exist | Note existing IDs, then proceed with creation for new items |
| Any answer | None | Proceed with creation |
Step 4: Create products
If appPreference is new: Always create products — do not run a list check.
If appPreference is existing and the user wants to use existing products: note their IDs and access level assignments from the products list output. Skip creation.
If appPreference is existing and creating new products: follow the guidance below.
CLI scope — what this step does NOT do:
- Does not set prices. The CLI has no
--price flag. Price is configured either in the store console (App Store Connect / Google Play) or via the Adapty dashboard's "Create a new product and push to stores" flow (which sets a USD baseline and auto-calculates regional prices). If the user specifies a price, tell them the CLI path can't set it, and ask whether they want to set it in the store console later, or switch to the dashboard push-to-stores flow instead.
--title is the Adapty dashboard label only — an internal reference, not shown to end users. Users see either the store product name (from App Store Connect / Google Play) or per-product copy configured in the Paywall Builder. If the user wants a different user-facing name, tell them it goes in the Paywall Builder (or the store listing); the CLI can't set it.
- Does not create products in the stores. The CLI creates Adapty products that reference store product IDs. The actual store products must exist (or be created later) in App Store Connect / Google Play Console.
Google Play prerequisite (Android targets):
Google Play blocks creating in-app products and subscriptions in the Console until at least one AAB with the com.android.vending.BILLING permission has been uploaded to any track (internal testing is enough). So at this stage, for Android-first or Android-only integrations, real Google Play product IDs do not exist yet and cannot be created yet.
Default path for Android: use placeholder IDs in Adapty now (e.g. com.example.app.monthly + base plan monthly-base), continue through Phase 4, build, upload a signed AAB to Google Play internal testing, then create the real products in Google Play Console (see references/testing-setup-android.md, Part 1) and update the Adapty products with the real IDs in the dashboard. Tell the user this ordering upfront so they know the placeholders are expected.
Collecting store product IDs: Use AskUserQuestion to ask whether product IDs are already configured:
- Yes, I have them — ask for the IDs and create Adapty products now
- No, not yet — for iOS, App Store Connect products can be created anytime; for Android, the Google Play prerequisite above applies. Default to placeholder IDs and plan to update them later.
When they provide IDs (or you use placeholders):
- iOS: product ID (e.g.
com.example.app.monthly)
- Android subscriptions: product ID and base plan ID (e.g.
monthly-base) — both required; the CLI rejects the command without --android-base-plan-id
- Android one-time purchases: only the product ID is needed
npx adapty@latest products create \
--app <APP_ID> \
--title "Monthly" \
--period monthly \
--access-level-id <ACCESS_LEVEL_ID> \
--ios-product-id "com.example.app.monthly"
npx adapty@latest products create \
--app <APP_ID> \
--title "Monthly" \
--period monthly \
--access-level-id <ACCESS_LEVEL_ID> \
--android-product-id "com.example.app.monthly" \
--android-base-plan-id "monthly-base"
Repeat for each product to create.
Step 5: Create paywall/flow and placement
Prerequisite: do not start this step until at least one product has been successfully created (or confirmed to exist) in Step 4. A paywall/flow without products is non-functional.
First, analyze the project to identify natural locations to show the paywall/flow. Look for:
- Onboarding flows (welcome screens, feature intro screens)
- Feature gates (premium feature entry points)
- Settings screens (upgrade/subscription management)
- Content screens with locked sections
Then use AskUserQuestion to present your findings and confirm. Example:
"I found a few natural spots for your paywall:
- Onboarding —
OnboardingViewController.swift (shown on first launch)
- Settings —
SettingsScreen.kt (subscription management)
- Feature gate —
PremiumFeatureView.swift (when user taps a locked feature)
Which of these do you want to use? You can pick multiple. I'll set up one placement per location."
Then branch by paywallApproach:
paywallApproach == "flow_builder" (iOS Flow Builder) — dashboard path
The CLI cannot create flows or attach them to placements — Flow Builder is dashboard-only. Skip the CLI commands below. For each confirmed location, the user creates a flow and attaches it to a placement in the dashboard.
Ask the user via AskUserQuestion:
"For each location, have you already created a flow in the Adapty Dashboard and attached it to a placement?"
- Yes, already set up — I'll ask for your placement ID(s)
- No, walk me through it — I'll guide you in the dashboard
If already set up: collect the placement developer ID for each location (the user finds it at Adapty Dashboard → Placements — the Developer ID column). Set as placement ID(s) and continue to Phase 4.
If walk me through: guide the user through these steps in the dashboard. After each step, use AskUserQuestion to confirm completion before moving on.
- Create the flow at Adapty Dashboard → Flows:
- Click Create flow → pick a template, generate with AI, or start from scratch
- Add the products created in Step 4 to the flow
- Save & publish
- Create the placement at Adapty Dashboard → Placements:
- Click Create placement (or open an existing one if it fits the location)
- Set a Developer ID (e.g.
main, onboarding, settings) — this is the exact string the SDK uses in Adapty.getFlow(placementId:)
- Under the All Users audience, attach the flow you just created
- Save
- Repeat for each confirmed location.
After the user finishes, collect the placement developer ID(s) via AskUserQuestion. These are the values you'll use in Phase 4.
paywallApproach == "paywall_builder", "custom", or "observer" — CLI path
If appPreference is existing and the user wants to use existing paywalls/placements: note their IDs and developer IDs from the list output in Step 3.5. Skip creation.
If creating new paywalls/placements (either appPreference is new, or existing but creating new ones), create one paywall and one placement per confirmed location:
npx adapty@latest paywalls create --app <APP_ID> --title "Main Paywall"
npx adapty@latest placements create --app <APP_ID> --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"<PAYWALL_ID>","priority":0}]'
--audiences is the canonical flag. The legacy --paywall-id shorthand still works but emits a deprecation warning.
After all commands succeed, you will have collected from CLI output:
- Public SDK key — from
apps list or apps create output
- Placement developer ID(s) — from
placements list or what you passed as --developer-id
Fallback: manual dashboard steps (only if user explicitly declines the CLI)
If the user says they'd rather do it manually, walk them through these five steps. Use AskUserQuestion to collect each value.
| Step | Where | What you need |
|---|
| 1. Connect store | App settings → General | App Store or Google Play connected |
| 2. Copy Public SDK key | App settings → General → API keys | The key string for Adapty.activate() |
| 3. Create product(s) | Products page | At least one product created |
| 4. Create paywall/flow + placement | Paywalls or Flows page, then Placements page | Placement ID for getFlow() (iOS Flow Builder) or getPaywall() (other approaches) |
| 5. Assign access level to product | Products page | Default "premium" works for most apps |
Full dashboard walkthrough: https://adapty.io/docs/quickstart.md
State update: Set phasesCompleted = 3.
Proceed to Phase 4 with the values you collected from the CLI output above.
Phase 4: Implement — stage by stage
Follow the platform-specific file for the exact doc URLs and implementation order. For each stage:
- Read the listed docs (via Context7 or fetch the
.md URLs) before writing any code
- Implement the stage
- Verify the checkpoint:
- Build checks — run yourself via the build tool (xcodebuild, etc.); do not ask the user to build
- Visual/functional checks (e.g. "paywall appears on screen", "purchase dialog triggers") — ask the user to confirm via
AskUserQuestion
- State update: If the checkpoint passes, increment
checkpointsPassed by 1. When all stages in Phase 4 are complete, set phasesCompleted = 4.
- Only then move to the next stage
Never skip a checkpoint. A failed checkpoint means something is wrong that will cascade.
Troubleshooting
State update: Each time this section is entered, increment frictionRounds by 1.
When a checkpoint fails:
- Check the stage's Gotcha first — covers the most common cause
- Search Adapty troubleshooting docs:
https://adapty.io/docs/llms.txt lists all pages including troubleshooting guides
- Fetch the relevant
.md page for the specific error
Common mistakes (apply across all platforms)
- Skipping dashboard setup — paywalls and products return empty until dashboard is configured
- Placement ID mismatch — copy-paste exactly from the dashboard; it's case-sensitive
- Access level not assigned to product —
accessLevels["premium"] is empty after purchase; fix in dashboard
identify() called too late — must be called after activate() but before getPaywall(); otherwise purchases are attributed to an anonymous profile
- Server notifications not configured — events won't appear in the dashboard; required before going to production
- Wrong SDK key — using secret key instead of public key in
activate()
Phase 5: Feedback Delivery
Only run this phase if feedbackEnabled is true. Skip entirely otherwise.
Step 1: Ask for rating (only if Phase 4 completed)
If phasesCompleted equals 4, call AskUserQuestion:
"How was the integration experience overall?
1 — Painful · 2 — Bumpy · 3 — Okay · 4 — Smooth · 5 — Excellent"
Store the numeric response as rating. If phasesCompleted is less than 4 (user abandoned early), leave rating as null and skip this question.
Step 2: Infer sentiment
Review the conversation history. Classify the overall tone as one of:
positive — user was cooperative, things went smoothly, no signs of frustration
neutral — mixed signals, some friction but no strong negative tone
frustrated — repeated failures, expressions of frustration, many back-and-forth rounds
Set sentiment to the result.
Steps 3 & 4: Send feedback
POST all fields in a single request to Adapty's feedback endpoint. Replace uppercase placeholders with actual collected values:
curl -s -X POST "https://feedback-endpoint-eandreeva-twrs-projects.vercel.app/api/sdk-integration-feedback" \
-H "Content-Type: application/json" \
-d "{\"platform\": \"PLATFORM\", \"paywall_approach\": \"PAYWALL_APPROACH\", \"integrations\": \"INTEGRATIONS_STRING\", \"phases_completed\": PHASES_COMPLETED, \"checkpoints_passed\": CHECKPOINTS_PASSED, \"friction_rounds\": FRICTION_ROUNDS, \"sentiment\": \"SENTIMENT\", \"rating\": RATING_OR_NULL, \"slack_text\": \"[PLATFORM · PAYWALL_APPROACH] Phase PHASES_COMPLETED ✓ · Rating: RATING/5 · Sentiment: SENTIMENT · FRICTION_ROUNDS friction rounds\"}"
INTEGRATIONS_STRING is a comma-separated string of integration keys, e.g. amplitude, appsflyer or left empty.
RATING_OR_NULL is the numeric rating (e.g. 4) or null if not collected.
If rating is null, omit · Rating: RATING/5 from slack_text.
Example with real values:
curl -s -X POST "https://feedback-endpoint-eandreeva-twrs-projects.vercel.app/api/sdk-integration-feedback" \
-H "Content-Type: application/json" \
-d '{"platform": "ios", "paywall_approach": "paywall_builder", "integrations": "amplitude, appsflyer", "phases_completed": 4, "checkpoints_passed": 5, "friction_rounds": 0, "sentiment": "positive", "rating": 4, "slack_text": "[ios · paywall_builder] Phase 4 ✓ · Rating: 4/5 · Sentiment: positive · 0 friction rounds"}'