Skip to main content
Apple reviews change. This page reduces the patterns reviewers cite most under Guideline 4.3; it does not guarantee approval. Treat it as a checklist, not a legal opinion.

What 4.3 actually says

Apple’s Guideline 4.3 (Spam) has two parts. 4.3(a) says don’t ship multiple Bundle IDs of the same app. 4.3(b) says don’t pile into a saturated category without a unique, high-quality experience. There’s a related rule, 4.2.6, about apps “created from a commercialized template or app generation service.” Reviewers don’t always cite the sub-letter. When they say “4.3,” they usually mean one of three things they noticed:
  1. Your binary, listing, and screenshots look too similar to another app already on the store.
  2. Your product is functionally identical to a known template (same screens, same flows, same strings).
  3. The category is saturated and your app doesn’t show a clear reason to exist.

Shared architecture is fine. Undifferentiated product is not.

Thousands of apps share SwiftUI, RevenueCat, Supabase, and OpenRouter. Apple knows this. What gets flagged is when the product, listing, and binary fingerprint all look like a copy of something else. Shared architecture alone does not guarantee approval — differentiation is product + listing + binary. Your job before submitting:
  • Keep the architecture (it’s yours now anyway).
  • Replace every user-visible string, asset, and flow that screams “boilerplate.”
  • Lead your App Store listing with your hero feature, not a generic chat screen.
  • Strip dead code that ships in the binary and exposes the boilerplate’s name.

What v2.2.0 ships to help you

v2.2.0 makes 4.3(a) hardening a first-class, automatable workflow instead of a manual grep:

scripts/appstore-43-audit.sh

Builds a Release binary, scans the main executable, every app-owned bundle file, and bundled legal/demo markdown for starter fingerprints, then exits non-zero on blocking app-owned hits (and until you replace the starter BrandConfig.appDisplayName, set production legal URLs, and rename the upstream bundle ID).

template.manifest.json

Machine-readable single source of truth at the repo root: identity surface, module graph (removable + couplings), config schema, the canonical fingerprint list, differentiation rules, recipes, and verification commands. The audit script reads its fingerprint list from here so the script, CI, and agent prompts never diverge.

docs/checklists/APP_STORE_4_3_HARDENING.md

The full playbook: binary audit, branding/identity map, metadata, App Review Notes template, and per-module removal notes (including coupling rules).

docs/prompts/AppStore4_3Hardening.prompts.md

Prompt packs for your coding agent, including the must-run starter-fingerprint cleanup (SwiftAI, Boilerplate, DesignSystem/SAI).
The App Generator (a separate, standalone macOS product, coming soon) automates this entire pass — it drives your own coding-agent CLI to generate a differentiated, 4.3-safe app from the template. It ships no LLM and is not bundled in the boilerplate. The steps below are the manual workflow you can run today on any copy of the source.

The steps to take before you hit “Submit”

1

Run the automated Release audit first

Before anything manual, run the bundled audit. It builds a Release configuration, scans the binary and app bundle, and fails on blocking app-owned fingerprints.
# From your distribution repo root
bash scripts/appstore-43-audit.sh
The upstream starter is expected to fail until you configure app-specific branding, legal URLs, backend URLs, and bundle identity. The script reports four buckets: main-executable risky strings, app-owned bundle risky strings, bundled legal/demo markdown, and configuration readiness. Third-party SDK noise is reported separately and is non-blocking. Re-run it until every app-owned hit is gone or intentionally documented.
2

Run a Release `strings` audit on the .ipa

The audit script does this for you, but if you want to inspect the binary by hand, reviewers’ automated checks compare binary fingerprints. Anything that ships in your Release .ipa can match boilerplate fingerprints.
# Build a Release archive in Xcode, then export the .ipa
unzip -o YourApp.ipa -d /tmp/yourapp
strings -a /tmp/yourapp/Payload/YourApp.app/YourApp \
  | grep -i -E "swiftai|boilerplate|designsystem/sai|echollm|mockauth" \
  | sort -u
The canonical fingerprint list lives in template.manifest.json under fingerprints.patterns. If the output contains the boilerplate’s product name, the SAI design-system prefix, dev-only client names, or stub strings, fix the source and rebuild. Don’t ship an .ipa whose strings give away its origin.
3

Remove dead Swift files from the app target

Files that aren’t referenced by your app’s UI but are still in the app target still compile into the binary. That includes example views, sample data, and demo flows you replaced.Open Xcode → Project navigator → check Target Membership for every file in Features/, Examples/, and any *Sample*.swift. Anything you don’t use, remove from the app target (you can keep the file on disk if you want it for reference).See the per-module removal notes in docs/checklists/APP_STORE_4_3_HARDENING.md in your distribution repo. Removing a module without pruning its couplings leaves orphaned code paths in the binary — see the module-coupling step below before deleting FeatureChat, AI, or Payments.
4

Rebrand every user-visible string

