UiPath Documentation
uipath-cli
latest
false

UiPath CLI user guide

Last updated May 7, 2026

CI/CD recipe: Jenkins

This page gives you a complete declarative Jenkinsfile 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. Place it in the root of your repo, create a matching Jenkins credentials entry, and the pipeline 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 Jenkins syntax, including the bits (withCredentials, stash / unstash, agent selection) that are Jenkins-specific.

Prerequisites

Before copying the Jenkinsfile:

  1. Create an External Application in UiPath with the OR.* scopes your pipeline needs. See Authentication — Flow 2.
  2. Store the secrets in Jenkins credentials:
    • Manage Jenkins → Credentials → System → Global credentials (unrestricted).
    • Add two Secret text entries with IDs UIPATH_CLIENT_ID and UIPATH_CLIENT_SECRET.
    • Add a Plain text or Secret text entry with ID UIPATH_TENANT (the tenant name; not strictly secret, but credentials are the most portable place).
  3. Agent requirements. The pipeline assumes an agent labelled linux with Node.js 18+ and npm on the PATH. See the alternative agent options below if you run on Windows or in a container.
  4. Provision a Test Manager project and test set if you want the test stage. Add TEST_SET_KEY and PROJECT_KEY as additional credentials (or as pipeline parameters).

Jenkinsfile

