Skip to main content

Configuration

You can drive the CLI entirely with flags, but you won't want to. There are three places a setting can live, and they exist because they answer different questions:

  • sweetpad.toml, committed next to your project: what does this project need? Everyone who clones the repo gets it.
  • ~/.config/sweetpad/config.toml, yours alone: what do I prefer? SweetPad writes only its [feedback] table there, and only when you run sweetpad feedback off or on.
  • Remembered context, managed for you: what did I pick last time? This is where the answers to interactive prompts go.

sweetpad status always reports which of these a value came from, so a build shaped by a file you didn't write still explains itself.

The project file​

sweetpad.toml is the team-shared layer. Put it next to your project, commit it, and everyone gets the same defaults:

# sweetpad.toml
scheme = "MyApp"
configuration = "Debug"
destination = "platform=iOS Simulator,name=iPhone 16 Pro"

[xcodebuild]
args = ["-skipMacroValidation"]

SweetPad looks for it by walking up from your working directory to the git root, so one file at the repo root serves the whole checkout, and you don't need a copy per subdirectory.

Every key​

The top level takes the same targeting values as the flags:

KeyWhat it does
schemeDefault scheme.
configurationDefault build configuration.
destinationDefault destination, as a raw specifier.
sdkSDK override. Rarely needed, since the destination usually implies it.
developer_dirPin the Xcode this project uses, for builds and manifest reads.
workspaceName the .xcworkspace, relative to this file. See below.
projectName the .xcodeproj, relative to this file. See below.
generatorDeclare the project as generated, e.g. "xcodegen".

Then four tables:

TableKeyWhat it does
[run]hotDefault sweetpad run to hot reload. --no-hot opts out.
hot_recompiler"resolver" or "buildlog".
auto_unsandboxWhether a hot macOS build may strip the App Sandbox. Default true.
[format]tool"swift-format" or "swiftlint".
[testing]scheme, configuration, destination, targetTest-only overrides, layered over the build values.
[xcodebuild]argsArguments added to every command that builds.

A fuller example:

# sweetpad.toml
scheme = "MyApp"
developer_dir = "/Applications/Xcode-16.4.app/Contents/Developer"

[run]
hot = true

[format]
tool = "swiftlint"

[testing]
configuration = "Test"
destination = "platform=iOS Simulator,name=iPhone SE (3rd generation)"

[xcodebuild]
args = ["-skipMacroValidation", "-disablePackageRepositoryCache"]

Pointing at a project somewhere else​

By default the file sits beside the container it configures. When it doesn't, as with a repo root and the Xcode project a few directories down, name the container relative to the file:

# sweetpad.toml, at the repo root
project = "ios/App.xcodeproj"

Now every command works from anywhere in the checkout with no -C. workspace does the same for an .xcworkspace and wins when both are set.

You usually don't need this. Auto-discovery already searches upward to the git root and then up to two levels down, skipping the usual noise (Pods, node_modules, Carthage, vendor, DerivedData, build, and dotfile directories), so a layout like ios/App.xcodeproj works with no setup. The key earns its keep when two projects sit at the same depth. SweetPad reports that as an error listing both rather than guessing, and this is how you settle it.

Test-only flags​

[xcodebuild] args reaches every command that runs xcodebuild, but some flags only work when testing. xcodebuild build, archive, and clean fail on -enableCodeCoverage, -testPlan, -testLanguage, -testRegion, and -testProductsPath. SweetPad leaves these out of every command except sweetpad test and sweetpad test build, so you can keep them in the file:

[xcodebuild]
args = ["-skipMacroValidation", "-testPlan", "CI"] # -testPlan reaches only the test runs

-resultStreamPath needs a result bundle to stream into, so it stays out of sweetpad clean and out of sweetpad archive unless you pass -resultBundlePath after --. Run with -v to see each flag a command left out.

The rest of the testing flags, such as -test-iterations and -parallel-testing-enabled, work with every action and reach every command. Flags you type after -- are never left out.

Arguments SweetPad won't let you put here​

[xcodebuild] args refuses the arguments SweetPad settles itself, naming the key to use instead: -scheme, -configuration, -destination, -sdk, -workspace, and -project.

It also refuses the paths SweetPad names for itself. sweetpad test writes and reads back its own -resultBundlePath, so name one per run with sweetpad test --result-bundle, or pass it after -- on a build. sweetpad archive names its own -archivePath, -exportPath, and -exportOptionsPlist, so use --output-file and --export-options instead.