Search the codebase for the boilerplate’s product name and replace it everywhere a user can see it: launch screen, onboarding, settings, paywall, push copy, error toasts, support email, About screen, legal pages.
# In your project root
rg -i "swiftai|boilerplate" \
  --type swift --type plist \
  --glob '*.strings' \
  --glob '!**/Tests/**' --glob '!**/.build/**'
Build your branding map before you start: old name → new name, old bundle ID → new bundle ID, old support URL → new support URL. Apply it once. Don’t leave half-renamed files.The boilerplate’s design-system types use an SAI prefix (for example SAIGlass), which surfaces in the binary as DesignSystem/SAI. Rebrand the prefix with the bundled script — dry-run first, then apply after committing your work:
bash scripts/rebrand-design-system-prefix.sh SAI YourAppPrefix --dry-run
bash scripts/rebrand-design-system-prefix.sh SAI YourAppPrefix --apply
5

Pick a hero archetype and lead with it, not the chat tab

The boilerplate ships a chat-first home. If your app is a parking helper, a meal planner, or a study tool, chat is not your hero. Reviewers see five other AI-chat apps a day; if your first screenshot is “AI chat with bubbles,” you’re invisible.Differentiation here is enforced by mandatory archetype selection, not randomness. template.manifest.json flags differentiation.archetypeRequired, and the repo ships hero recipes in docs/recipes/hero/ to rebuild the home screen around a distinct primary experience:
  • DashboardGrid.md — metrics/cards home
  • FeedTimeline.md — scrolling feed
  • MapCanvas.md — map-centric UI
  • GuidedTaskFlow.md — step-by-step flow
  • ConversationalHome.md — chat-first (only if chat genuinely is your product)
First screenshot: the screen that shows what your app actually does (parking spot map, meal of the day, flashcard review). Second screenshot: the differentiated outcome. Chat, if it appears at all, comes after.
6

Read the module removal notes before you delete anything

Removing a module without its couplings leaves dangling symbols in the binary, which reviewers’ tooling can fingerprint. The biggest gotcha:
LLMClient and LLMMessage live in FeatureChat, and the AI package re-exports FeatureChat (@_exported import FeatureChat in Packages/AI/Sources/AI/AI.swift). You cannot delete FeatureChat alone without first moving LLMClient / LLMMessage into AI (or Core) and dropping the FeatureChat dependency from Packages/AI/Package.swift. Otherwise Packages/AI stops compiling.
If you remove Payments, you also need to remove paywall presentation hooks from FeatureSettings (which depends on Payments). The full per-module table, including which symbols to grep for and what to delete, is in docs/checklists/APP_STORE_4_3_HARDENING.md. Pair it with the buyer-facing prompts in docs/prompts/AppStore4_3Hardening.prompts.md.

Pre-submit checklist

  • bash scripts/appstore-43-audit.sh reports PASS (no blocking app-owned hits)
  • strings on the Release .ipa returns no swiftai, boilerplate, designsystem/sai, or stub matches
  • Bundle ID is yours, not the boilerplate’s com.berkin.SwiftAIBoilerplatePro
  • No demo or sample files are in the app target’s Compile Sources
  • Removed modules have their couplings pruned (see module table)
  • Branding map applied: name, support email, URLs, paywall copy
  • Onboarding pages reflect your product, not generic AI-chat copy
  • Settings → About shows your company, your URLs, your version
  • Push notifications and email templates use your wording
  • First screenshot leads with your hero, not the chat screen
  • App name and subtitle describe your product, not “AI chat”
  • Description’s first line answers “what does this app do for me”
  • Privacy policy and terms hosted at your domain (not boilerplate’s)
  • You explain in plain language what the app does and who it’s for
  • You credit the architecture honestly if asked, while making clear the product, content, and listing are yours
  • Demo account credentials work and have realistic data

Example LLM cleanup prompt

Must-run starter fingerprint prompt

If the Release audit reports SwiftAI, Boilerplate, or DesignSystem/SAI, run this first. These are starter fingerprints and should not remain in app-owned Release output.
I am preparing an iOS app derived from SwiftAI Boilerplate Pro for App Store submission. The Release audit still reports these blocking app-owned fingerprints:

- SwiftAI
- Boilerplate
- DesignSystem/SAI

My app details:
- App name: [FILL IN]
- Bundle identifier: [FILL IN]
- Design-system prefix to use instead of SAI: [FILL IN, e.g. MYA]
- Primary app purpose: [FILL IN]

Tasks:
1. Search the production app target, linked local packages, app extension targets, generated configuration, launch storyboard, asset names, Info.plist values, xcodeproj settings, and package resources for exact matches of `SwiftAI`, `Boilerplate`, and `DesignSystem/SAI`.
2. Replace app-owned product identity strings with my app-specific name and bundle identifier. Do not rename upstream docs, changelog entries, or comments unless they are bundled into the app or compiled into Release.
3. Rename the design-system source prefix/path references that cause `DesignSystem/SAI` to appear in Release. Use the repo's rebrand script if available (positional args: old prefix, new prefix, mode):
   `bash scripts/rebrand-design-system-prefix.sh SAI [PREFIX] --apply`
