UiPath Documentation
uipath-cli
latest
false

UiPath CLI user guide

Last updated May 7, 2026

CI/CD recipe: GitHub Actions

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:

  1. Create an External Application in UiPath with the OR.* scopes your pipeline needs. See Authentication — Flow 2.
  2. Store the secrets in the repository (or organization/environment) settings:
    • Settings → Secrets and variables → Actions → New repository secret.
    • Add UIPATH_CLIENT_ID and UIPATH_CLIENT_SECRET as secrets.
    • Add UIPATH_TENANT as a variable (not a secret — it is not sensitive).
  3. Provision a Test Manager project and test set if you want the test job. Put TEST_SET_KEY (e.g. PROJECT:42) and PROJECT_KEY in 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@v4 pins Node to the major version specified by NODE_VERSION. The CLI requires Node 18+.
  • actions/cache@v4 caches the npm global node_modules directory, keyed on the pinned CLI version and the runner OS. When the cache hits, the install step's if ! command -v uip guard turns into a no-op.
  • Install step configures a user-local npm prefix (no sudo needed on GitHub-hosted runners), adds it to GITHUB_PATH so later steps see the uip command, 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 pack with an explicit --version built from github.run_number (monotonic, unique per run in the repo).
  • actions/upload-artifact@v4 uploads the .zip so the deploy job can download it in the next run.
  • outputs.solution_version propagates the computed version to the deploy job — the simplest way to share a value between jobs in the same workflow.

deploy job

  • Re-installs the CLIneeds: build forces the job order, but each job runs on a fresh runner. The cache should hit on the second install.
  • actions/download-artifact@v4 pulls the solution-zip into $OUTPUT_DIR.
  • Authenticate step passes secrets via the step's env: block, then uip login reads them through the env. prefix. The env.VAR_NAME prefix 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 publish and uip solution deploy run. --name uses github.run_number so each deployment is identifiable.
  • environment: uipath-prod ties 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:

  1. uip tm testset execute launches and returns an ExecutionId.
  2. uip tm wait blocks until terminal state. Exit 2 means timeout on wait (not auth failure); exit code is reported via ::error:: so it surfaces in the Actions UI.
  3. uip tm report get reads Data.Failed and the step exits 1 when 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/[email protected] \
      @uipath/[email protected] \
      @uipath/[email protected]

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 a run: command line directly, the value becomes part of the rendered script — and the step log, unless GitHub masks it. Always route secrets through env: and read them with env.VAR_NAME inside uip.
  • $GITHUB_PATH vs $PATH. Exporting PATH=… in one step does not carry to the next. Use echo "…" >> "$GITHUB_PATH" to persist a PATH addition across steps on the same runner.
  • strict shell options. Start every multi-line run: with set -euo pipefail — without it, a failed uip solution pack can be followed by a "successful" publish of a stale artifact. See Scripting patterns — strict shell options.
  • Cache false-positives. If you bump CLI_VERSION but 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 runs npm 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 cache path: and the npm config set prefix line must move together. On a non-ubuntu-latest runner, dump the real path first with - run: npm root -g and mirror whatever it reports.

See also

Was this page helpful?

Connect

Need help? Support

Want to learn? UiPath Academy

Have questions? UiPath Forum

Stay updated