Swift Package, Target, Module, and Product: How They Fit Together
Build a small Swift package and trace the difference between its manifest, modules, library product, tests, and client imports.
A package is not the same thing as a module, and a product name is not always the name you write after import. These terms are often identical in a small sample project, which hides the distinction. A package with two targets and one differently named product makes each role visible.
Updated September 2026 with Swift 6 examples.
Swift Package Manager was announced as an open-source project on December 3, 2015. The explicit product and target declarations used below were described in the 2017 manifest redesign and shipped with Swift 4 on September 19, 2017; the code sample itself uses a current Swift 6 tools-version.
Swift Package Manager reads a Package.swift manifest to learn what a package contains and how its targets depend on one another. The Swift Package Manager introduction defines a target as a module-building unit and a product as something the package offers to clients.
Start with the manifest
This package is called Notes. It has two regular targets, one test target, and one library product:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "Notes",
products: [
.library(name: "NotesKit", targets: ["NotesCore", "NotesStorage"])
],
targets: [
.target(name: "NotesCore"),
.target(name: "NotesStorage", dependencies: ["NotesCore"]),
.testTarget(
name: "NotesStorageTests",
dependencies: ["NotesStorage", "NotesCore"]
)
]
)
The usual directory layout for those target names is:
Notes/
├── Package.swift
├── Sources/
│ ├── NotesCore/Note.swift
│ └── NotesStorage/MemoryStore.swift
└── Tests/
└── NotesStorageTests/MemoryStoreTests.swift
The folders are a convention that matches this manifest; a target can declare a custom path if its sources live elsewhere. The // swift-tools-version: 6.0 line tells SwiftPM the minimum tools and PackageDescription API version needed to read the manifest. It is not a version number assigned to the Notes library and does not set an iOS deployment target. These are separate settings in the PackageDescription API.
Package: the container and dependency identity
Notes is the package described by this manifest. The package groups the manifest, source, tests, and any resources or external package dependencies it declares. When another package depends on Notes, SwiftPM obtains and resolves the package first. That does not mean every target in Notes automatically becomes a dependency of every client target.
A remote package can have version requirements resolved from releases or tags. A local path dependency uses the package at that path instead of selecting a remote version. Neither the package name nor a version requirement is a Swift import statement.
Target and module: what gets compiled
NotesCore and NotesStorage are separate Swift targets. Each builds its own module and namespace. A Swift source file in NotesStorage may import NotesCore because the manifest declares that target dependency. NotesCore does not declare a dependency on NotesStorage, so the reverse import is not part of this package's dependency graph.
Here is a type in Sources/NotesCore/Note.swift:
public struct Note: Equatable {
public let text: String
public init(text: String) {
self.text = text
}
}
And here is Sources/NotesStorage/MemoryStore.swift, which uses that module:
import NotesCore
public struct MemoryStore {
public private(set) var notes: [Note] = []
public init() {}
public mutating func save(_ note: Note) {
notes.append(note)
}
}
MemoryStore can use Note because of the declared target dependency and import NotesCore. The public access levels matter too: exporting a target through a product does not automatically make its otherwise internal declarations usable from another module. The client needs access to the type and to the initializer or members it calls.
Product: what a client selects
NotesKit is the library product. The manifest assembles it from the two targets. A client declares a dependency on this product, not directly on a repository folder. Inside its Swift files, the client imports the module names NotesCore and NotesStorage. import NotesKit would not name a module in this example.
The package can also contain a target that is not part of any public product. NotesStorageTests is one: it is built for testing and depends on both library targets, but the NotesKit product does not include it.
The library declaration leaves the linkage type unspecified. SwiftPM can choose static or dynamic linkage for a consumer; the manifest can explicitly request .static or .dynamic when a library must use one form. Linkage is a product setting, distinct from the target's module name. See the Product definition.
A test target and a client
A test in Tests/NotesStorageTests/MemoryStoreTests.swift can import the two modules named in its target dependencies:
import Testing
import NotesCore
import NotesStorage
@Test func storesANote() {
var store = MemoryStore()
store.save(Note(text: "Hello"))
#expect(store.notes.map(\.text) == ["Hello"])
}
An external client can use a local dependency to demonstrate the other side. Its Package.swift adds the package path, then makes the NotesKit product a dependency of its executable target:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "NotesClient",
dependencies: [.package(path: "../Notes")],
targets: [
.executableTarget(
name: "NotesClient",
dependencies: [.product(name: "NotesKit", package: "Notes")]
)
]
)
The client's Sources/NotesClient/main.swift imports the modules provided by that product:
import NotesCore
import NotesStorage
var store = MemoryStore()
store.save(Note(text: "Hello"))
print(store.notes.count) // 1
This separates two declarations that are often mistaken for one: .package(path:) identifies the package to obtain, while .product(name:package:) tells this particular client target which product it uses. The Swift source then imports the relevant modules. SwiftPM's dependency guide shows the same two-level relationship for remote packages.
Read the three names in order: package Notes groups the project; targets/modules NotesCore and NotesStorage compile its code; product NotesKit exposes those modules to clients. The test target belongs to the package without becoming part of the client-facing product.