// storebound config for one app (profile "subscription-app": accounts plus auto-renewable subscriptions, an // analytics SDK, no ads). Every answer below is a starting point: edit it to match your app. Credentials NEVER go // here (env vars or .storebound/credentials.json, gitignored). Paths are relative to this file. // The type import is optional: it only works when storebound is installed in the project (bun add -d storebound). import type { ShipConfig } from 'storebound'; const config: ShipConfig = { app: { name: 'Example App', profile: 'subscription-app', copyright: '2026 Example Studio', privacyPolicyUrl: 'https://example.com/privacy', termsUrl: 'https://example.com/terms', // Terms of Use (EULA): in the app and the metadata (Apple 3.1.2) supportUrl: 'https://example.com/support', marketingUrl: 'https://example.com', website: 'https://example.com', contactEmail: 'support@example.com', }, // Accounts (account-deletion skill). Remove this block if the app has no sign-up / sign-in at all. // Apple 5.1.1(v): deletion must start inside the app. Google Play: in the app AND on a public web page. accounts: { enabled: true, deletionUrl: 'https://example.com/delete-account', // public page, no login needed to request deletion inApp: 'Settings > Account > Delete account', // goes into the App Review notes // How people sign up: 'username' (a name picked in the app, no password, no email), 'username-password', // 'email-password', 'email-link', 'phone', 'oauth', 'other'. `storebound scan` proposes the account data and // dataSafety.accountCreation from it; only the 'email-*' methods mean an email address. signUp: ['email-password'], }, // Sign-in for store reviewers (review-access skill). The demo account itself lives ONLY in // .storebound/credentials.json ("reviewDemoAccount") or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD. reviewAccess: { signInRequired: true, // false if every feature works without signing in instructions: 'Sign in with the demo account. Sample data is already there; nothing needs a second device.', }, // id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale languages: [ { id: 'en', play: 'en-US', apple: 'en-US' }, { id: 'tr', play: 'tr-TR', apple: 'tr' }, ], play: { packageName: 'com.example.app', defaultLanguage: 'en-US', listing: 'store/listing.md', icon: 'store/icons/play-512.png', }, apple: { bundleId: 'com.example.app', listing: 'store/appstore.md', // Export compliance (export-compliance skill): decide it, then uncomment. false ONLY if the app uses no encryption // beyond the OS (HTTPS through the system, standard sign-in). // usesNonExemptEncryption: false, // ipad: true, info: { categories: { primary: 'PRODUCTIVITY', secondary: 'UTILITIES' }, ageRating: {}, // every content question NONE / false; change what applies (user-generated content, web access ...) contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT', free: true, reviewNotes: [ 'Example App keeps lists and notes in sync across devices. Pro (yearly with a 1-week free trial, or monthly) unlocks unlimited lists, sharing and themes.', 'Subscriptions: Settings > Pro (or the paywall after the third list). Restore Purchases is on the paywall and in Settings > Pro.', 'Privacy policy and Terms of Use: on the paywall and in Settings > About.', ].join('\n\n'), }, }, // Auto-renewable subscriptions (store-subscriptions skill): `storebound apple subscriptions`, `storebound play // subscriptions`. Apple: one group, levels rank the plans (1 = highest). Play: one subscription per entry with one // auto-renewing base plan. Prices: USD base, Apple equalizes from the USA, Play converts per region; overrides set a // region by hand. The disclosure (price per period, renewal, how to cancel, Terms + Privacy links) belongs on the // purchase screen and in the descriptions. subscriptions: [ { name: 'Example Pro', listings: { en: { name: 'Example Pro' }, tr: { name: 'Example Pro' } }, subscriptions: [ { id: 'com.example.app.pro_yearly', period: 'P1Y', level: 1, usd: 29.99, listings: { en: { title: 'Pro yearly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] }, tr: { title: 'Pro yıllık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] }, }, intro: { type: 'free', duration: 'P1W' }, // 1 week free trial for new subscribers reviewScreenshot: 'store/iap-review/paywall.png', }, { id: 'com.example.app.pro_monthly', period: 'P1M', level: 2, usd: 3.99, overrides: [{ region: 'TR', currency: 'TRY', price: 99.99 }], listings: { en: { title: 'Pro monthly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] }, tr: { title: 'Pro aylık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] }, }, reviewScreenshot: 'store/iap-review/paywall.png', }, ], }, ], // Google Play Data safety. Types not listed = not collected. Typical app with accounts + an analytics SDK acting as // your processor (collected, not shared). Edit to match what your app and its SDKs really do. dataSafety: { collectsData: true, encryptedInTransit: true, deletionRequest: true, // Sign-up methods. Google does not publish these ids: compare with Play Console > App content > Data safety > // Export to CSV before the first import. The delete-account URL question that follows: add it under `extra` with // the exact id from that export, e.g. { question: '', value: 'https://example.com/delete-account' }. accountCreation: ['PSL_ACM_USER_ID_PASSWORD'], data: { PSL_EMAIL: { shared: false, collect: ['functionality', 'account'] }, PSL_USER_ACCOUNT: { shared: false, collect: ['functionality', 'account'] }, PSL_USER_GENERATED_CONTENT: { shared: false, collect: ['functionality'] }, PSL_USER_INTERACTION: { shared: false, collect: ['analytics'] }, PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] }, PSL_PERFORMANCE_DIAGNOSTICS: { shared: false, collect: ['analytics'] }, PSL_DEVICE_ID: { shared: false, collect: ['analytics'] }, PSL_PURCHASE_HISTORY: { shared: false, collect: ['functionality'] }, // subscription status tied to the account }, }, renders: 'store/renders', shots: { style: 'app', // calm gradient, device frame, headline + subline (game: sparkles and badges) copy: 'store/shots/copy.json', frames: 'store/shots/frames', // /phone/.png, /tablet/.png out: 'store/renders', // fonts: { title: 'Inter', body: 'Inter' }, // the app style default scenes: [ { id: 'home', palette: 'mist', frames: [{ src: 'home.png' }] }, { id: 'detail', palette: 'mint', frames: [{ src: 'detail.png' }] }, { id: 'share', palette: 'sky', frames: [{ src: 'share.png' }] }, { id: 'privacy', palette: 'slate', frames: [{ src: 'settings.png' }] }, ], feature: { frame: 'home.png', palette: 'mist' }, // Raw frames from a web build, the iOS Simulator (ios-sim) or an Android emulator (android-emu): sign in to a demo // account with realistic sample data, never real user data (store-screenshots skill). capture: { baseUrl: 'http://127.0.0.1:5173', query: 'lang={lang}&demo=1', shots: { 'home': { path: '/', delayMs: 1000, device: 'both' }, 'detail': { path: '/lists/1', delayMs: 1000, device: 'both' }, 'share': { path: '/lists/1/share', delayMs: 1000, device: 'both' }, 'settings': { path: '/settings', delayMs: 1000, device: 'both' }, }, }, }, icons: { composite: 'store/icon/icon.png', foreground: 'store/icon/foreground.png', background: 'store/icon/background.png', out: 'store/icons', }, }; export default config;