Project_WL/.claude/skills/unity-cli/references/projects-templates.md

43 KiB

Projects, releases & templates — unity-cli command reference

Part of the unity-cli skill. See that skill's SKILL.md for CLI install, global flags, environment variables, exit codes, and common workflows. All global flags (--format json, --non-interactive, --yes, --proxy, …) apply to every command below.


# List registered projects
unity projects list --format json

# Register an existing project
unity projects add /path/to/MyProject

# Remove from registry (does not delete files)
unity projects remove /path/to/MyProject

# Show project details
unity projects info /path/to/MyProject --format json

# Open a project in the editor
unity open /path/to/MyProject

# Open with a specific editor version
unity open /path/to/MyProject --editor-version 6000.0.47f1

# Pass extra Unity arguments
unity open /path/to/MyProject --args "-logFile output.log"

# Pass a build target (forwarded to Unity as -buildTarget / -buildTargetGroup)
unity open /path/to/MyProject --build-target StandaloneOSX
unity open /path/to/MyProject --build-target-group Standalone

# Version shorthand (equivalent to open with --editor-version)
unity 6000.0.47f1 /path/to/MyProject

The project argument is matched against the Hub registry first (exact name or path opens immediately; a glob like "My Game*" prompts when multiple match); with no registry match it falls back to treating the argument as a filesystem path. Path matching is tolerant of casing, separator direction, and a trailing slash — resolved against real filesystem path identity — so a registered project is found even when the path is spelled differently, while two genuinely distinct case-variant folders on a case-sensitive volume stay distinct. unity open forwards --args to the Editor correctly on all platforms (including Windows).

Signed-in Editor, no Hub required. unity open starts a small background identity helper that answers the Editor's account lookup with the session unity auth login stored — your account, organization list (so Package Manager entitlements resolve), and the service addresses for your resolved --cloudEnvironment — so a Hub-less machine gets a signed-in Editor instead of an anonymous one. It steps aside whenever a real Hub is running or starting, exits on its own a few minutes after the Editor stops using it, and can be disabled with UNITY_NO_EDITOR_IDENTITY_SERVER. Signed out, the Editor just starts anonymous, as before.

