- Overview
- Get started
- Concepts
- Using UiPath CLI
- How-to guides
- CI/CD recipes
- Azure DevOps
- GitHub Actions
- Jenkins
- GitLab CI
- Command reference
- Overview
- Exit codes
- Global options
- uip codedagent
- uip docsai
- add-test-data-entity
- add-test-data-queue
- add-test-data-variation
- analyze
- build
- create-project
- diff
- find-activities
- get-analyzer-rules
- get-default-activity-xaml
- get-errors
- get-manual-test-cases
- get-manual-test-steps
- get-versions
- get-workflow-example
- indicate-application
- indicate-element
- inspect-package
- install-data-fabric-entities
- install-or-update-packages
- list-data-fabric-entities
- list-workflow-examples
- pack
- restore
- run-file
- search-templates
- start-studio
- stop-execution
- uia
- uip traces
- Migration
- Reference & support
UiPath CLI user guide
This page gives you a complete .github/workflows/deploy.yml that installs the CLI, authenticates with an External Application, packs and publishes a UiPath Solution, deploys it to Orchestrator, and runs a Test Manager suite against the deployment. Drop it into your repo, add two secrets, and it runs.
For the underlying principles — auth, caching, tool pre-install, version pinning — see How-to: deploy to Orchestrator from CI. This page focuses on the GitHub Actions syntax.
Prerequisites
Before copying the YAML below:
- Create an External Application in UiPath with the
OR.*scopes your pipeline needs. See Authentication — Flow 2. - Store the secrets in the repository (or organization/environment) settings:
- Settings → Secrets and variables → Actions → New repository secret.
- Add
UIPATH_CLIENT_IDandUIPATH_CLIENT_SECRETas secrets. - Add
UIPATH_TENANTas a variable (not a secret — it is not sensitive).
- Provision a Test Manager project and test set if you want the test job. Put
TEST_SET_KEY(e.g.PROJECT:42) andPROJECT_KEYin the repo variables. See How-to: run tests from the CLI.
.github/workflows/deploy.yml
name: Deploy UiPath Solution
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
env:
CLI_VERSION: '1.0.0'
NODE_VERSION: '20'
SOLUTION_NAME: 'my-solution'
SOLUTION_DIR: './my-solution'
OUTPUT_DIR: './dist'
jobs:
build:
name: Pack Solution
runs-on: ubuntu-latest
outputs:
solution_version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Compute version
id: version
run: echo "version=1.2.0-ci.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache npm global (@uipath/cli)
uses: actions/cache@v4
with:
path: ~/.npm-global/lib/node_modules
key: uip-${{ env.CLI_VERSION }}-${{ runner.os }}
- name: Install UiPath CLI and tools
shell: bash
run: |
set -euo pipefail
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo "$HOME/.npm-global/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
uip tools install \
@uipath/orchestrator-tool \
@uipath/solution-tool \
@uipath/test-manager-tool
fi
uip --version
- name: Pack Solution
shell: bash
run: |
set -euo pipefail
mkdir -p "$OUTPUT_DIR"
uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
--name "$SOLUTION_NAME" \
--version "${{ steps.version.outputs.version }}"
- uses: actions/upload-artifact@v4
with:
name: solution-zip
path: ${{ env.OUTPUT_DIR }}/${{ env.SOLUTION_NAME }}.${{ steps.version.outputs.version }}.zip
deploy:
name: Publish and deploy
needs: build
runs-on: ubuntu-latest
environment: uipath-prod # attach approval gates here if needed
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache npm global (@uipath/cli)
uses: actions/cache@v4
with:
path: ~/.npm-global/lib/node_modules
key: uip-${{ env.CLI_VERSION }}-${{ runner.os }}
- name: Install UiPath CLI and tools
shell: bash
run: |
set -euo pipefail
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo "$HOME/.npm-global/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
uip tools install \
@uipath/orchestrator-tool \
@uipath/solution-tool \
@uipath/test-manager-tool
fi
- uses: actions/download-artifact@v4
with:
name: solution-zip
path: ${{ env.OUTPUT_DIR }}
- name: Authenticate
shell: bash
env:
UIPATH_CLIENT_ID: ${{ secrets.UIPATH_CLIENT_ID }}
UIPATH_CLIENT_SECRET: ${{ secrets.UIPATH_CLIENT_SECRET }}
UIPATH_TENANT: ${{ vars.UIPATH_TENANT }}
run: |
set -euo pipefail
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
- name: Publish to tenant feed
shell: bash
run: |
set -euo pipefail
uip solution publish \
"$OUTPUT_DIR/$SOLUTION_NAME.${{ needs.build.outputs.solution_version }}.zip"
- name: Deploy to Orchestrator
shell: bash
run: |
set -euo pipefail
uip solution deploy run \
--name "${SOLUTION_NAME}-${{ github.run_number }}" \
--package-name "$SOLUTION_NAME" \
--package-version "${{ needs.build.outputs.solution_version }}" \
--folder-name MySolution \
--folder-path Shared
test:
name: Run Test Manager suite
needs: deploy
if: ${{ vars.TEST_SET_KEY != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache npm global (@uipath/cli)
uses: actions/cache@v4
with:
path: ~/.npm-global/lib/node_modules
key: uip-${{ env.CLI_VERSION }}-${{ runner.os }}
- name: Install CLI and authenticate
shell: bash
env:
UIPATH_CLIENT_ID: ${{ secrets.UIPATH_CLIENT_ID }}
UIPATH_CLIENT_SECRET: ${{ secrets.UIPATH_CLIENT_SECRET }}
UIPATH_TENANT: ${{ vars.UIPATH_TENANT }}
run: |
set -euo pipefail
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo "$HOME/.npm-global/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
uip tools install @uipath/test-manager-tool
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
- name: Launch, wait, verify
shell: bash
env:
TEST_SET_KEY: ${{ vars.TEST_SET_KEY }}
PROJECT_KEY: ${{ vars.PROJECT_KEY }}
run: |
set -euo pipefail
EXECUTION_ID=$(uip tm testset execute \
--test-set-key "$TEST_SET_KEY" \
--output-filter "Data.ExecutionId" \
--output plain)
echo "started execution $EXECUTION_ID"
if ! uip tm wait \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--timeout 1800; then
code=$?
case "$code" in
2) echo "::error::test run did not finish within 30 minutes"; exit 2 ;;
*) echo "::error::wait failed (exit $code)"; exit "$code" ;;
esac
fi
uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY"
FAILED=$(uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--output-filter "Data.Failed" \
--output plain)
if [ "$FAILED" -gt 0 ]; then
echo "::error::$FAILED test case(s) failed"
exit 1
fi
name: Deploy UiPath Solution
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
env:
CLI_VERSION: '1.0.0'
NODE_VERSION: '20'
SOLUTION_NAME: 'my-solution'
SOLUTION_DIR: './my-solution'
OUTPUT_DIR: './dist'
jobs:
build:
name: Pack Solution
runs-on: ubuntu-latest
outputs:
solution_version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Compute version
id: version
run: echo "version=1.2.0-ci.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache npm global (@uipath/cli)
uses: actions/cache@v4
with:
path: ~/.npm-global/lib/node_modules
key: uip-${{ env.CLI_VERSION }}-${{ runner.os }}
- name: Install UiPath CLI and tools
shell: bash
run: |
set -euo pipefail
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo "$HOME/.npm-global/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
uip tools install \
@uipath/orchestrator-tool \
@uipath/solution-tool \
@uipath/test-manager-tool
fi
uip --version
- name: Pack Solution
shell: bash
run: |
set -euo pipefail
mkdir -p "$OUTPUT_DIR"
uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
--name "$SOLUTION_NAME" \
--version "${{ steps.version.outputs.version }}"
- uses: actions/upload-artifact@v4
with:
name: solution-zip
path: ${{ env.OUTPUT_DIR }}/${{ env.SOLUTION_NAME }}.${{ steps.version.outputs.version }}.zip
deploy:
name: Publish and deploy
needs: build
runs-on: ubuntu-latest
environment: uipath-prod # attach approval gates here if needed
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache npm global (@uipath/cli)
uses: actions/cache@v4
with:
path: ~/.npm-global/lib/node_modules
key: uip-${{ env.CLI_VERSION }}-${{ runner.os }}
- name: Install UiPath CLI and tools
shell: bash
run: |
set -euo pipefail
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo "$HOME/.npm-global/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
uip tools install \
@uipath/orchestrator-tool \
@uipath/solution-tool \
@uipath/test-manager-tool
fi
- uses: actions/download-artifact@v4
with:
name: solution-zip
path: ${{ env.OUTPUT_DIR }}
- name: Authenticate
shell: bash
env:
UIPATH_CLIENT_ID: ${{ secrets.UIPATH_CLIENT_ID }}
UIPATH_CLIENT_SECRET: ${{ secrets.UIPATH_CLIENT_SECRET }}
UIPATH_TENANT: ${{ vars.UIPATH_TENANT }}
run: |
set -euo pipefail
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
- name: Publish to tenant feed
shell: bash
run: |
set -euo pipefail
uip solution publish \
"$OUTPUT_DIR/$SOLUTION_NAME.${{ needs.build.outputs.solution_version }}.zip"
- name: Deploy to Orchestrator
shell: bash
run: |
set -euo pipefail
uip solution deploy run \
--name "${SOLUTION_NAME}-${{ github.run_number }}" \
--package-name "$SOLUTION_NAME" \
--package-version "${{ needs.build.outputs.solution_version }}" \
--folder-name MySolution \
--folder-path Shared
test:
name: Run Test Manager suite
needs: deploy
if: ${{ vars.TEST_SET_KEY != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache npm global (@uipath/cli)
uses: actions/cache@v4
with:
path: ~/.npm-global/lib/node_modules
key: uip-${{ env.CLI_VERSION }}-${{ runner.os }}
- name: Install CLI and authenticate
shell: bash
env:
UIPATH_CLIENT_ID: ${{ secrets.UIPATH_CLIENT_ID }}
UIPATH_CLIENT_SECRET: ${{ secrets.UIPATH_CLIENT_SECRET }}
UIPATH_TENANT: ${{ vars.UIPATH_TENANT }}
run: |
set -euo pipefail
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo "$HOME/.npm-global/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
uip tools install @uipath/test-manager-tool
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
- name: Launch, wait, verify
shell: bash
env:
TEST_SET_KEY: ${{ vars.TEST_SET_KEY }}
PROJECT_KEY: ${{ vars.PROJECT_KEY }}
run: |
set -euo pipefail
EXECUTION_ID=$(uip tm testset execute \
--test-set-key "$TEST_SET_KEY" \
--output-filter "Data.ExecutionId" \
--output plain)
echo "started execution $EXECUTION_ID"
if ! uip tm wait \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--timeout 1800; then
code=$?
case "$code" in
2) echo "::error::test run did not finish within 30 minutes"; exit 2 ;;
*) echo "::error::wait failed (exit $code)"; exit "$code" ;;
esac
fi
uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY"
FAILED=$(uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--output-filter "Data.Failed" \
--output plain)
if [ "$FAILED" -gt 0 ]; then
echo "::error::$FAILED test case(s) failed"
exit 1
fi
Walkthrough
build job
actions/setup-node@v4pins Node to the major version specified byNODE_VERSION. The CLI requires Node 18+.actions/cache@v4caches the npm globalnode_modulesdirectory, keyed on the pinned CLI version and the runner OS. When the cache hits, the install step'sif ! command -v uipguard turns into a no-op.- Install step configures a user-local npm prefix (no
sudoneeded on GitHub-hosted runners), adds it toGITHUB_PATHso later steps see theuipcommand, and installs the CLI and pre-installs the three tools used in this pipeline. See Installing UiPath CLI — CI/CD. - Pack step invokes
uip solution packwith an explicit--versionbuilt fromgithub.run_number(monotonic, unique per run in the repo). actions/upload-artifact@v4uploads the.zipso thedeployjob can download it in the next run.outputs.solution_versionpropagates the computed version to thedeployjob — the simplest way to share a value between jobs in the same workflow.
deploy job
- Re-installs the CLI —
needs: buildforces the job order, but each job runs on a fresh runner. The cache should hit on the second install. actions/download-artifact@v4pulls thesolution-zipinto$OUTPUT_DIR.- Authenticate step passes secrets via the step's
env:block, thenuip loginreads them through theenv.prefix. Theenv.VAR_NAMEprefix is the supported way to keep a secret out of the command line — see Authentication — the env.VAR_NAME prefix. Do not write--client-secret "${{ secrets.UIPATH_CLIENT_SECRET }}"— that embeds the value into the rendered command and into the step log. - Publish + deploy steps use
uip solution publishanduip solution deploy run.--nameusesgithub.run_numberso each deployment is identifiable. environment: uipath-prodties the job to a deployment environment. Environments are where you configure required reviewers, wait timers, and deployment branches — keep them in the UI and leave the YAML declarative.
test job
Runs only if the TEST_SET_KEY repo variable is set — the if: guard skips it otherwise. The shell block is the canonical launch → wait → verify pattern from How-to: run tests from the CLI:
uip tm testset executelaunches and returns anExecutionId.uip tm waitblocks until terminal state. Exit2means timeout onwait(not auth failure); exit code is reported via::error::so it surfaces in the Actions UI.uip tm report getreadsData.Failedand the step exits1when anything failed.
Common variations
Pin tool versions too
By default uip tools install picks the latest tool version in the CLI's MAJOR.MINOR line. For strict patch-level reproducibility:
- name: Install pinned tools
run: |
uip tools install \
@uipath/orchestrator-tool@1.0.2 \
@uipath/solution-tool@1.0.2 \
@uipath/test-manager-tool@1.0.2
- name: Install pinned tools
run: |
uip tools install \
@uipath/orchestrator-tool@1.0.2 \
@uipath/solution-tool@1.0.2 \
@uipath/test-manager-tool@1.0.2
Promote across environments
Add a second deploy job that depends on the first, with different secrets:
deploy-stage:
needs: build
environment: uipath-stage
# …same steps as `deploy`, using ${{ secrets.UIPATH_STAGE_CLIENT_ID }} etc.
deploy-prod:
needs: deploy-stage
environment: uipath-prod # add "Required reviewers" in the environment settings
# …same steps as `deploy`, using ${{ secrets.UIPATH_PROD_CLIENT_ID }} etc.
deploy-stage:
needs: build
environment: uipath-stage
# …same steps as `deploy`, using ${{ secrets.UIPATH_STAGE_CLIENT_ID }} etc.
deploy-prod:
needs: deploy-stage
environment: uipath-prod # add "Required reviewers" in the environment settings
# …same steps as `deploy`, using ${{ secrets.UIPATH_PROD_CLIENT_ID }} etc.
Approval gates live in the environment settings — the needs: chain enforces order. See How-to: pack and publish a Solution — promote one package across tenants.
Rollback
Add a workflow_dispatch input and a guarded job that re-deploys a specific version:
on:
workflow_dispatch:
inputs:
rollback_version:
description: 'Version to roll back to (e.g. 1.1.9)'
required: false
type: string
jobs:
rollback:
if: ${{ github.event.inputs.rollback_version != '' }}
runs-on: ubuntu-latest
# …install + auth…
steps:
- name: Re-deploy previous version
run: |
uip solution deploy run \
--name "${SOLUTION_NAME}-rollback" \
--package-name "$SOLUTION_NAME" \
--package-version "${{ github.event.inputs.rollback_version }}" \
--folder-name MySolution \
--folder-path Shared
on:
workflow_dispatch:
inputs:
rollback_version:
description: 'Version to roll back to (e.g. 1.1.9)'
required: false
type: string
jobs:
rollback:
if: ${{ github.event.inputs.rollback_version != '' }}
runs-on: ubuntu-latest
# …install + auth…
steps:
- name: Re-deploy previous version
run: |
uip solution deploy run \
--name "${SOLUTION_NAME}-rollback" \
--package-name "$SOLUTION_NAME" \
--package-version "${{ github.event.inputs.rollback_version }}" \
--folder-name MySolution \
--folder-path Shared
Trigger with Actions → Deploy UiPath Solution → Run workflow and enter the rollback version. For destructive rollback (uninstall + solution packages delete), see How-to: pack and publish a Solution — rollback.
Skip tests
Set (or leave unset) the TEST_SET_KEY repo variable. The if: ${{ vars.TEST_SET_KEY != '' }} guard skips the whole job.
Common pitfalls
${{ secrets.X }}interpolation. It substitutes at the step-rendering layer. If you put a secret into arun:command line directly, the value becomes part of the rendered script — and the step log, unless GitHub masks it. Always route secrets throughenv:and read them withenv.VAR_NAMEinsideuip.$GITHUB_PATHvs$PATH. ExportingPATH=…in one step does not carry to the next. Useecho "…" >> "$GITHUB_PATH"to persist a PATH addition across steps on the same runner.strict shell options. Start every multi-linerun:withset -euo pipefail— without it, a faileduip solution packcan be followed by a "successful" publish of a stale artifact. See Scripting patterns — strict shell options.- Cache false-positives. If you bump
CLI_VERSIONbut the cache key does not include it, you will keep using the old CLI. The key in this YAML includes${{ env.CLI_VERSION }}exactly for this reason. - Cache path is coupled to the npm prefix. The cache block uses
~/.npm-global/lib/node_modules, which only works because the install step runsnpm config set prefix "$HOME/.npm-global". If you change the prefix (e.g. on a self-hosted Windows runner where the convention is%APPDATA%\npm\node_modules), both the cachepath:and thenpm config set prefixline must move together. On a non-ubuntu-latest runner, dump the real path first with- run: npm root -gand mirror whatever it reports.
See also
- How-to: deploy to Orchestrator from CI — platform-agnostic guidance.
- How-to: pack and publish a Solution — versioning and rollback.
- How-to: run tests from the CLI — the launch → wait → verify pattern.
- CI/CD recipe: Azure Pipelines, Jenkins, GitLab CI — the same pipeline in other platforms.