Wire SwiftGen into an Xcode Run Script build phase so your type-safe resource code regenerates automatically and never drifts out of sync with your assets and strings.
The whole point of SwiftGen is that generated Swift symbols match your resources exactly. If you rely on developers remembering to run swiftgen config run by hand, that guarantee breaks the moment someone adds an image or a localized string and forgets to regenerate. The generated file goes stale, symbols are missing or wrong, and the failure surfaces as a confusing compile error or a runtime bug. Running SwiftGen as part of the Xcode build removes that human step: every build regenerates the code before compilation, so the symbols always reflect the current resources. There are two common ways to automate it — a Run Script build phase that calls the SwiftGen binary, or the Swift Package Manager build tool plugin that runs during the build automatically. This guide focuses on the Run Script build phase because it gives you the most control over ordering, paths, and incremental-build behavior, and because it works with binaries installed via Homebrew or Mint. Either way, the goal is the same: make generation a reliable, invisible part of building the app. The Run Script approach also has the advantage of being explicit and inspectable — the exact command lives in your project and appears in the build log, which makes it easy to reason about when something goes wrong.
In Xcode, select your project in the navigator, choose the app target, and open the Build Phases tab. Click the plus button at the top of the phase list and choose New Run Script Phase. A collapsible Run Script section appears at the bottom of the list. Drag it so it runs before the Compile Sources phase — this ordering matters, because SwiftGen must write the generated file before the Swift compiler reads it. Rename the phase to something clear like Run SwiftGen by double-clicking its title, which makes build logs easier to read. In the script editor area you will enter the command that invokes SwiftGen. Keep the phase minimal and single-purpose; do not fold unrelated shell steps into it. Once added and correctly ordered, this phase runs on every build of the target. If you have multiple targets that need generated code, add the phase to each, or generate into a shared location that all targets include. The next sections cover the exact script contents and how to make the phase efficient. Also uncheck the option that runs the script only when installing, if present, so generation happens on ordinary debug builds and not just on archive, which is a subtle setting that otherwise leaves your day-to-day builds using stale output.
The script content depends on how you installed SwiftGen. For a Homebrew install, the safest command explicitly checks both Apple Silicon and Intel paths, because Xcode build phases run with a minimal PATH that may not include Homebrew's bin directory. A robust script tests for /opt/homebrew/bin/swiftgen and falls back to /usr/local/bin/swiftgen, then runs the found binary with config run. For a Mint-pinned setup, call mint run swiftgen config run, again using an absolute path to mint if it is not on the build PATH. Point SwiftGen at your config explicitly with --config "$SRCROOT/swiftgen.yml" so it does not depend on the working directory. A common resilient pattern is to print a clear warning and exit successfully if the binary is missing, so a teammate without SwiftGen installed can still build, though stricter teams prefer to fail the build to force correct setup. Avoid hardcoding a developer's home directory. Using Xcode's build variables like SRCROOT keeps the script portable across machines and CI, which is essential once more than one person builds the project. Whichever resilience policy you choose, be explicit about it in the script's comments so the next person understands whether a missing binary is meant to warn or to fail, and can align CI with that decision.
By default a Run Script phase executes on every single build, which slows incremental builds unnecessarily. Xcode can skip a script phase when nothing it depends on has changed, but only if you declare its input and output files. In the Run Script phase there are Input Files and Output Files lists (and optional .xcfilelist references for long lists). Add your resource inputs — for example $(SRCROOT)/Resources/Assets.xcassets and your .strings files — to Input Files, and add the generated Swift files such as $(SRCROOT)/Generated/Assets.swift to Output Files. With these declared, Xcode compares timestamps and runs SwiftGen only when an input changed or an output is missing, keeping clean builds correct and incremental builds fast. Getting these lists right is important: if you omit an input, Xcode may skip generation when you actually changed a resource, leaving stale output; if you omit an output, Xcode cannot track the result and may rerun needlessly. Take the time to list every input resource and every generated output accurately, and revisit the lists whenever you add a new parser to swiftgen.yml. For projects with many resource folders, an .xcfilelist checked into the repository is easier to maintain than a long inline list and keeps the phase readable as the project grows.
The most frequent build-phase failure is swiftgen: command not found, and it is almost always a PATH problem rather than a missing install. Xcode's build environment does not source your interactive shell profile, so tools installed by Homebrew or Mint may not be on PATH inside the script. On Apple Silicon Macs, Homebrew installs to /opt/homebrew/bin, whereas on Intel Macs it uses /usr/local/bin, so a script that hardcodes one path breaks on the other architecture. The reliable fix is to reference the binary by absolute path with a fallback, checking /opt/homebrew/bin/swiftgen first and /usr/local/bin/swiftgen second, or to export a PATH that includes both at the top of the script. For Mint, the same reasoning applies to the mint binary. Verify by building and reading the phase's output in the Xcode report navigator, where the full command and any error appear. If you use the SPM plugin instead of a Run Script phase, you avoid this class of problem entirely because SPM locates the tool itself, which is one reason some teams prefer the plugin on mixed-hardware teams and CI. A dedicated troubleshooting guide covers every variant of this error in depth, but for the build phase specifically the absolute-path-with-fallback pattern resolves the overwhelming majority of cases.
When SwiftGen runs during the build, you must decide whether the generated files are committed or ignored. If you generate at build time on every machine and in CI, you can add the output paths to .gitignore so they never appear in diffs — this avoids merge conflicts in machine-written code but requires SwiftGen to be available everywhere the project builds, including CI. Alternatively, commit the generated files so the repository always compiles even without SwiftGen installed, accepting that generated diffs show up in pull requests. Whichever you choose, add the generated files to the exclude lists of SwiftLint and SwiftFormat so those tools do not lint or reformat code SwiftGen owns. Make sure generated files that are ignored are still added to the Xcode target's Compile Sources, or that the build phase writes them into a location the target already includes. Document the choice in your README so a new contributor understands why the files do or do not appear in version control, and applies the same convention when they add a parser. If you ignore generated files, verify a clean checkout builds by deleting them locally and rebuilding, which is the fastest way to catch a case where the target references a file the build no longer produces.
After wiring up the phase, verify it end to end. Add a new image or localized string, build, and confirm the generated symbol appears without any manual generation step. Then delete the generated file, build again, and confirm SwiftGen recreates it — this proves the phase runs and the output paths are correct. Check the build log in the report navigator to see the SwiftGen phase execute and to confirm it is skipped on a no-change rebuild, which validates your input/output file lists. On CI, ensure SwiftGen is installed or resolved the same way it is locally, or the CI build will fail with command not found. Over time, keep the input and output file lists in step with swiftgen.yml: every time you add or remove a parser, update the lists so incremental builds stay both fast and correct. If builds become slow or generation runs when it should not, the file lists are the first place to look. A correctly automated SwiftGen phase is set-and-forget, but the file lists are the part that needs occasional attention as the project grows. Consider adding a brief note to your pull-request checklist reminding contributors to update the file lists whenever they touch swiftgen.yml, since that single habit prevents the most common regressions in the automated setup.
Before the Compile Sources phase. SwiftGen must write the generated Swift file before the compiler reads it, so drag the Run Script phase above Compile Sources in the Build Phases tab.
Xcode build phases run with a minimal PATH that does not include Homebrew or Mint directories. Reference the binary by absolute path with a fallback between /opt/homebrew/bin and /usr/local/bin, or export a PATH that includes both.
Declare the resource inputs in the phase's Input Files list and the generated files in Output Files. Xcode then skips the phase when nothing changed, speeding up incremental builds.
Either works. Ignore them if SwiftGen runs in CI and on every machine, or commit them so the project builds without the tool installed. Document your choice and exclude the files from linters.
Yes. The Swift Package Manager build tool plugin runs SwiftGen during the build and locates the tool itself, avoiding PATH problems. It offers less control over output location than a Run Script phase.
Install or resolve SwiftGen on CI the same way you do locally — via a committed Mintfile, the SPM plugin, or a Homebrew install step — so the build phase finds the binary and does not fail with command not found.