Unity CLI Explained: What Changes for CI/CD and Testing

What the Unity CLI is, how to install it, the commands that matter for CI/CD and testing, and what it replaced in our builds.

← All articles

TL;DR

  • The Unity CLI is a single unity binary, announced on 20 July 2026, that installs editors and modules, opens projects, runs builds and tests, and handles sign-in from a terminal, with no Unity Hub GUI.
  • It replaces the -batchmode -executeMethod calls we used for years in GitHub Actions, Bitbucket Pipelines, and GitLab CI. Output can be JSON or TSV, and the exit codes are documented (0 for success, 8 when unity test ran and tests failed, 7 for a retryable service error).
  • You install it with Unity's install script, Homebrew (brew install --cask unity-cli), winget (winget install Unity.CLI), or apt and dnf.
  • An optional package, com.unity.pipeline, lets you register your own commands with a [CliCommand] attribute and run them, or arbitrary C#, against an Editor that is already open, with no recompile. Unity's docs label the CLI experimental, so commands and output can still change.
  • We use it for CI/CD builds, a test command that keeps the Editor open, and screenshot capture. Unity also pitches it as infrastructure for AI agents; that is not the angle this post takes, and the CLI is useful without it.

The Unity CLI is Unity's standalone command-line tool for installing editors, opening projects, and running builds and tests from a terminal or a CI job. Unity announced it on 20 July 2026. I ran it against our actual build setup in the days after, and this post covers what it replaces, how to install it, the commands that matter for CI/CD, and the parts that still need care.

How we used to do this

For as long as I can remember, driving Unity from a script meant the Editor's command-line arguments: -batchmode -quit -projectPath, and if you wanted anything beyond a default build, -executeMethod pointing at a static method in your project. A typical CI build call looked something like this:

Unity -batchmode -quit -nographics \
  -projectPath "$PWD" \
  -buildTarget Android \
  -executeMethod BuildScript.PerformBuild \
  -logFile -

That worked, but every piece of it was a workaround. There's no structured result, just a log stream, so your CI script greps for a string like "Build succeeded" or checks whether a specific file exists afterward. Exit codes were inconsistent enough across platforms that most pipelines we've built double-check with a log grep anyway, on top of the exit code. Running tests meant the same pattern with different flags:

Unity -batchmode -quit -projectPath "$PWD" \
  -runTests -testPlatform EditMode \
  -testResults results.xml \
  -logFile -

which at least produced NUnit XML that CI test reporters could parse, so testing was always the least painful part of this. Custom commands were the worst of the three: every one needed a static method baked into the project ahead of time, and every single invocation cost a full Editor boot plus a domain reload, often a minute or more before your actual code ran. If you wanted to run five small checks, you paid that startup cost five times.

We've run this workflow across GitHub Actions, Bitbucket Pipelines, and GitLab CI on different projects over the years, and the shape barely changes platform to platform. All three need a self-hosted runner with a licensed Unity install (none of them offer Unity in their own shared compute), and the YAML wrapper around the batchmode call is nearly identical whichever one you're on. The actual pain was never the CI platform, it was always what happened once Unity itself started up. We've written before about the Gradle build failures that show up once the build itself is running, and the log-scraping habit is the same root cause there: no structured way to ask Unity what happened.

What the Unity CLI is and how to install it

The CLI ships as a single self-contained binary. Unity's CLI guide recommends an install script (curl -fsSL https://unity.com/install.sh | bash on macOS and Linux, a PowerShell script on Windows), and the same guide lists package-manager installs:

brew install --cask unity-cli       # macOS, Homebrew
winget install Unity.CLI            # Windows
sudo apt update && sudo apt install unity-cli   # Debian and Ubuntu
sudo dnf install unity-cli          # Fedora and RHEL

The docs list macOS 14 or later, Windows 10 21H1 or later, and Linux distributions with glibc 2.34 or later, such as Ubuntu 22.04 and RHEL 9. You update a script-installed CLI in place with unity self-update (unity upgrade still works as an alias); a package-managed install updates through its package manager instead. Either way, the CLI is the only thing a build agent needs installed to begin, because it installs the Editor itself.

The practical difference shows up immediately: because it's a native binary rather than a wrapper around the Editor, it starts in a fraction of the time the old Unity Hub headless path took. That gap is small per call, but a CI pipeline or a script makes dozens of calls a day, and it adds up fast.

Installing an editor with the modules you need is one line:

unity install 6000.2.10f1 -m android ios webgl

From there the commands read the way you'd expect: unity editors lists what's installed, unity open launches a project with the right editor already resolved, unity auth login signs you in, and unity modules list 6000.2.10f1 tells you which module IDs a given version actually has.

The Unity CLI commands that matter for CI/CD and testing

This table comes from Unity's CLI reference, checked on 25 October 2026. Run unity --help for the full list on your installed version; the reference lists dozens of top-level commands.