4. Verify no dead demo/showcase Swift files or bundled markdown resources still compile into the app target.
5. Run:
   `bash scripts/appstore-43-audit.sh`
6. If the audit still fails, classify every remaining hit as:
   - intentional upstream template/docs
   - DEBUG/test-only
   - third-party SDK noise
   - still-blocking production/app-owned

Constraints:
- Keep useful boilerplate features unless I explicitly ask to remove them.
- Preserve DEBUG clone-and-run behavior.
- No force unwraps in new production code.
- Do not hide failures by weakening the audit script.
Paste this into Claude Code, Cursor, or your editor’s AI chat after you’ve branched off main. It runs the heavy renaming pass and flags couplings before you submit.
You are auditing an iOS app for App Store Guideline 4.3 (Spam) and 4.2.6
(template apps) before submission. The app is built on SwiftAI Boilerplate
Pro; the architecture is shared with other apps, which is fine. The goal
is to remove undifferentiated product, listing, and binary fingerprints.

Do these passes in order. After each pass, print a short report and wait
for me to confirm before continuing.

Pass 1: Branding map.
- Search the repo (excluding Tests, .build, Pods, DerivedData) for any
  occurrence of: "SwiftAI", "Boilerplate", "SwiftAIBoilerplatePro",
  "swiftaiboilerplate.com", boilerplate support email, boilerplate
  bundle ID prefix.
- Output a table: file path, line, old token, suggested replacement
  using my product name "<MY_APP_NAME>" and bundle ID "<MY_BUNDLE_ID>".
- Do not edit yet. Wait for me to confirm.

Pass 2: User-visible strings.
- Once I confirm, apply the renames from Pass 1 only to user-visible
  strings: .strings files, Localizable, SwiftUI Text(...), accessibility
  labels, push payload templates, email templates.
- Leave Swift type names, package names, and folder names alone unless
  I tell you otherwise.
- Print a diff summary.

Pass 3: Dead app-target code.
- List every file in the **app target's** Compile Sources whose symbols
  aren't referenced from any view that appears in the running app's
  navigation graph. Include sample data, example views, and demo
  flows.
- Output the candidate removal list. Do not delete; I'll review.

Pass 4: Module coupling check.
- If FeatureChat is being removed, list every reference to its public
  types from outside the FeatureChat package, especially in the AI
  module's clients and CompositionRoot.
- Same for Payments → FeatureSettings paywall hooks.
- Output a "blocking removals" list with file:line references.

Pass 5: App Store listing review.
- Read the screenshots referenced in /AppStoreAssets (or wherever I
  point you) and the App Store description draft if I paste one.
- Tell me: does the first screenshot lead with the hero feature?
  Does the description's first sentence answer "what this app does
  for me"? Where does it still sound like a generic AI-chat
  template?

Constraints:
- Don't invent files or symbols. If you can't find something, say so.
- Don't claim the app will pass review. Your job is to reduce the
  patterns reviewers commonly cite under 4.3 / 4.2.6.
- Stop after each pass and print a short summary.

If you already got a 4.3 rejection

Don’t panic, and don’t argue with the reviewer in the first reply.
1

Read the rejection literally

Apple’s message usually quotes the guideline number and points at one or two specific things: a screenshot, a string, a flow, or a binary similarity. Treat that as the entire problem statement, not a hint at a wider issue.
2

Run the full pre-submit workflow again

Even if the rejection mentions only one thing, re-run bash scripts/appstore-43-audit.sh and walk every pre-submit step. Reviewers compare binaries; if one fingerprint matched, others probably did too.
3

Reply in App Review with what changed

In App Store Connect → Resolution Center, write a short reply: what you changed, why your app is differentiated, and a one-paragraph plain-language description of what users do with it. Resubmit a new build with a bumped build number.
4

If you disagree, request a call

If you’ve done the work and still believe the rejection is wrong, you can request a phone call through App Review. Show up with your branding map, screenshots, and a clear answer to “who is this for and what makes it different.” Don’t argue the guideline; argue the facts.

What a boilerplate can’t guarantee

Honest version: a boilerplate can save you weeks of plumbing. It can’t tell Apple your app is differentiated, because that’s a judgment call about your product, your listing, and your category. The patterns above are the ones reviewers cite most often. They don’t cover product-market fit, content quality, or whether your category is genuinely too crowded. If your idea is “a chat app with a different color scheme,” no checklist will save you. If your idea is a real product that happens to use a chat interface, this checklist removes the noise so reviewers can see it.

Building Your App

Branding, backend, and feature customization

Deployment Guide

TestFlight and App Store submission steps

Customization Recipes

Colors, themes, onboarding, and modules

Apple Guideline 4.3

Read Apple’s wording directly