pipeline {
  agent none

  options {
    timestamps()
    buildDiscarder(logRotator(numToKeepStr: '30'))
    disableConcurrentBuilds()
  }

  environment {
    CLI_VERSION      = '1.0.0'
    SOLUTION_NAME    = 'my-solution'
    SOLUTION_DIR     = 'my-solution'
    OUTPUT_DIR       = 'dist'
    SOLUTION_VERSION = "1.2.0-ci.${env.BUILD_NUMBER}"

    // npm global prefix for a user-local, no-sudo install
    NPM_PREFIX       = "${env.WORKSPACE}/.npm-global"
  }

  stages {

    stage('Build') {
      agent { label 'linux' }
      steps {
        checkout scm
        sh '''#!/usr/bin/env bash
          set -euo pipefail

          mkdir -p "$NPM_PREFIX"
          npm config set prefix "$NPM_PREFIX"
          export PATH="$NPM_PREFIX/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

          mkdir -p "$OUTPUT_DIR"
          uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
            --name "$SOLUTION_NAME" \
            --version "$SOLUTION_VERSION"
        '''

        // Carry the .zip to the Deploy stage, which runs on a fresh agent.
        stash name: 'solution-zip', includes: "${OUTPUT_DIR}/${SOLUTION_NAME}.${SOLUTION_VERSION}.zip"

        archiveArtifacts artifacts: "${OUTPUT_DIR}/*.zip", fingerprint: true
      }
    }

    stage('Deploy') {
      agent { label 'linux' }
      steps {
        unstash 'solution-zip'

        withCredentials([
          string(credentialsId: 'UIPATH_CLIENT_ID',     variable: 'UIPATH_CLIENT_ID'),
          string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
          string(credentialsId: 'UIPATH_TENANT',        variable: 'UIPATH_TENANT')
        ]) {
          sh '''#!/usr/bin/env bash
            set -euo pipefail

            mkdir -p "$NPM_PREFIX"
            npm config set prefix "$NPM_PREFIX"
            export PATH="$NPM_PREFIX/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 login \
              --client-id env.UIPATH_CLIENT_ID \
              --client-secret env.UIPATH_CLIENT_SECRET \
              --tenant "$UIPATH_TENANT"

            uip solution publish "${OUTPUT_DIR}/${SOLUTION_NAME}.${SOLUTION_VERSION}.zip"

            uip solution deploy run \
              --name "${SOLUTION_NAME}-${BUILD_NUMBER}" \
              --package-name "$SOLUTION_NAME" \
              --package-version "$SOLUTION_VERSION" \
              --folder-name MySolution \
              --folder-path Shared
          '''
        }
      }
    }

    stage('Test') {
      when {
        expression { return env.TEST_SET_KEY?.trim() }
      }
      agent { label 'linux' }
      steps {
        withCredentials([
          string(credentialsId: 'UIPATH_CLIENT_ID',     variable: 'UIPATH_CLIENT_ID'),
          string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
          string(credentialsId: 'UIPATH_TENANT',        variable: 'UIPATH_TENANT'),
          string(credentialsId: 'TEST_SET_KEY',         variable: 'TEST_SET_KEY'),
          string(credentialsId: 'PROJECT_KEY',          variable: 'PROJECT_KEY')
        ]) {
          sh '''#!/usr/bin/env bash
            set -euo pipefail

            mkdir -p "$NPM_PREFIX"
            npm config set prefix "$NPM_PREFIX"
            export PATH="$NPM_PREFIX/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"

            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 "test run did not finish within 30 minutes" >&2; exit 2 ;;
                *) echo "wait failed (exit $code)" >&2; 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 "$FAILED test case(s) failed" >&2
              exit 1
            fi
          '''
        }
      }
    }
  }

  post {
    always {
      // Best-effort cleanup; uip logout only matters on a persistent agent.
      node('linux') {
        sh '''
          if command -v uip >/dev/null; then
            uip logout || true
          fi
        '''
      }
    }
  }
}
pipeline {
  agent none

  options {
    timestamps()
    buildDiscarder(logRotator(numToKeepStr: '30'))
    disableConcurrentBuilds()
  }

  environment {
    CLI_VERSION      = '1.0.0'
    SOLUTION_NAME    = 'my-solution'
    SOLUTION_DIR     = 'my-solution'
    OUTPUT_DIR       = 'dist'
    SOLUTION_VERSION = "1.2.0-ci.${env.BUILD_NUMBER}"

    // npm global prefix for a user-local, no-sudo install
    NPM_PREFIX       = "${env.WORKSPACE}/.npm-global"
  }

  stages {

    stage('Build') {
      agent { label 'linux' }
      steps {
        checkout scm
        sh '''#!/usr/bin/env bash
          set -euo pipefail

          mkdir -p "$NPM_PREFIX"
          npm config set prefix "$NPM_PREFIX"
          export PATH="$NPM_PREFIX/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

          mkdir -p "$OUTPUT_DIR"
          uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
            --name "$SOLUTION_NAME" \
            --version "$SOLUTION_VERSION"
        '''

        // Carry the .zip to the Deploy stage, which runs on a fresh agent.
        stash name: 'solution-zip', includes: "${OUTPUT_DIR}/${SOLUTION_NAME}.${SOLUTION_VERSION}.zip"

        archiveArtifacts artifacts: "${OUTPUT_DIR}/*.zip", fingerprint: true
      }
    }

    stage('Deploy') {
      agent { label 'linux' }
      steps {
        unstash 'solution-zip'

        withCredentials([
          string(credentialsId: 'UIPATH_CLIENT_ID',     variable: 'UIPATH_CLIENT_ID'),
          string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
          string(credentialsId: 'UIPATH_TENANT',        variable: 'UIPATH_TENANT')
        ]) {
          sh '''#!/usr/bin/env bash
            set -euo pipefail

            mkdir -p "$NPM_PREFIX"
            npm config set prefix "$NPM_PREFIX"
            export PATH="$NPM_PREFIX/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 login \
              --client-id env.UIPATH_CLIENT_ID \
              --client-secret env.UIPATH_CLIENT_SECRET \
              --tenant "$UIPATH_TENANT"

            uip solution publish "${OUTPUT_DIR}/${SOLUTION_NAME}.${SOLUTION_VERSION}.zip"

            uip solution deploy run \
              --name "${SOLUTION_NAME}-${BUILD_NUMBER}" \
              --package-name "$SOLUTION_NAME" \
              --package-version "$SOLUTION_VERSION" \
              --folder-name MySolution \
              --folder-path Shared
          '''
        }
      }
    }

    stage('Test') {
      when {
        expression { return env.TEST_SET_KEY?.trim() }
      }
      agent { label 'linux' }
      steps {
        withCredentials([
          string(credentialsId: 'UIPATH_CLIENT_ID',     variable: 'UIPATH_CLIENT_ID'),
          string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
          string(credentialsId: 'UIPATH_TENANT',        variable: 'UIPATH_TENANT'),
          string(credentialsId: 'TEST_SET_KEY',         variable: 'TEST_SET_KEY'),
          string(credentialsId: 'PROJECT_KEY',          variable: 'PROJECT_KEY')
        ]) {
          sh '''#!/usr/bin/env bash
            set -euo pipefail

            mkdir -p "$NPM_PREFIX"
            npm config set prefix "$NPM_PREFIX"
            export PATH="$NPM_PREFIX/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"

            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 "test run did not finish within 30 minutes" >&2; exit 2 ;;
                *) echo "wait failed (exit $code)" >&2; 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 "$FAILED test case(s) failed" >&2
              exit 1
            fi
          '''
        }
      }
    }
  }

  post {
    always {
      // Best-effort cleanup; uip logout only matters on a persistent agent.
      node('linux') {
        sh '''
          if command -v uip >/dev/null; then
            uip logout || true
          fi
        '''
      }
    }
  }
}