Command What it does Where it fits in a pipeline
unity install Installs an Editor version, optionally with modules Build agent setup, pinned to one Editor version
unity editors Lists releases and installed Editors Checking what a runner or a teammate's machine has
unity open Opens a project in the Editor version it declares Local work
unity build Builds a project in batch mode with CI-friendly flags, including Android signing and export options The build step, in place of the -batchmode -executeMethod call
unity test Runs Edit Mode and Play Mode tests through the Editor test runner and writes an NUnit report The test step; exit code 8 means the tests ran and some failed
unity auth login Signs in with a browser flow Local machines; a build agent uses a service account instead (below)
unity doctor Prints a diagnostic snapshot and environment health checks First step on a new machine or a failing agent
unity pipeline install Installs the Unity Pipeline package that connects the CLI to the Editor One-off setup before custom commands
unity command Forwards a command to a connected Editor, or lists the commands it exposes Running your own [CliCommand] methods
unity self-update Updates the CLI binary Keeping agents on the same CLI version

The reference lists unity build and unity test as built-in commands, so a custom [CliCommand] is only needed for steps they don't cover. Our own pipelines wrap an existing build method in one, as described below.

Built for automation

This is the part that actually fixes our old CI setup. Every command can emit structured output instead of a log stream:

unity editors --format json
unity editors --format tsv

Errors go to stderr, and exit codes follow a table in Unity's reference: 0 for success, 1 for a general error, 2 for a usage error, 3 for an authentication failure, 6 when the command's main operation failed, 7 when a Unity service couldn't be reached, 8 when unity test ran and some tests failed, and 130 for Ctrl+C. The split matters for scripts: Unity documents 7 as safe to retry and 6 and 8 as terminal, so a pipeline can retry a flaky network call without ever re-running a failed test. That's a script's entire job made simpler, no more grepping a log file for a string that might change wording in the next Unity release.

Unattended installs skip every prompt:

unity install 6000.2.10f1 -m android ios --accept-eula --yes

and on a build agent, authentication happens through a service account: set UNITY_SERVICE_ACCOUNT_ID and UNITY_SERVICE_ACCOUNT_SECRET to the account's key ID and secret key, with no browser and no manual step. unity doctor prints a diagnostic snapshot and environment health checks, which is the kind of thing that used to eat half an afternoon of a new contractor's onboarding.

Talking to a running Editor

Managing installs is only half of it. An experimental companion package, com.unity.pipeline, lets a running Editor receive commands from the CLI, entirely on your own machine, on Unity 6.0 LTS and newer. Adding it to a project is one command:

unity pipeline install

Once it's there, any static method in your project becomes a terminal-callable command by adding a [CliCommand] attribute, with [CliArg] on its parameters. Here's a small one we added for our own use, a screenshot command:

using Unity.Pipeline.Commands;
using UnityEngine;

public static class DevPipelineCommands
{
    [CliCommand("screenshot", "Capture the Game view and save it to disk")]
    public static string Screenshot(
        [CliArg("path", "Where to save the PNG", Required = true)] string path)
    {
        ScreenCapture.CaptureScreenshot(path);
        return path;
    }
}

No registration step; the package finds it. unity command screenshot --path Screenshots/build-42.png runs it against the connected Editor and hands back the result. There's also unity command eval, which compiles and runs an arbitrary C# expression against a live Editor with no recompile:

unity command eval "return Application.version;"

It's gated behind a security token for obvious reasons, and the same mechanism can target a running development build instead of the Editor with a --runtime flag.

Unity's own announcement leans hard on all of this being the foundation for AI agents operating the Editor directly. I'm leaving that part alone here. Not everyone building games wants an agent driving their Editor, and the automation and the fast local REPL are worth having regardless of who or what is calling them.

What we're actually using it for

CI/CD. The batchmode calls in our GitHub Actions and GitLab CI jobs are being replaced with unity install plus a [CliCommand] wrapping our existing build method. The build agent no longer needs a pre-baked Unity install; the pipeline installs exactly the version and modules it needs and reports back in JSON, which our existing pipeline scripts can consume directly instead of parsing log output.

TDD. We wrapped Unity's TestRunnerAPI in our own [CliCommand("run-tests", ...)]. The difference isn't the test framework, it's that the Editor stays open between runs instead of cold-starting for every batchmode invocation. For an actual test-driven loop, where you want results back in seconds after a small change, that's the difference between it being practical and not.

Automatic screenshots. The screenshot command above runs against a connected Editor or a live dev build, so we can capture a consistent set of reference images per scene on every build, for visual regression checks, without a human opening the project and clicking through it.

Other places this is worth trying

A few other uses worth considering, even if we haven't built all of them ourselves yet:

  • Multi-platform build matrices where each job installs only the editor version and modules it needs, instead of maintaining a separate Docker image per Unity version.
  • unity doctor as a first step in onboarding a new machine or a contractor, instead of a checklist of manual Unity Hub steps.
  • Smoke-testing a nightly dev build by hitting a --runtime command after it launches, instead of a person opening the build to confirm it didn't crash on startup.
  • Auditing which Unity versions and modules are actually installed across a small team's machines with unity editors, before a version mismatch causes a "works on my machine" bug.

Where to start

Unity's announcement post has the background and a short demo video; the CLI documentation and the command reference cover every command. If you're untangling a Unity build pipeline or setting one up from scratch, that's the kind of CI/CD and DevOps work we do alongside Unity engineering day to day.

Untangling a Unity Build Pipeline?

Contact Us