Xcode Module Not Found After SPM Add: Quick Fix
Link the package product to your target, import the product name, and clean derived data.
20+ years shipping production backend systems. Notes here come from systems that actually shipped.
- ✓Xcode with a basic iOS project
- ✓Adding files to targets
- ✓Terminal basics for cleanup
- Xcode resolves packages per project but links them per target, so each target that imports must list the product
- Import the product name declared in Package.swift, which often differs from the repo name you pasted
- Resolve package versions fully, then clean derived data and restart Xcode before rebuilding
- Pin dependencies to versions and commit Package.resolved so every machine and CI runner builds identically
Picture a library that stocks a new book series on its shelves. Resolving is the delivery truck arriving. Linking is putting the books on your branch library's shelf. If your branch never shelved them, asking the librarian finds nothing, even though the books are in the building. Shelving your branch is linking the product to your target, and shelving every branch covers tests and widgets too.
You add a Swift package, watch it resolve with a satisfying checkmark, type import, and Xcode answers no such module. The package sits right there in the navigator, documented and promising, while the compiler insists it doesn't exist. Cleaning doesn't help. Readding doesn't help. Restarting feels like superstition.
The gap is that resolving and linking are separate steps. Resolving downloads the package into the project. Linking attaches its products to the specific target being built. Xcode resolves once per project but links per target, so your import fails in exactly the targets you forgot to connect, including test bundles everyone forgets.
Beginners hit this alongside three lookalike failures: importing the repo name instead of the product name, building on stale derived data mid-download, and tracking a branch that moved under them. All four produce the same gray error with different cures.
This guide shows you how to confirm target membership, import the true product name, clean derived data correctly, restart Xcode at the right moment, and pin versions so the fix sticks. Five minutes of systematic checks beats an hour of re-adding packages.
Resolve Is Not Link: Target Membership First
A Swift package joins your project in two distinct moves. Resolving fetches the code and records versions; it happens once per project and puts the package in the navigator. Linking connects a package product to a build target; it happens once per target and puts the compiled module on that target's search path. The navigator checkmark proves the first move. Only a successful import proves the second.
Open the failing target's General tab and read Frameworks, Libraries, and Embedded Content. If the package product isn't listed, the compiler literally cannot see the module no matter how healthy the checkout is. Test bundles, widget extensions, and watch targets each keep their own list, which is why the app builds while its tests fail with the identical import line.
The command line confirms the same facts without clicking. xcodebuild -list shows schemes and targets so you verify which target actually builds. xcodebuild -resolvePackageDependencies forces resolution to complete and reports checkout errors as text. Use these when CI fails but Xcode looks fine, since headless builds expose link gaps the IDE's cached state hides.
Make membership review part of adding any package. The developer who adds it links every importing target immediately, including tests, and the pull request shows each target building. A package added for one target and imported by two is a demo-day failure filed in advance.
Import the Product Name, Not the Repo Name
Packages have three names that routinely differ: the repository name, the internal target names, and the product names clients actually import. The repository is the URL slug you pasted. Targets are the package author's build units. Products are the libraries the package vends, and only product names work in import statements. Guessing among the three fails two times out of three.
Read Package.swift of the dependency and find the products array. The name field of the .library entry is your import string, exact case included. In Xcode you can also select the resolved package in the navigator to see its products without opening the manifest. Copy that string into your import rather than reconstructing it from the URL.
Mixed packages multiply the confusion by vending several products from one repo, like AnalyticsCore alongside AnalyticsUI. Importing the umbrella repo name compiles nowhere; each file imports exactly the product it uses. When a tutorial's import stops working, the package usually renamed a product between major versions while the repo URL stayed put.
Lock the discovery into the codebase. Add a comment or a central wrapper file naming the product and its pinned version, so the next developer doesn't re-derive the string from memory. Grep for the old wrong import after fixing, because copy-pasted files spread the wrong name faster than the fix.
Resolve Fully, Then Clean, Then Build
Package resolution is asynchronous: Xcode downloads checkouts, verifies versions, and generates module maps in the background. Building mid-resolution compiles against partial checkouts with missing module maps, which produces no such module errors that no amount of cleaning fixes, because the underlying download still hasn't finished. The navigator's progress badges are the signal most developers ignore.
The correct sequence is resolve, wait, clean, build. Trigger File, Packages, Resolve Package Versions or the xcodebuild equivalent, watch every package badge turn solid, then clean the build folder and compile. Each step depends on the previous one completing: cleaning mid-download just deletes scaffolding the resolver still needs.
Corrupt checkouts need stronger medicine. A branch switch, a killed Xcode, or a disk-full moment can leave SourcePackages half-written in a state resolve alone won't repair. Deleting the SourcePackages checkout folder under derived data and resolving again fetches pristine copies. This preserves your project settings while replacing exactly the damaged layer.
Teach the sequence to the team as law: badges solid before build, resolve before clean, clean before rebuild. Paste it in the repo readme next to the setup steps. Random re-adding of packages is how developers spend an hour fixing a ninety-second download wait.
Derived Data, Restarts, and Stale Module Graphs
Derived data caches the module graph: resolved checkouts, built products, indexes, and the maps connecting imports to files. When packages change underneath it, through upgrades, branch moves, or interrupted downloads, the cache describes a project that no longer exists. Clean removes build products but preserves the graph; only deleting derived data rebuilds the map from current truth.
Restarting Xcode matters because the IDE holds the package graph in memory alongside the disk cache. Closing the window leaves the process and its stale graph running. Quitting fully, deleting derived data, and reopening forces both layers to reconstruct from the project file and fresh checkouts. Either step alone fixes half the cases; together they fix nearly all.
Recognize the stale-cache signature: the module worked yesterday, nothing in your diff touches packages, and teammates build fine. Your disk cache diverged from shared truth, usually via an interrupted resolve or an uncommitted Package.resolved. The fix is local hygiene, not project surgery, so resist re-adding the package or editing search paths.
Don't let the reset become a habit that hides real bugs. If you delete derived data weekly for the same package, something structural is wrong: an unpinned branch, an uncommitted resolved file, or a post-install script fighting Xcode. Fix the structure after unblocking yourself, or the stale cache will return on schedule.
Version Rules and Package.resolved That Reproduce
Version rules decide which code a fresh checkout receives. A from: rule takes the latest compatible release, an exact pins one release, and a branch tracks a moving target. Branches feel current but make builds unreproducible: Monday's checkout and Friday's checkout are different code with the same project file. Releases and demos must never track branches.
Package.resolved records the exact versions last resolved, and committing it makes every machine and CI runner check out identical code. Without it, two developers with the same version rules can resolve different releases on different days, and only one of them sees the new API. The resulting works-on-my-Mac module error wastes exactly the afternoon you needed for the demo.
Upgrades deserve a deliberate flow. Bump the version rule, resolve, read the package's changelog for renamed products and raised deployment targets, fix imports across all targets, and commit the rule plus the resolved file together. Reviewers then see the dependency change as one atomic diff instead of a surprise.
Lint both halves in CI: fail builds when Package.resolved is uncommitted or when any dependency tracks a branch on release schemes. These checks cost minutes to write and save the demo-day scramble permanently. Reproducibility isn't paperwork; it's the difference between debugging your code and debugging your checkout.
Platform Floors and Capability Mismatches
Platform requirements fail at dependency time with errors that masquerade as missing modules. A package declaring iOS 17 APIs can't link into a target deploying iOS 15, and the resulting diagnostic sometimes blames the module rather than the deployment gap. Read the full error: when it names platforms, the import name was never the problem.
Align the two numbers deliberately. Either raise your target's deployment setting to meet the package, accepting the dropped OS versions, or pin an older package release that still supports your floor. The package's README and release notes document supported platforms per version; match them before editing build settings.
Capabilities bite the same way. Packages vendoring resources, plugins, or binary targets need the corresponding Xcode version and sometimes explicit resource handling in the consuming target. A binary artifact built for simulators only fails linking for device archives in ways that read like absence. Check the package's platform and artifact matrix when the import is spelled right and linked yet still missing.
Record the floor decision next to the dependency. A comment in Package.swift or the readme stating the minimum iOS and the reason stops the next developer from upgrading past it blindly. Platform drift is silent until archive day; documentation makes it a reviewed choice instead of a release surprise.
The Widget That Missed the Demo Over One Link
- Add-and-build-the-app doesn't prove the project builds. Compile every target, including widgets and tests, before calling a dependency done.
- Branch-tracked dependencies are moving parts. Pin versions for anything that ships or demos, and reserve branches for experiments.
- Commit Package.resolved and lint for it. Reproducible checkouts turn teammate-machine mysteries into local five-minute fixes.
| File | Command / Code | Purpose |
|---|---|---|
| check-links.sh | xcodebuild -list | Resolve Is Not Link |
| resolve-clean.sh | xcodebuild -resolvePackageDependencies -scheme ShopApp | Resolve Fully, Then Clean, Then Build |
| full-reset.sh | rm -rf ~/Library/Developer/Xcode/DerivedData/ShopApp-* | Derived Data, Restarts, and Stale Module Graphs |
| Package.swift | dependencies: [ | Version Rules and Package.resolved That Reproduce |
Key takeaways
Common mistakes to avoid
5 patternsAdding the package to the project but not to the target
Importing the repo name instead of the product name
Building against a half-resolved package checkout
Trusting stale derived data after package changes
Tracking a branch instead of a versioned release
Interview Questions on This Topic
A package is added but import fails. What's the first check?
Frequently Asked Questions
20+ years shipping production backend systems. Notes here come from systems that actually shipped.
That's Xcode. Mark it forged?
5 min read · try the examples if you haven't