Reserved flags — do NOT pass these via --args. -projectPath is managed by the command (Unity's parser is last-wins, so forwarding it would silently redirect the open to a different project), and -useHub/-hubIPC are deliberately never passed — they tell the Editor a Unity Hub manages its session, which the CLI is not. Passing any of them fails fast, before launch, with exit code 6:

Error: Forwarded argument '-useHub' conflicts with a reserved Unity flag managed by this command. Remove it from `--args`.

All three spellings Unity accepts are rejected (-useHub, --useHub, -useHub=1, case-insensitively). Everything else — -logFile <path>, -nographics, custom flags your project reads — is forwarded verbatim.

projects create

Create a project. On a TTY, prompts for any missing options (parent directory, editor version, template) and then asks whether to link the project to a Unity Cloud project — that last question defaults to No, so pressing Enter creates an unlinked project. In CI, pass --non-interactive or pipe stdin to suppress prompts and rely on stored defaults. The first positional argument is the project name; --path sets the parent directory:

unity projects create MyGame --editor-version 6000.0.47f1 --template com.unity.template.3d

# Place the project in a specific directory
unity projects create MyGame --path /path/to/projects --editor-version 6000.0.47f1

# --template also accepts a .tgz file path or a directory, not just a registered template id
unity projects create MyGame --template /path/to/template.tgz

Cloud linking during creation:

# Create and link a NEW Unity Cloud project as part of creation
unity projects create MyGame --cloud --cloud-org <id-or-name>

# Link an EXISTING cloud project instead
unity projects create MyGame --cloud-project <id-or-name>

Passing any of --cloud, --cloud-project, or --cloud-org answers the cloud question, so it is not asked again. The question is also skipped in every machine output mode (--json, --format tsv|ndjson, --quiet), under --non-interactive (or UNITY_NON_INTERACTIVE), when stdout is not a TTY, and when the current credentials cannot create a cloud project (signed out, or service-account auth) — those keep today's flag-only, default-off behaviour. Unlike the other three questions, this one can fire even when every option was supplied on the command line, so --non-interactive is what keeps a fully-specified scripted run from stopping on it. Be aware that the global currently gates only this question: the parent-directory, editor-version, and template questions still gate on terminal interactivity alone, so --non-interactive on a TTY does not make them fall back to stored defaults. For a fully unattended run on a terminal, pass --path, --editor-version, and --template as well — or use projects new, which never prompts at all.

Answering Yes never costs you the project: if the link cannot be set up (expired session, no resolvable organization), the project is still created unlinked and the reason is reported as a warning, exit 0. --cloud behaves differently and still fails outright — an explicit flag is a contract, not a suggestion.

When a project is created without a cloud link, human output ends with a line pointing at unity projects link cloud. It is human-format only: json, ndjson, and tsv output is unchanged.

For machine consumers, cloud state is reported by the presence of the cloudLinked, cloudProject, and cloudOrgSource fields on the result payload — they are emitted only when a cloud link was requested. Their absence is itself the signal that the project is unlinked; do not read cloudLinked expecting a false.

Source-control during creation — publish the new project to a fresh repository:

unity projects create MyGame \
  --vcs github \
  --git-namespace my-org \
  --git-repo my-game \
  --git-visibility private \
  --git-default-branch main \
  --git-token-stdin

Source-control flags (shared with projects link vcs): --vcs github|gitlab|uvcs|<host>, --git-namespace <name>, --git-repo <name>, --git-visibility private|public|internal (default private), --git-default-branch <name>, --git-remote-protocol https|ssh (default https), --git-description <text>, --git-token <pat> / --git-token-stdin, --no-initial-commit, --git-lfs, and --vcs-region <name> for Unity Version Control.

Flag names differ by subcommand: projects create and projects link vcs use --git-namespace / --git-repo, while projects clone (below) uses --vcs-namespace / --vcs-repo. Copy the names for the exact command you're running, and confirm with --help if unsure.

--git-remote-protocol ssh attaches the created repository's SSH remote instead of its HTTPS one. The provider REST call that creates the repository still needs the resolved PAT — SSH has no equivalent for that API call — but the local origin remote and the initial push then use the repository's git@<host>:<owner>/<repo>.git form with pure ambient SSH auth (the running ssh-agent, a repository-local core.sshCommand, or an ~/.ssh/config host alias — whichever the machine already has configured; the CLI never handles keys itself). Passing --git-token/--git-token-stdin alongside --git-remote-protocol ssh is fine: the token still authenticates the repository-creation API call, it's only the git transport that switches.

Self-hosted hosts create through a provider CLI, not REST. --vcs also accepts a bare host (e.g. --vcs gitea.example.com) for anything other than github.com/gitlab.com — there is no built-in REST client for those, so the repository is created through gh, glab, or tea, whichever is installed and already signed in for that host (checked in that order). The same fallback covers github/gitlab themselves when no REST token can be resolved: the matching CLI (gh for github, glab for gitlab) steps in if it is signed in, before the command gives up. No PAT is ever read, stored, or forwarded on a provider-CLI path. When a provider CLI created the repository, the machine-readable result carries vcs.mechanism (gh | glab | tea) naming which one — omitted for the REST path and for a [url]-form link, so existing scripted consumers see no change on github.com/gitlab.com. If nothing can create it — no REST token and no signed-in provider CLI for that host — the error explains creating the repository yourself and linking it with unity projects link vcs <path> <url>.

Where the Git token comes from. Resolution order, first hit wins: --git-token-stdin--git-tokenUNITY_GITHUB_TOKEN / UNITY_GITLAB_TOKENgit credential fill (the user's credential helper) → an interactive masked prompt. The first three are explicit per-command overrides and always beat the helper. The CLI stores no Git token at any tier, including one typed at the prompt.

Whether anyone is there to answer is decided up front. On a real terminal, a configured credential helper is free to run its own sign-in — including Git Credential Manager's browser and device-code flows — and its instructions are relayed to you rather than swallowed. Without a terminal, in CI, under a machine-readable --format, or with --non-interactive, nothing prompts at all: the command fails immediately with exit 4, naming the credential it needed and how to supply it out of band (--git-token[-stdin] or the env vars above). Interaction is judged on all three standard streams, so redirecting stderr alone can no longer leave a password prompt writing into a file while waiting on a keystroke you were never shown.

The credential lookup passes the full repository URL, so a helper that keeps one account per URL can return a different token per organization, but only when credential.useHttpPath is set, since git otherwise withholds the path from helpers:

git config --global credential.useHttpPath true

Per-project identity instead lives in the repository's own config (credential.useHttpPath, credential.username in its .git/config); projects link vcs runs the lookup inside the project, so the Hub, the CLI, and plain git all resolve the same credential. For a CI pipeline spanning several organizations, pass --git-token-stdin per invocation: the env vars hold one token per provider, and there is no per-org variant.

Per-organization scoping needs the organization to be known before the credential is looked up. projects clone always requires --vcs-namespace, so it is always scoped. projects create and projects link vcs take --git-namespace, and when it is omitted the lookup stays host-scoped, because the namespace cannot be resolved until you are authenticated and you cannot authenticate without a credential. Pass --git-namespace to target a specific organization's credential.

projects new

Create a project without any interactive prompts — resolves missing options from stored defaults, never asks the user. The first positional argument is the project name; --path sets the parent directory:

# All omitted options resolve from stored defaults
unity projects new MyGame

# Override stored defaults with explicit values
unity projects new MyGame --path /path/to/projects --editor-version 6000.0.47f1 --template com.unity.template.3d

# Open the project immediately after creation
unity projects new MyGame --open

new never links to Unity Cloud and never asks. Its human output ends with the same pointer at unity projects link cloud; machine output is unchanged. To link during creation, use projects create --cloud, or link afterwards with projects link cloud.

projects clone

Clone a remote repository and register the Unity project it contains. Works across providers:

# Clone by provider + namespace + repo
unity projects clone --vcs github --vcs-namespace my-org --vcs-repo my-game --path ./MyGame

# Check out a specific ref (branch, sha, or UVCS changeset)
unity projects clone --vcs uvcs --vcs-namespace my-org --vcs-repo my-game --ref main

# Authenticate with a personal access token (prefer stdin)
unity projects clone --vcs gitlab --vcs-namespace my-org --vcs-repo my-game --git-token-stdin

# Project lives in a subdirectory of the repo
unity projects clone --vcs github --vcs-namespace my-org --vcs-repo monorepo \
  --path ./repo --project-path packages/MyGame

# Clone an arbitrary git URL instead (HTTPS, or SSH via the standard
# git user@host:path shorthand) — no --vcs/--vcs-namespace/--vcs-repo needed
unity projects clone https://github.com/my-org/my-game.git
unity projects clone --ref develop <ssh-clone-url-for-your-host>

Options: --vcs github|gitlab|uvcs, --vcs-namespace <name>, --vcs-repo <name>, --ref <branch|sha|changeset> (an all-digit ref is treated as a Unity Version Control changeset, anything else as a branch), --path <dest> (clone destination), --project-path <subpath> (project subdirectory), --git-token <pat> / --git-token-stdin, --json. Git LFS assets are fetched as pointer files only.

The [url] form is an alternative to --vcs/--vcs-namespace/--vcs-repo, not an addition to them — passing a URL alongside any of those three flags is a bad-arguments error. --ref, --path, --project-path, --no-lfs always apply. --git-token/--git-token-stdin only apply when the URL is an explicit HTTPS URL whose host is github.com or gitlab.com — passing a token for any other host (an SSH-form or SCP-style URL, or a host that isn't github.com/gitlab.com) is a bad-arguments error, since there's nowhere for that credential to go. No token is required: with none supplied, the clone runs with whatever git auth is already set up on the machine (SSH agent, a configured credential helper, .netrc, or userinfo embedded in the URL itself) — this includes every SSH-form clone, even against github.com/gitlab.com, since the provider credential helper is HTTPS-only. Unity-project detection is always a post-clone scan of the downloaded tree for the URL form (never a provider API call, regardless of host or token) — only Git LFS credentials differ by tier: an HTTPS URL to github.com/gitlab.com with a token uses the provider-scoped LFS credential helper, everything else uses the machine's own git auth for the LFS pull too. A malformed URL, an unreachable host, a rejected/unknown SSH host key, and an authentication failure are reported as distinct errors (exit 2, 7, 3, and 3 respectively).

SSH transport policy. No SSH URL is ever rewritten to HTTPS, and no key handling happens in the CLI — a key held by a running ssh-agent is used automatically, and a repository-local core.sshCommand or an ~/.ssh/config host alias behaves identically to plain git, because the CLI only supplies its own SSH defaults when none of those (nor GIT_SSH_COMMAND/GIT_SSH) are already set — and it supplies none of them at all on an interactive terminal, deferring entirely to ssh's own prompts (a real fingerprint prompt for an unfamiliar host, a real passphrase prompt for a protected key with no agent), since a person at the terminal can answer them. Only in a non-interactive invocation (no TTY, or --non-interactive/UNITY_NON_INTERACTIVE), where no prompt could ever be answered, does the CLI supply its own defaults: an unknown host is trusted on first connect and pinned (so a later change to that host's key still fails loudly — this is not silenced), and a passphrase-protected key with no agent fails fast with an actionable error instead of hanging.

Working in a Unity Version Control workspace

Two reads are wrapped, because Unity-aware wrapping adds something, and code reviews get their own subgroup because cm cannot reach them at all. Everything else forwards to cm unchanged, because wrapping it would add nothing.

# Wrapped: the lock listing joined to YOUR pending changes. This is the one thing cm cannot
# do for you, since it has no single command that knows both.
unity vcs uvcs locks                        # who holds what, and what collides with your work
unity vcs uvcs locks --format json          # stable envelope: asOf, inLocalChanges, unlockCommand

# Wrapped: recent history in a shape a script or an agent can rely on.
unity vcs uvcs changesets --limit 50
unity vcs uvcs changesets --format json

# Straight through to cm: partial checkout, shelves, and lock mutations. cm's own flags,
# cm's own output, cm's own --help. This is the supported route, not a workaround.
unity uvcs partial configure
unity uvcs partial update /Assets/Levels
unity uvcs shelve -c "wip: lighting pass"
unity uvcs shelve --apply sh:12
unity uvcs lock list
unity uvcs lock unlock itemid:42@my-game
unity uvcs --help                           # cm's own help, forwarded verbatim

unity vcs uvcs --help says the same thing at the point of confusion: anything it does not list forwards straight to cm. unity cm <args> is the identical passthrough under cm's own name.

Lock state is shared and racy. unity vcs uvcs locks reports the state as of the moment it read it, and says so in both the human output and the asOf field. Do not cache a reading or treat one as authoritative; read again before you act. The wrapper never mutates a lock for the same reason it never paraphrases cm's flags: releasing someone else's lock is a decision about shared team state, so it stays an explicit unity uvcs lock unlock the user types.

Code review comments

unity vcs uvcs review reads the review comments a person left in the Unity Version Control GUI, and answers them. This is the one UVCS surface with no cm route at all — cm codereview manages review objects but has no comment verbs — so without these commands a reviewer's feedback is invisible to anything outside the GUI, and a human has to restate every comment in the prompt.

# Find a review. Any of --changeset, --branch or --status narrows it server-side.
unity vcs uvcs review list --branch /main --format json

# Read its comments, with the file and line each one is anchored to.
unity vcs uvcs review comments --review 42 --format json
unity vcs uvcs review comments --changeset 118      # the review attached to a changeset
unity vcs uvcs review comments --review 42 --all-activity

# Answer one, then record which change addressed it.
unity vcs uvcs review reply   --review 42 --comment 7 --body "Fixed in the next changeset."
unity vcs uvcs review resolve --review 42 --comment 7 --changeset 118

All four take the usual optional [path] operand; everything else is an option. --limit defaults to 50 and caps at 500, and the envelope's truncated tells you when there was more.

Unity Cloud only. The reviews service resolves a per-organization cloud region, so a self-hosted workspace has no reviews API — those commands refuse with VCS_UVCS_REVIEW_SELF_HOSTED and point at the GUI rather than failing obscurely.

line is one-based, and may be absent. The service anchors a comment with a zero-based line in a string field whose -1 means "not anchored to a line". The CLI does that arithmetic once: line in the envelope matches what the dashboard shows, and is null — never 0 — for a comment that is not tied to a line. Do not add one yourself.

The default is file comments, not the whole feed. The service's comments route is an activity feed of fourteen types, only four of which (Comment, Change, Question, Conversation) are things a person wrote against a file. comments returns those four; --all-activity adds the status changes, reviewer requests and timeline entries. Read activityRead alongside returned to tell "no comments" from "activity, but nothing to act on".

Resolving means naming a changeset, and is not idempotent. There is no resolved flag on the wire — the state IS the changeset the comment was applied in — so --changeset is required and is never inferred from whatever the workspace happens to be sitting on. A comment that is already resolved is refused (VCS_UVCS_REVIEW_ALREADY_RESOLVED) rather than silently re-pointed, because overwriting it would lose which change actually addressed the comment. The service performs no already-resolved check of its own, so that guard is entirely client-side: on a review with more activity than one page, where the comment falls outside the window, resolve refuses (VCS_UVCS_REVIEW_STATE_UNKNOWN) rather than writing blind — resolve those from the GUI. The guard is not atomic either: another writer can resolve between the read and the write, and the service offers no conditional write to close that window.

Writes name both ids explicitly. reply and resolve take --review and --comment and accept no --changeset/--branch selector, unlike the read leaves: they write a durable record other people read, and an indirect selector resolving to the wrong review would post into a conversation nobody looked at. Both ids come straight out of review comments. A reply body carrying control characters is refused rather than stripped, so what lands in the review is exactly what was written.

The cm client is needed for all of this. unity plugin install plastic installs it; a command that needs it and cannot find it says so and names that command.

Connecting to a self-hosted or enterprise host

GitHub Enterprise Server, self-managed GitLab, and self-hosted Gitea/Forgejo all work with the URL form of projects clone and projects link vcs (not projects create --vcs, which accepts only github, gitlab, and uvcs), but each host signs in separately: being signed in to github.com grants nothing on ghe.example.com. The first attempt against a new host fails to authenticate (exit 3) until you sign in to that host specifically; the CLI then prints the exact command for whichever mechanism your machine has.

Three ways in, any one is enough:

# 1. The provider's own CLI, scoped to the host (enables repo browsing/creation)
gh auth login --hostname ghe.example.com
glab auth login --hostname gitlab.example.com
tea login add --name work --url https://gitea.example.com

# 2. Git Credential Manager: one credential per host, no per-host setup,
#    picked up once `git credential approve` has stored one for that host

# 3. SSH: needs neither of the above; uses your SSH agent
unity projects clone <ssh-clone-url-for-your-host>

gh and glab hold one session per host and can be signed in to several simultaneously, which is why the sign-in commands are host-scoped rather than bare. tea has no default host at all: every login is a named entry for one instance URL.

The CLI reads and stores no token on any of these paths. It asks each installed provider CLI whether it holds a session for that specific host, with credential-bearing environment variables stripped from the child process: gh otherwise applies GH_ENTERPRISE_TOKEN to any non-cloud host and dials it to validate, which would leak the token to a host that merely appeared in a failing URL. Stripped, gh answers from its own config: an unconfigured host is never contacted. Side effect: authenticating purely via GH_ENTERPRISE_TOKEN (no gh auth login entry) reads as signed out, so you may be offered a sign-in you do not need. A host with no provider CLI available is not a dead end: plain git still clones and pushes, with credentials from the credential manager or the SSH agent. Note --git-token/--git-token-stdin still only apply to github.com and gitlab.com over HTTPS; for any other host, rely on one of the three paths above.

projects pin / unpin

# Pin a project to the top of the list
unity projects pin /path/to/MyProject

# Unpin
unity projects unpin /path/to/MyProject

projects size

Report a project's on-disk footprint broken down by top-level folder (Assets, Library, Packages, …) with a total, so you can see how much is regenerable build state (Library, Temp) versus source and assets:

# Size of one project (defaults to the current project when the argument is omitted)
unity projects size /path/to/MyProject

# Summarize every registered project, largest first
unity projects size --all

# Machine output — raw bytes instead of readable KB/MB/GB units
unity projects size --all --json

Human output uses readable units; --json (and --format ndjson) emit raw byte counts.

projects clean

The counterpart to projects size: deletes the regenerable folders (Library, Temp, Logs, …) to reclaim disk space. Unity rebuilds them on the next open — at the cost of a slow first import.

# Preview: what would be deleted, with sizes — deletes nothing
unity projects clean --dry-run

# Clean the current project (prompts to confirm)
unity projects clean

# Clean a project by path or registered name
unity projects clean ./MyGame

# Non-interactive: --yes is REQUIRED in a script or CI
unity projects clean MyGame --yes

The project argument defaults to the current directory and accepts a path or a registered project name. Guardrails worth relying on:

  • It refuses while the project is open in a running editor, naming the PID — cleaning Library under a live editor corrupts the session. If the CLI cannot determine whether an editor has it open, it warns and proceeds, so close editors first in automation.
  • It refuses to delete unprompted. In a non-interactive shell without -y, --yes it stops rather than deleting.
  • A path that isn't a Unity project (no ProjectVersion.txt) is rejected outright, so a mistyped path can't delete anything.

--dry-run is the safe way to size the win first; it reports what it would reclaim and exits without touching the filesystem.

projects verify

An Editor-free integrity check on the project, meant as the first step of a CI job. unity doctor answers "can this machine build?"; this answers "is this project sound?" — the version-control damage that otherwise surfaces after the expensive build step, as a confusing import error or an artifact that is wrong rather than missing:

# Verify the current project
unity projects verify

# Verify a project by path or registered name
unity projects verify ./MyGame

# CI gate: warnings fail the job too
unity projects verify --strict

# Only the checks you care about (either spelling works)
unity projects verify --check meta-missing,guid-duplicate

# Confirm the project targets the version the pipeline pins
unity projects verify --expect-editor 6000.0.30f1

# Inline annotations on the job, anchored to the offending file and line
unity projects verify --format github

Exits 0 when nothing error-severity is found, 6 otherwise. Warnings alone still exit 0--strict promotes them. Every finding carries a stable code, a severity, a project-relative path, and a remediation hint:

Code Severity Detects
META_MISSING error An asset under Assets/ with no sibling .meta. Unity assigns a fresh guid, silently breaking every reference to it.
META_ORPHAN warning A .meta whose asset no longer exists.
GUID_DUPLICATE error Two .meta files claiming the same guid — typically two branches that each added one.
CONFLICT_MARKERS error Unresolved merge markers in a .meta, ProjectSettings/*.asset, Packages/manifest.json, or packages-lock.json.
MANIFEST_INVALID error Packages/manifest.json does not parse, or a dependency version is not a string.
EDITOR_VERSION_DRIFT warning ProjectVersion.txt disagrees with --expect-editor.
PATH_UNVERIFIABLE warning A path the scan did not inspect, named so you know which subtree went unchecked. Always on — not selectable via --check.

Worth knowing:

  • Editor-version drift is opt-in. Nothing in the CLI stores a pinned version, so the check only runs when you pass --expect-editor <version> — the version your pipeline pins.
  • --format json returns the full report (findings plus an errors/warnings/filesScanned summary); --format ndjson emits one type: "finding" record per finding as it is found, then a terminal result frame. Piped stdout defaults to tsv, like the rest of the CLI.
  • Safe to paste into a public log. Finding paths are project-relative, terminal-escape-stripped, and the project root has its home directory masked.
  • It is built to scan an untrusted repository — a fork, an unreviewed pull-request branch, a third-party template. So it refuses to follow a symlinked Assets/, ProjectSettings/, or Packages/ (exit 6, PROJECTS_VERIFY_UNSCANNABLE_DIR), which would otherwise make it enumerate or read a tree outside the project into your CI log, and it bounds every file read at 16 MiB. A symlink deeper in the tree is not refused — it is skipped and reported (next bullet), since one link inside Assets/ should not fail the whole scan.
  • summary.unverifiable tells you whether the pass is complete, and each skipped path is named. Anything the scan could not inspect — a symlinked directory or file at any depth, an unreadable subtree, a walk past the depth cap, an over-budget file, a missing Assets/ — is counted there and reported as a PATH_UNVERIFIABLE warning carrying the path, so you can see which subtree went unchecked instead of only how many did. Under --strict those warnings promote to errors like any other, so a project cannot satisfy the gate by making verification impossible rather than by being sound.
  • --expect-editor must be a real Unity version. A typo like 6000.x exits 2 rather than becoming a drift warning that passes — otherwise a misconfigured pipeline would silently satisfy its own version gate.
  • data.checks lists what actually ran. EDITOR_VERSION_DRIFT is absent unless you passed --expect-editor, since without a version to compare against there is nothing to check.
  • No Editor, no license, no network, no installed editor — and it does not read asset bodies, so it stays fast on a large project.
  • Detection only. It does not repair anything; fixing meta/guid divergence needs the Editor's own asset database.
  • Names Unity's importer ignores (dot-prefixed, ~-suffixed, .tmp, cvs) are skipped, so a .gitignore or a Documentation~ folder never reports a missing .meta.

projects require

Ensure the editor version required by a project is installed, installing it if needed:

unity projects require /path/to/MyProject --yes

On a TTY with no path, prompts interactively.

projects upgrade

Upgrade a project to a different Unity editor version. --to is required:

unity projects upgrade --to 6000.0.47f1
unity projects upgrade /path/to/MyProject --to 6000.0.47f1 --yes

projects export / import

# Export the project registry to a file (or stdout if -o is omitted)
unity projects export -o projects.json

# Import a previously exported registry
unity projects import projects.json
unity projects import --input projects.json

projects exec — run a command across every registered project

Run one command in each registered project. The command runs in that project's own directory, with UNITY_PROJECT_PATH and UNITY_EDITOR_VERSION set in its environment. Everything after -- is the command:

# Every registered project
unity projects exec -- git status --short

# Only pinned projects
unity projects exec --filter pinned -- git pull

# Only Unity 6 projects, four at a time, without stopping on failures
unity projects exec --filter 'version:6000.*' --parallel 4 --continue-on-error -- npm test

# See what would run, without running it
unity projects exec --dry-run --filter 'name:My*' -- ./build.sh

# Machine-readable per-project results
unity projects exec --json -- git rev-parse HEAD

--filter is repeatable and every term must match (AND):

Term Matches
name:<glob> project name or path — a bare glob (My*) is shorthand for this
version:<glob> the project's required editor version (6000.*)
pinned / pinned:false pin state; bare pinned means pinned

Globs are path-aware, so use **/ to match inside a path: name:My* matches by project name, name:**/work/* by location.

Behavior worth knowing:

  • Projects run one at a time and the run stops at the first failure. Raise --parallel <n> for concurrency, or pass --continue-on-error to run the whole fleet regardless. With --parallel > 1, each project's output is buffered and flushed when it finishes so runs can't interleave; "stop" then means no new projects start — those already running finish.
  • Buffered output is capped at 4 MiB per project, after which it is cut short and the run warns. Sequential mode (--parallel 1) streams live and is never capped, so use it when you need the full output of a chatty command.
  • Ctrl-C stops scheduling and terminates the projects already running, then exits 130.
  • Exit code is 6 if any project failed, 2 for a usage error (unknown filter key, bad --parallel, a command not on your PATH), 0 otherwise. No matching projects is a success (exit 0) with a warning.
  • Arguments are passed to the command verbatim, not through a shell — pipes, &&, and shell globbing are not available. Put that logic in a script and exec the script.
  • In --json / --format ndjson / --format tsv, the child's own output goes to stderr so stdout stays machine-parseable.
  • --format ndjson streams one {"type":"project",…} frame per project as it settles and always closes with the standard {"type":"result",…} envelope (success, command, data, errors, warnings) — including under --dry-run.
# Open a registered project by name, fuzzy title match, or path
unity projects open MyProject
# (the top-level `unity open` is the same thing)

# --- Cloud links ---
# Connect an existing local project to a Unity Cloud project
unity projects link cloud /path/to/MyProject --cloud-org <id-or-name>
# Disconnect from its Unity Cloud project
unity projects unlink cloud /path/to/MyProject

# --- Version-control links ---
# Publish a local project to a NEW GitHub / GitLab / Unity Version Control repository
unity projects link vcs /path/to/MyProject \
  --vcs github --git-namespace my-org --git-repo my-game --git-token-stdin
# Attach to an ALREADY-EXISTING remote instead of creating one — pass its URL
unity projects link vcs /path/to/MyProject https://github.com/my-org/my-game.git
# Remove a project's git remotes (the remote repositories are NOT deleted)
unity projects unlink vcs /path/to/MyProject
# Also detach the Unity Version Control workspace
unity projects unlink vcs /path/to/MyProject --unlink-workspace

link vcs shares the source-control flag set documented under projects create. link cloud / link vcs accept --cloud-org <id-or-name> (env UNITY_CLOUD_ORG).

The [url] second operand attaches to a remote that already exists, instead of creating one — the one thing the flag form of link vcs cannot do. It is mutually exclusive with --vcs, --git-namespace, --git-repo, --git-visibility, --git-default-branch, --git-remote-protocol, --git-description, --cloud-org, and --cloud-project (all meaningless without a repository to create — the URL's own scheme already says which transport to use). --git-token[-stdin], --no-initial-commit, and --git-lfs still apply, and the same ambient-auth / Tier A rules as projects clone [url] govern whether the push uses a supplied token or the machine's own git auth.


Releases — browse Unity versions

# List recent releases
unity releases --format json

# Filter by stream (alpha, beta, lts, tech)
unity releases --stream lts --format json
unity releases --stream tech --format json
unity releases --stream beta --format json

# LTS only shorthand
unity releases --lts --format json

# Filter from a year onward
unity releases --since 2023 --format json

# Paginate
unity releases --limit 10 --skip 20 --format json

Templates

# List templates for an editor version (uses default editor if --editor is omitted)
unity templates list --editor 6000.0.47f1 --format json

# List only locally installed templates
unity templates list --editor 6000.0.47f1 --installed --format json

# Filter by type (core, learning, sample, custom, new, all) — case-insensitive
unity templates list --editor 6000.0.47f1 --type core --format json
unity templates list --editor 6000.0.47f1 --type learning --format json
unity templates list --editor 6000.0.47f1 --type sample --format json
unity templates list --editor 6000.0.47f1 --type new --format json
unity templates list --editor 6000.0.47f1 --type all --format json  # no-op, returns everything

# List only user-generated (custom) templates
unity templates list --editor 6000.0.47f1 --custom --format json
# --type custom is an alias for --custom
unity templates list --editor 6000.0.47f1 --type custom --format json

# --custom and --type are mutually exclusive — using both is an error (exit 1)

# Show template details
unity templates info com.unity.template.3d --editor 6000.0.47f1 --format json

# Create a custom template from an existing Unity project
# --name and --display-name are REQUIRED
unity templates create /path/to/MyProject \
  --name com.myorg.template.mytemplate \
  --display-name "My Template"

# With all optional options
unity templates create /path/to/MyProject \
  --name com.myorg.template.mytemplate \
  --display-name "My Template" \
  --description "A starting point for our projects" \
  --template-version 1.0.0 \
  --output /path/to/templates/dir \
  --keep-embedded-packages \
  --keep-project-settings \
  --overwrite

# JSON output (includes path to created .tgz archive)
unity templates create /path/to/MyProject \
  --name com.myorg.template.mytemplate \
  --display-name "My Template" \
  --json

# NDJSON streaming — emits progress frames then a result frame
unity templates create /path/to/MyProject \
  --name com.myorg.template.mytemplate \
  --display-name "My Template" \
  --format ndjson

templates create key notes:

  • --name must be a valid npm package name (e.g. com.myorg.template.mytemplate)
  • --output overrides the Hub-configured user templates directory
  • --overwrite replaces an existing archive of the same name without error
  • On success, prints the path to the created .tgz archive
  • Created templates appear in unity templates list --editor <v> --custom

templates pack — portable archive, not a registered template. create installs into the Hub-configured user templates directory so the template shows up in templates list --custom; pack writes a standalone .tgz to a file path you choose and registers nothing. Reach for pack when the archive is an artifact to check in, attach to a release, or hand to someone else.

# Pack a project into a portable template archive (--output is REQUIRED)
unity templates pack ./MyProject \
  --output ./my-template.tgz \
  --name com.myorg.template.mytemplate \
  --display-name "My Template"

# Minimal form — prompts for name and display name on a TTY
unity templates pack ./MyProject --output ./my-template.tgz

# Replace an existing archive, with machine output
unity templates pack ./MyProject --output ./my-template.tgz --overwrite --json

templates pack key notes:

  • --output <file> is a file path, not a directory, and is required
  • --name and --display-name are required; on a TTY they're prompted for when omitted, so pass both in CI
  • Use --template-version, not --version — the latter collides with the global -V, --version flag
  • The output path may not be inside the project being packed; that's rejected, so the archive can't include itself
  • An existing output file is an error unless --overwrite is passed
  • --keep-embedded-packages and --keep-project-settings retain content that is otherwise stripped
  • Consumable directly by project creation: unity projects create MyGame --template ./my-template.tgz
# Delete a user-generated custom template (prompts for confirmation)
unity templates delete com.myorg.template.mytemplate --editor 6000.0.47f1

# Skip the confirmation prompt (CI-friendly)
unity templates delete com.myorg.template.mytemplate --editor 6000.0.47f1 --yes

# JSON output
unity templates delete com.myorg.template.mytemplate --editor 6000.0.47f1 --yes --json

templates delete key notes:

  • Only user-generated templates (created via Hub UI or templates create) can be deleted
  • Attempting to delete a built-in Unity template exits with a descriptive error (exit 6)
  • Attempting to delete a template that doesn't exist exits with a descriptive error (exit 6)
  • In interactive mode, prompts for confirmation before deleting; use --yes to skip
  • On success, the template no longer appears in unity templates list --editor <v> --custom
# Get/set/reset the default storage path for custom templates
# Print current configured templates location
unity templates location

# Set a new default templates directory (must exist as a directory)
unity templates location --set /path/to/templates

# Reset templates location to the Hub default
unity templates location --reset

# JSON output for any variant
unity templates location --json
unity templates location --set /path/to/templates --json
unity templates location --reset --json

templates location key notes:

  • --set and --reset are mutually exclusive (using both is an error)
  • --set validates that the path exists and is a directory (exits 2 if not)
  • --reset restores the Hub default templates path
  • JSON output: { "path": "..." } inside the standard envelope
# Edit a user-generated (custom) template's metadata
# At least one of --display-name, --description, --template-version,
# --preview-image, --remove-preview-image is required
unity templates edit com.myorg.template.mytemplate --editor 6000.0.47f1 --display-name "My Updated Template"

# Update multiple fields at once
unity templates edit com.myorg.template.mytemplate \
  --editor 6000.0.47f1 \
  --display-name "My Updated Template" \
  --description "A new description for the template" \
  --template-version 1.1.0

# Replace / remove preview image
unity templates edit com.myorg.template.mytemplate --editor 6000.0.47f1 --preview-image /path/to/image.png
unity templates edit com.myorg.template.mytemplate --editor 6000.0.47f1 --remove-preview-image

# JSON / NDJSON output (--yes required because these are non-interactive)
unity templates edit com.myorg.template.mytemplate --editor 6000.0.47f1 --display-name "Updated" --yes --json

templates edit key notes:

  • Only works on user-generated (custom) templates; built-in templates cannot be edited
  • Use --editor to specify which editor version's template list to search, or omit to use the stored default
  • --preview-image <path> resolves to an absolute path before passing to the service
  • --remove-preview-image is only applied when no valid --preview-image path is given; if both are passed with a valid image path, the new image wins and --remove-preview-image is ignored
  • On success (human format), prints the updated template's display name