-enableCodeCoverage and -test-iterations can stay in the file. When you pass --coverage or --retry-flaky to sweetpad test, the flag replaces the file's copy, and -v says so.

-derivedDataPath is refused too. Only the builds, sweetpad clean, and the app commands would follow it, while sweetpad clean --purge, sweetpad derived-data, and the editor's index would keep using the DerivedData location Xcode's settings name. A relative value would also resolve against the project's directory, where SweetPad runs xcodebuild, and not against this file the way project does. Pass that one per command, after -- on the build and as --derived-data-path on app launch.

Swift packages ignore the table entirely: they build with swift build, which knows none of xcodebuild's flags.

tip

For KEY=VALUE build settings, an .xcconfig is usually the better committed home, because Xcode honors it too, so ⌘B and sweetpad build stay in agreement. Put flags in [xcodebuild] args; put build settings in an xcconfig unless you specifically want them only when building through SweetPad.

Your personal config​

~/.config/sweetpad/config.toml holds your own preferences. It's a file you own, and it honors XDG_CONFIG_HOME if you set one. SweetPad writes to it in one case: sweetpad feedback off sets enabled = false under [feedback] (see Feedback reports), and sweetpad feedback on sets it back. Both change only that key and keep the rest of the file, comments included.

It has a [defaults] table for values that apply everywhere, plus per-project tables:

# ~/.config/sweetpad/config.toml
[defaults]
configuration = "Debug"

[projects."/Users/me/code/MyApp/MyApp.xcodeproj"]
scheme = "MyApp"
destination = "platform=iOS Simulator,name=iPhone 15"

[projects."/Users/me/code/MyApp/MyApp.xcodeproj".testing]
configuration = "Test"
target = "MyAppTests"
warning

The project key is the container: the .xcodeproj, .xcworkspace, or Package.swift itself, not the directory holding it. A key that can't match a real container is reported as a warning on every run, which is how you'll notice you wrote the directory path instead.

Personal config beats sweetpad.toml, so this is also where you override a team default you disagree with, without touching the committed file.

Remembered context​

The third layer isn't a file you edit. When SweetPad prompts you for a scheme or a destination, it saves the answer per project and stops asking. Inspect and change it with sweetpad context:

sweetpad context show
sweetpad context select
sweetpad context set scheme MyApp
sweetpad context remove --all

Destinations and devices covers this in full.

Which setting wins​

Highest first:

flag > environment variable > config.toml > sweetpad.toml > remembered answer > auto-detect

Read it as most-explicit-wins. A flag you typed beats everything; auto-detection only happens when nothing else has an opinion. Note that your personal config outranks the committed project file, and that both outrank whatever you last picked at a prompt.

Environment variables​

Every targeting value has an environment twin, folded into the flag layer. A typed flag still wins, and a variable set to the empty string counts as unset:

VariableSets
SWEETPAD_WORKSPACEPath to the .xcworkspace.
SWEETPAD_PROJECTPath to the .xcodeproj.
SWEETPAD_SCHEMEScheme name.
SWEETPAD_CONFIGURATIONBuild configuration.
SWEETPAD_DESTINATIONRaw destination specifier.
SWEETPAD_ONHuman destination reference. Overrides SWEETPAD_DESTINATION.
SWEETPAD_SDKSDK override.
DEVELOPER_DIRThe Xcode every spawned tool uses.

Two more control behavior rather than targeting. SWEETPAD_NONINTERACTIVE turns prompts into errors, and CI does the same thing, which is why a pipeline usually needs neither. Both parse loosely: 0, false, no, off, and empty all mean off.

Color follows the usual conventions: NO_COLOR disables it, CLICOLOR_FORCE and FORCE_COLOR force it on even when output is piped, and an explicit --no-color still wins.

Typos are never silent​

Unknown keys produce a warning on every run, in both config files. So does a project key that can't match a real container. A setting that quietly does nothing is worse than one that complains, and this is the one part of the config system that's deliberately noisy.

Where things live​

PathWhat
sweetpad.tomlProject defaults. Committed, hand-authored.
~/.config/sweetpad/config.tomlYour defaults. Hand-authored; sweetpad feedback off sets one key.
~/.local/state/sweetpad/state.tomlRemembered context. Managed; use sweetpad context.

sweetpad open config opens your personal config in your editor, and sweetpad help config has this material offline.