SonarQube project configuration in one file
SonarQube and SonarCloud read a small properties file from the root of your repository to learn what to scan. This tool generates that sonar-project.properties from a few fields: the unique project key, source and test paths, exclusion globs, and the coverage report location for your language.
How it works
The SonarScanner CLI looks for sonar-project.properties in its working directory and parses key=value lines. The required keys are sonar.projectKey plus a server connection (sonar.host.url, and sonar.organization on SonarCloud). Analysis scope comes from sonar.sources and sonar.tests, both comma-separated directory lists, while sonar.exclusions removes matching files using Ant-style globs such as **/dist/**.
SonarQube never executes your tests. Instead it imports a coverage report your CI already generated, so the property name depends on the language. JavaScript and TypeScript use sonar.javascript.lcov.reportPaths pointing at an LCOV file; Python uses sonar.python.coverage.reportPaths; Java uses sonar.coverage.jacoco.xmlReportPaths. The builder emits the correct property for the language you select.
SonarQube vs SonarCloud: what changes
The two platforms use the same scanner and almost identical configuration, but differ in two key properties:
- Self-hosted SonarQube:
sonar.host.urlpoints to your server (for examplehttps://sonar.example.com). Nosonar.organizationis needed. Authentication typically uses a user token or project analysis token generated in the server’s UI. - SonarCloud:
sonar.host.urlis alwayshttps://sonarcloud.io.sonar.organizationis required and must match your SonarCloud organization key. Projects are created either via the SonarCloud UI or automatically on first scan.
The builder generates the correct set of keys for whichever platform you select.
Coverage import by language
SonarQube’s coverage integration relies on your test runner producing a supported format:
| Language | Test tool example | Coverage file | SonarQube property |
|---|---|---|---|
| JavaScript / TypeScript | Jest, Vitest | coverage/lcov.info | sonar.javascript.lcov.reportPaths |
| Python | pytest-cov | coverage.xml | sonar.python.coverage.reportPaths |
| Java / Kotlin | JaCoCo | target/site/jacoco/jacoco.xml | sonar.coverage.jacoco.xmlReportPaths |
| Go | gotestsum | coverage.out | sonar.go.coverage.reportPaths |
| C# / .NET | dotnet test | coverage.cobertura.xml | sonar.cs.vscoveragexml.reportsPaths |
The critical sequencing rule in CI: run tests and generate coverage before invoking the scanner. If the coverage file does not exist when the scanner runs, SonarQube reports 0% coverage without error, which is a silent failure that misleads quality gates.
Common exclusion patterns
# Node.js project
sonar.exclusions=**/node_modules/**,**/dist/**,**/*.spec.ts,**/*.test.ts
# Java project
sonar.exclusions=**/target/**,**/*Test.java,**/generated/**
# Python project
sonar.exclusions=**/__pycache__/**,**/migrations/**,**/tests/**
Exclusions affect the analysis scope (what lines count toward coverage and issues). To exclude files from duplication detection or issue reporting without removing them from coverage, use sonar.cpd.exclusions and sonar.issue.ignore.multicriteria respectively.
Generated output example
A minimal SonarCloud configuration for a TypeScript project:
sonar.projectKey=com.example:my-app
sonar.organization=my-org
sonar.host.url=https://sonarcloud.io
sonar.sources=src
sonar.tests=test
sonar.exclusions=**/node_modules/**,**/*.spec.ts
sonar.sourceEncoding=UTF-8
sonar.javascript.lcov.reportPaths=coverage/lcov.info
Keep the authentication token out of this file — pass it as the SONAR_TOKEN environment variable when you run the scanner. Committing tokens to the repository is a common security mistake that SonarCloud itself will flag.