Walkthrough

Top-level setup

  • agent none at the top declares that the pipeline has no default agent; each stage picks its own with agent { label 'linux' }. This lets you put the build on a high-capacity agent and the deploy on a locked-down agent if your infrastructure separates them that way.
  • optionstimestamps() adds [2026-04-24T10:30:12] prefixes to every console line, which makes debugging long runs much easier. disableConcurrentBuilds() prevents two deploys from racing into the same Orchestrator folder.
  • environment block — the same pattern as the other recipes: pin CLI_VERSION, compute SOLUTION_VERSION from BUILD_NUMBER, and route npm's global prefix into the workspace so installs are per-build and need no sudo.

Build stage

  • checkout scm — Jenkins needs this explicitly in a declarative pipeline.
  • Install step — same if ! command -v uip guard as other platforms. The workspace-local npm prefix ($NPM_PREFIX) means every build starts clean; no caching across runs. If your Jenkins agents are persistent and you want to cache the CLI, either use the Job Cacher plugin or set NPM_PREFIX to a path on the agent rather than ${env.WORKSPACE}.
  • Pack step — straight uip solution pack with an explicit version.
  • stash name: 'solution-zip' — this is the Jenkins-specific piece. Unlike GitHub Actions artifacts or Azure Pipelines publish, a stash is scoped to the pipeline run and survives across stages without needing the workspace to persist. Every stage that needs the .zip calls unstash 'solution-zip' at its start. See the Jenkins docs on stash / unstash.
  • archiveArtifacts — a copy of the .zip also appears on the build page for humans to download. fingerprint: true lets Jenkins track it across jobs.

Deploy stage

  • unstash 'solution-zip' restores the .zip to the workspace. Combined with a fresh agent, this cleanly separates "build context" from "deploy context".
  • withCredentials binds three credential entries to environment variables visible to the sh block. The closure is the credentials-safe boundary — values are masked in the console log and not leaked to any step outside this block. Use this for every uip call that talks to Orchestrator.
  • uip login uses env.UIPATH_CLIENT_ID / env.UIPATH_CLIENT_SECRET — the env.VAR_NAME prefix reads the variable set by withCredentials. See Authentication — the env.VAR_NAME prefix. Never interpolate the secret into the command line directly (--client-secret "$UIPATH_CLIENT_SECRET") — that embeds it into the rendered shell command and, under some logging configurations, into the console output.
  • Publish + deployuip solution publish then uip solution deploy run. --name "${SOLUTION_NAME}-${BUILD_NUMBER}" makes each deployment traceable back to its Jenkins build.

Test stage

Gated by when { expression { return env.TEST_SET_KEY?.trim() } } — if you do not set the TEST_SET_KEY credential (or leave it empty), the stage is skipped. 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 the execution reaches a terminal state. On wait, exit code 2 means timeout (not authentication failure — it is a domain-specific reuse of the code).
  3. uip tm report get reads Data.Failed and the shell exits 1 when any test failed.

post { always }

Best-effort uip logout. On ephemeral agents this is unnecessary; on a persistent agent it clears the workspace-local .uipath/ folder so a later job on the same workspace does not reuse a stale session. The || true swallows the error if uip was never installed (the build failed before installation).

Common variations

Agent variations

  • Windows agent — replace sh with bat, translate the bash scripts to cmd.exe or PowerShell. The env. prefix and every uip command are identical; only the surrounding shell changes. See Installing UiPath CLI — Windows.
  • Docker agentagent { docker { image 'node:20' } } gives you a fresh Node runtime per build with zero agent config. Add args '-u root' if you need sudo-free global npm installs in a rootful image; otherwise configure the NPM prefix to $WORKSPACE/.npm-global as above and skip the privilege dance.
  • Kubernetes plugin — use a pod template with a node:20 container and mount /root/.npm-global as a PVC to cache the CLI between builds.

Pin tool versions too

For patch-level reproducibility:

sh 'uip tools install @uipath/[email protected] @uipath/[email protected] @uipath/[email protected]'
sh 'uip tools install @uipath/[email protected] @uipath/[email protected] @uipath/[email protected]'

Promote across environments

