Introduction
Currently, SwiftPM supports a handful of hardcoded templates that act as a starting point for user projects. These built-in templates, accessible via swift package init
, allow users to initialize basic packages such as libraries, tools, executables, and macros. More complex package initialization is achieved through third-party command-line tools or custom scripts, many of which operate outside the SwiftPM ecosystem. This disconnect increases the complexity and makes it harder for users to discover, share, or customize initialization workflows.
To address this gap, this proposal introduces a system that allows Swift packages to declare reusable templates with custom logic for generating a user’s package code. These templates can be distributed like any other Swift package and executed in a standardized, first-class manner via a new SwiftPM command. Template authors would also have access to tools to improve development experience and maintainability over time.
Motivation
While swift package init
remains valuable for initializing simple packages, many Swift developers face more complex requirements when starting a new project, whether that may be initializing an HTTP server, or creating a package that implements an OpenAPI specification.
As a solution, this proposal introduces an extended version of swift package init
. This extended command allows developers to easily get started with a new package based on a specific use case. Furthermore, swift package init
supports invoking custom, user-defined templates that are:
- Fully integrated with SwiftPM
- Customizable through arguments and logic
- Shareable just like standard Swift packages
- Flexible enough for a wide range of use-cases
This enhancement will empower developers to create and reuse templates for specific use-cases, all while ensuring consistency and good practices.
Proposal
Package Generation from template
Swift Package Manager, along with the improved swift package init
command, enables users to easily generate a package based on predefined templates. The init
command currently allows users to select a template bundled within SwiftPM via the --type
option. However, this functionality will now be extended to support selecting a template from an external package located either on local disk, in a git repository, or in a package registry, by specifying the template’s location using the --path
, --url
, or --package-id
options along with any necessary versioning requirements. SwiftPM will then generate the package based on the selected template by performing the following steps:
- Creates a temporary working directory
- Initializes the appropriate base package
- Prompts the user for any required configurations
- Generates the final package structure in the temporary directory
- Copies the fully-initialized package into the user’s specified destination directory
Below is an example of generating a package from a template located in a registry.
% swift package init --type PartsService --package-id author.template-example
...
Build of product 'PartsService' complete! (7.15s)
Add a starting database migration routine: [y/N] y
Add a README.md file with an introduction and tour of the code: [y/N] y
Choose from the following:
• Name: include-database
About: Add full database support to your package.
• Name: exclude-database
About: Create the package without database integration
include-database
Pick a database system for part storage and retrieval. [sqlite3, postgresql] (default: sqlite3):
sqlite3
Building for debugging...
[1/1] Write swift-version.txt
Build of product 'PartsService' complete! (0.42s)
After the project is generated, the user is left with a scaffolded package and is ready to start hacking.
.
├── Package.swift
├── Scripts
│ └── create-db.sh
├── README.md
├── Sources
│ ├── Models
│ │ └── Part.swift
│ │
│ └── App
│ └── main.swift
│
└── Tests
└── PartsServiceTests
└── PartsServiceTests.swift
Below is the new output of swift package init --help
:
% swift package init --help
OVERVIEW: Initialize a new package.
USAGE: swift package init [<options>] [<args> ...]
ARGUMENTS:
<args> Template arguments to auto-fill prompts and skip input.
OPTIONS:
--type <type> Specifies the package type or template.
Valid values include:
library - A package with a library.
executable - A package with an executable.
tool - A package with an executable that uses
Swift Argument Parser. Use this template if you
plan to have a rich set of command-line arguments.
build-tool-plugin - A package that vends a build tool plugin.
command-plugin - A package that vends a command plugin.
macro - A package that vends a macro.
empty - An empty package with a Package.swift manifest.
<custom> - When used with --path, --url, or --package-id,
this resolves to a template from the specified
package or location.
--name <name> Provide custom package name.
--path <path> Path to the package containing a template.
--url <url> The git URL of the package containing a template.
--package-id <package-id>
The package identifier of the package containing a template.
--exact <exact> The exact package version to depend on.
--revision <revision> The specific package revision to depend on.
--branch <branch> The branch of the package to depend on.
--from <from> The package version to depend on (up to the next major version).
--up-to-next-minor-from <up-to-next-minor-from>
The package version to depend on (up to the next minor version).
--to <to> Specify upper bound on the package version range (exclusive).
--build-package Run 'swift build' after package generation to validate the template.
--version Show the version.
-h, -help, --help Show help information.
To encourage community reuse and sharing, templates are intended to be distributed using the same mechanism as Swift packages themselves. This ensures that developers can rely on familiar distribution workflows:
- Git repositories (public or private)
- Swift package Registry entries
- Local paths for internal or enterprise use
SwiftPM will resolve these sources similarly to how it handles regular dependencies, caching them for offline use and version resolution.
This change opens the door to an ecosystem of templates maintained by framework authors as well as enterprise-internal scaffolds tailored to a company’s needs.
Security & Trust
Given that templates may execute arbitrary Swift code, a design decision was made to execute templates via SwiftPM command-line plugins, a secure and permission-aware mechanism already established in the Swift ecosystem. By leveraging this infrastructure, template authors can include Swift-based logic while SwiftPM maintains clear security boundaries.
SwiftPM plugins run in a sandboxed process separate from the SwiftPM itself, enforcing the principle of least privilege. As such, they operate with limited permissions and reduce the risk of malicious code affecting critical system components. Furthermore, each plugin is paired with a singular template, allowing developers to have clear visibility into the sources of their respective templates, reducing the risk of hidden malicious logic. Finally, they have a scoped execution environment i.e. they cannot arbitrarily modify files outside of allowed directories, reducing the risk of corruption or backdoor insertion.
IDE Integrations
To further enhance the usability and accessibility of custom templates, the implementation and API surface of templates should enable consumption and integration with IDEs, supporting package initialization with templates through graphical and automated workflows. These integrations would provide intuitive interfaces for discovering, configuring, and initializing packages from templates, making the feature more approachable for a broader audience.
Authoring a template
Maintaining templates over time, especially those with big decision trees, can be challenging for authors. Ensuring that a template behaves as expected across different configurations is crucial to its reliability and long-term usability. As such, the template-authoring experience centers around two facets:
- Flexibility
- Testability
Template authors should have the flexibility to design their templates however they prefer, whether that preference may be using templating engines, string interpolation or another approach. It should be up to the template author to choose the style that brings them comfort and familiarity.
At the same time this flexibility requires a reliable way for templates to communicate with SwiftPM and vice-versa. In order to allow this communication, template authors must fulfill the following requirements.
Information required by the template must be defined through swift-argument-parser
At its very essence, a template is an executable that is run via command-line plugin. As such, its arguments are declared via swift-argument-parser syntax:
@Flag(help: "Add a README.md file with an introduction and tour of the code")
var readme: Bool = false
@Option(help: "Pick a database system for part storage and retrieval.")
var database: Database = .sqlite3
@Flag(help: "Add a starting database migration routine.")
var migration: Bool = false
The reasoning by this decision is to leverage Swift’s powerful JSON parsing capabilities alongside --experimental-dump-help
, which generates a JSON output of available arguments, in order to prompt the consumer for their choices.
However, a template is not simply a list of options, flags, and arguments. It is a branching decision tree, where specific choices influence which options are available next. To model these branching paths, authors can organize them into subcommands, which help users navigate complex template structures more naturally.
@main
struct ServerGenerator: ParsableCommand {
public static let configuration = CommandConfiguration(
commandName: "server-generator",
abstract: "This template gets you started with starting to experiment with servers in swift.",
subcommands: [
CRUD.self,
Bare.self
],
)
@Flag(help: "Add a README.md file with an introduction and tour of the code")
var readme: Bool = false
mutating func run() throws {
...
}
}
struct Bare: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "bare",
abstract: "Generate a bare server"
)
@OptionGroup
var serverOptions: SharedOptionsServers
func run() throws {
try? FileManager.default.removeItem(atPath: packageDir / "Package.swift")
try packageSwift(serverType: .bare).write(toFile: packageDir / "Package.swift")
}
}
public struct CRUD: ParsableCommand {
public static let configuration = CommandConfiguration(
commandName: "crud",
abstract: "Generate CRUD server",
)
@Option(help: "Set the logging level.")
var logLevel: LogLevel = .debug
@OptionGroup
var serverOptions: SharedOptionsServers
public func run() throws {
try? FileManager.default.removeItem(atPath: packageDir / "Package.swift")
try packageSwift(serverType: .bare).write(toFile: packageDir / "Package.swift")
}
}
The example code provided demonstrates how a template with multiple decision branches might be structured to generate files, but the actual approach to templating, including how source code is generated, organized, or customized, is entirely up to the template author. Swift Package Manager simply provides the mechanism to invoke templates; the template author defines the logic, content, and structure of the generated code according to their needs.
Below is what the consumer might see when initializing a project based on the template above:
% swift package init --type ServerTemplate --path <path/to/template>
...
Choose from the following:
• Name: crud
About: Generate CRUD server
• Name: bare
About: Generate a bare server
Type the name of the option:
crud
Set the logging level. [trace, debug, info, notice, warning, error, critical] (default: debug):
notice
Building for debugging...
[1/1] Write swift-version.txt
Build of product 'ServerTemplate' complete! (0.42s)
Template Target and product
This pitch introduces a new kind of target and product in SwiftPM: .template
. This new syntax abstracts the declaration of templates within a package and allows authors to declare their presence within the Package.swift
file.
Here is an example of a Swift package that defines a simple template:
let package = Package(
name: "TemplateExample",
products: .template(name: "Template1")
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from "1.3.0")
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0")
],
targets: .template(
name: "Template1",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client")
],
initialPackageType: .executable,
description: "A simple template that requires network access",
permissions: [
.allowNetworkConnections(scope: .none, reason: "Need network access to help generate a template"
]
)
)
The .template()
API allows for full control over a template’s dependencies, permissions, and descriptive metadata. Dependencies declared in the dependencies
array are made available only to the generated executable target, not the plugin. This separation ensures that plugin code used to instantiate the template remains isolated, improving encapsulation and security.
The initialPackageType
parameter corresponds to the current set of types of packages available for initialization. Each template type corresponds to a predefined package configuration that is used by the template executable as a base for scaffolding a new Swift package.
public enum initialType {
case library
case executable
case tool
case buildToolPlugin
case commandPlugin
case `macro`
case empty
}
Finally, the permissions
parameter is primarily focused on specifying security requirements and access scopes needed by the package. Whenever a permission is required, the template will prompt the user just as plugins do. This process helps ensure that users understand why certain permissions are required when executing an author’s template, ensuring transparency and establishing trust between users and authors.
Author’s Package structure
To maintain organization and consistency within an author’s package, unless specified differently by the author, all template-related code must be placed under the /Templates
directory. Below is an example of an author’s package directory.
.
├── Package.swift
│
├── Templates
│ └── Template1
│ └── Template1.swift
├── Plugins
│ └── Template1Plugin
│ └── Template1Plugin.swift
│
└── Tests
└── FooTests
└── FooTests.swift
As every template is an executable and a command-line plugin, templates must have an entry point along with arguments, options and flags to represent required configurations needed from the consumer. Afterwards, the author is free to write their template however they want.
Below is an example of a template based on Swift’s string interpolation that generates the basic structure for a simple Swift project, including a main.swift
file, and optionally a README.md
file.
import ArgumentParser
import Foundation
import SystemPackage
extension FilePath {
static func / (left: FilePath, right: String) -> FilePath {
left.appending(right)
}
}
extension String {
func write(toFile: FilePath) throws {
try self.write(toFile: toFile.string, atomically: true, encoding: .utf8)
}
}
//basic structure of a template that uses string interpolation
@main
struct HelloTemplateTool: ParsableCommand {
//swift argument parser needed to expose arguments to template generator
@Option(help: "The name of your app")
var name: String
@Flag(help: "Include a README?")
var includeReadme: Bool = false
//entrypoint of the template executable, that generates just a main.swift and a readme.md
func run() throws {
let fs = FileManager.default
let rootDir = FilePath(fs.currentDirectoryPath)
let mainFile = rootDir / "Sources" / name / "main.swift"
try fs.createDirectory(atPath: mainFile.removingLastComponent().string, withIntermediateDirectories: true)
try """
// This is the entry point to your command-line app
print("Hello, \(name)!")
""".write(toFile: mainFile)
if includeReadme {
try """
# \(name)
This is a new Swift app!
""".write(toFile: rootDir / "README.md")
}
print("Project generated at \(rootDir)")
}
}
Testing
Templates, like any other shippable software, should be testable. The flexibility of creating a template allows authors to write various types of tests, including unit tests, and integration tests. Authors should strive to deliver high-quality templates while minimizing unnecessary risk, complexity, and rework. Therefore, authors are strongly encouraged to write focused tests that ensure reliability and maintainability.
Below is an example of a simple unit test, verifying whether a file generation function correctly interpolates configuration values into an output file and reflects logging-related settings in the generated code.:
import Testing
import Foundation
@testable import ServerTemplate
struct CrudServerFilesTests {
@Test
func testGenTelemetryFileContainsLoggingConfig() {
let generated = CrudServerFiles.genTelemetryFile(
logLevel: .info,
logFormat: .json,
logBufferSize: 2048
)
#expect(generated.contains("let logBufferSize: Int = 2048"))
#expect(generated.contains("Logger.Level.info"))
#expect(generated.contains("LogFormat.json"))
}
}
To support end-to-end testing of templates, Swift Package Manager introduces a new subcommand: swift test template
. This subcommand allows authors to verify if a given template can successfully generate a package and that a generated package builds correctly. By specifying both a template and an output directory, SwiftPM performs the following actions:
- Reads the package, locates the template, and extracts all available options, flags, arguments, and subcommands,
- Prompts for all required inputs (options, flags, and arguments).
- Generates each possible path in the decision tree, from the root command to the leaf subcommands, using the user’s given inputs.
- Validates that each variant is created successfully and builds without error.
- Logs any errors to a file located within the generated package directory.
% swift test template --template-name ServerTemplate --output-path <output/directory/path>
...
Build of product 'ServerTemplate' complete! (3.37s)
Set logging buffer size (in bytes). (default: 1024):
2048
Server Port (default: 8080):
80
Set the logging format. [json, keyValue] (default: json):
keyValue
Set the logging level. [trace, debug, info, notice, warning, error, critical] (default: debug):
critical
Add a README.md file with an introduction to the server + configuration?: [y/N] y
Generating server-generator-crud
Generating server-generator-bare
Argument Branch Gen Success Gen Time(s) Build Success Build Time(s) Log File
server-generator-crud-mtls true 11.17 true 92.09 -
server-generator-crud-no-mtls true 11.53 true 91.65 -
Each variant of a template is written to a subdirectory within the specified output path, with directory names assembled as <command>-<subcommand>-...
to reflect the structure of the decision tree path. This layout not only aids in testing but also enables author review of the generated output of each variant, further increasing confidence in the correctness and reliability of the template. This end-to-end test support ensures an author’s template performs correctly across all decision branches, increasing confidence in its reliability.
Impact on Existing packages
This is an additive feature. Existing packages will not be affected by this change. It adds the ability to define and invoke templates as part of a Swift Package.
Alternatives
None.
Future
We have several plans for Swift templates, including:
- Enabling the ability to also enhance a package using a selected template
- Extending Swift registries to support template metadata, making templates more searchable and discoverable
- Providing an API to simplify modifications to Package.swift, allowing users to add dependencies, targets, target-dependencies and products without rewriting the file from scratch.
However, we want to also gauge community feedback before fully fleshing out the future of this new feature.