Turn each environment into its own stage, reusing the same stash:

stage('Deploy stage') {
  agent { label 'linux' }
  steps {
    unstash 'solution-zip'
    withCredentials([string(credentialsId: 'UIPATH_STAGE_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
      sh '...deploy to stage tenant...'
    }
  }
}

stage('Approval') {
  steps {
    input message: 'Deploy to production?', ok: 'Deploy'
  }
}

stage('Deploy prod') {
  agent { label 'linux' }
  steps {
    unstash 'solution-zip'
    withCredentials([string(credentialsId: 'UIPATH_PROD_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
      sh '...deploy to prod tenant...'
    }
  }
}
stage('Deploy stage') {
  agent { label 'linux' }
  steps {
    unstash 'solution-zip'
    withCredentials([string(credentialsId: 'UIPATH_STAGE_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
      sh '...deploy to stage tenant...'
    }
  }
}

stage('Approval') {
  steps {
    input message: 'Deploy to production?', ok: 'Deploy'
  }
}

stage('Deploy prod') {
  agent { label 'linux' }
  steps {
    unstash 'solution-zip'
    withCredentials([string(credentialsId: 'UIPATH_PROD_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
      sh '...deploy to prod tenant...'
    }
  }
}

The input step pauses the pipeline until a human clicks Deploy. Use Jenkins' Matrix Authorization Strategy to restrict who can approve. Deeper coverage in How-to: pack and publish a Solution — promote one package across tenants.

Rollback

Add a parameterized job — Jenkins → New Item → Pipeline → This project is parameterized → Add Parameter (String) → "ROLLBACK_VERSION" — and add a guarded stage:

stage('Rollback') {
  when { expression { return params.ROLLBACK_VERSION?.trim() } }
  agent { label 'linux' }
  steps {
    withCredentials([/* UIPATH_* credentials */]) {
      sh '''#!/usr/bin/env bash
        set -euo pipefail
        # …install + login…

        uip solution deploy run \
          --name "${SOLUTION_NAME}-rollback" \
          --package-name "$SOLUTION_NAME" \
          --package-version "$ROLLBACK_VERSION" \
          --folder-name MySolution \
          --folder-path Shared
      '''
    }
  }
  environment {
    ROLLBACK_VERSION = "${params.ROLLBACK_VERSION}"
  }
}
stage('Rollback') {
  when { expression { return params.ROLLBACK_VERSION?.trim() } }
  agent { label 'linux' }
  steps {
    withCredentials([/* UIPATH_* credentials */]) {
      sh '''#!/usr/bin/env bash
        set -euo pipefail
        # …install + login…

        uip solution deploy run \
          --name "${SOLUTION_NAME}-rollback" \
          --package-name "$SOLUTION_NAME" \
          --package-version "$ROLLBACK_VERSION" \
          --folder-name MySolution \
          --folder-path Shared
      '''
    }
  }
  environment {
    ROLLBACK_VERSION = "${params.ROLLBACK_VERSION}"
  }
}

Trigger from the build page with Build with Parameters. For destructive rollback (uninstall + delete artifact), see How-to: pack and publish a Solution — rollback.

Common pitfalls

  • withCredentials scoping. Credentials are only available inside the closure. If you call uip login inside withCredentials and uip solution publish outside, the second command has no session. Keep the whole CLI block inside one withCredentials.
  • stash before changing agents. A stash declared in one stage is usable in later stages only if the stash step ran to completion before the agent switched. If the Build stage's shell block exits non-zero before stash, later stages will fail at unstash. Putting stash outside the sh block — as in this recipe — ensures it runs on a clean agent regardless of shell outcome.
  • || true at the wrong time. The uip logout || true in post is safe because the only failure mode is "uip was never installed". Do not pepper || true through earlier stages — it hides real failures.
  • sh strict mode. Start every sh block's bash script with #!/usr/bin/env bash and set -euo pipefail. The default Jenkins shell is /bin/sh, which does not support pipefail. See Scripting patterns — strict shell options.
  • Multiline strings in Groovy. Prefer triple-single-quoted strings ('''...''') for shell bodies — they do not interpolate $VAR, so the variable is expanded by bash rather than by Groovy. This avoids a common class of "variable is empty" bugs. For values you want Groovy to fill in at pipeline-compile time, use ${env.SOME_VAR} explicitly.

See also

Was this page helpful?

Connect

Need help? Support

Want to learn? UiPath Academy

Have questions? UiPath Forum

Stay updated