CI/CD Integration
Important
The Tabular Editor CLI is in Limited Public Preview. It is offered for evaluation with a Tabular Editor account; no license is required during preview. Commands, flags, and outputs may change before general availability. The preview build stops functioning after 2026-09-30. We recommend against using the CLI in production CI/CD pipelines during preview. Please refer to our license agreement.
The Tabular Editor CLI is designed for unattended execution in continuous integration and delivery pipelines. A single binary, structured output, non-interactive mode, native CI annotations for GitHub Actions and Azure DevOps, and VSTEST-compatible test results make it a natural replacement for ad-hoc TE2 invocations.
Warning
Do not use the CLI in production pipelines during Limited Public Preview. Two preview-specific risks apply to pipeline owners:
- Hard expiry. The preview binary stops functioning on 2026-09-30 - any pipeline depending on it will fail on that date, regardless of your release calendar.
- No backwards-compatibility guarantee. Commands, flags, output shapes, and exit codes may change between preview builds, so pipeline steps may need updating when you refresh the vendored binary.
Build and evaluate in non-production pipelines, and share feedback in the public TabularEditor/CLI repository so the GA version matches your needs.
What makes the CLI CI-friendly
- Single self-contained binary. No runtime install, no
TabularEditor.exe, nostart /wait. --non-interactiveglobal flag. Disables every prompt; fails fast with actionable errors.--forceon mutating commands (te deploy,te refresh) skips confirmation prompts.--ci vsts/--ci github. Emit native pipeline annotations to stderr.--trx <file>. Produce VSTEST results consumable by Azure DevOps test publishing.- Structured errors.
--output-format jsonemits{"error": "...", "hint": "..."}to stderr so pipeline steps can fail with a useful message.
Adding the CLI to your repo
During Limited Public Preview, the CLI is gated behind sign-in on tabulareditor.com, so pipelines cannot fetch the archive from a public URL. The simplest reproducible approach is to commit the binary that matches your runner into your repository and reference it from each pipeline step.
A common layout:
your-repo/
└── tools/
└── te/
├── te # Linux / macOS binary (needs chmod +x at runtime)
└── te.exe # Windows binary
Place the extracted binary - not the archive - so the pipeline can call it directly. Pick the build that matches your runner OS/arch; see Installation and Setup for the filename table. The self-contained binary is ~70 MB; consider Git LFS if your repo is sensitive to size.
Note
Committing the binary also pins the CLI version to whatever you checked in, which is desirable for CI reproducibility. To upgrade, replace the binary in tools/te/ and commit it - the commit message is your version log. Keep in mind that the preview binary still expires on 2026-09-30 regardless of when you committed it, so a vendored copy is not a permanent dependency - plan to refresh it (and re-validate your pipeline against the new API surface) on preview-build cadence.
GitHub Actions
A complete deploy + test workflow. The example assumes the Linux te binary is committed at tools/te/te, and a service principal is stored in repository secrets (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID).
name: Deploy semantic model
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
steps:
- uses: actions/checkout@v4
- name: Set up Tabular Editor CLI
run: |
chmod +x ./tools/te/te
echo "$GITHUB_WORKSPACE/tools/te" >> $GITHUB_PATH
- name: Validate
run: te validate ./model --ci github --trx validate.trx
- name: Best Practice Analyzer (gate)
run: te bpa run ./model --fail-on error --ci github --trx bpa.trx
- name: Deploy
run: |
te deploy ./model \
-s "${{ vars.WORKSPACE }}" \
-d "${{ vars.MODEL }}" \
--auth env \
--non-interactive \
--force \
--ci github
- name: Regression tests
run: |
te test run \
-s "${{ vars.WORKSPACE }}" \
-d "${{ vars.MODEL }}" \
--auth env --non-interactive \
--ci github --trx tests.trx
- name: Publish test results
if: always()
uses: actions/upload-artifact@v4
with:
name: trx-results
path: '*.trx'
Azure DevOps Pipelines
The Azure DevOps Pipelines equivalent of the GitHub Actions workflow above. The example assumes te.exe is committed at tools\te\te.exe. --ci vsts emits ##vso[...] commands that the pipeline interprets as errors, warnings, and task-status updates.
trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
- group: 'te-cli-secrets' # Contains AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID
steps:
- checkout: self
- powershell: Write-Host "##vso[task.prependpath]$(Build.SourcesDirectory)\tools\te"
displayName: 'Set up Tabular Editor CLI'
- script: te validate ./model --ci vsts --trx validate.trx
displayName: 'Validate'
- script: te bpa run ./model --fail-on error --ci vsts --trx bpa.trx
displayName: 'BPA gate'
- script: |
te deploy ./model ^
-s "$(WORKSPACE)" -d "$(MODEL)" ^
--auth env --non-interactive --force --ci vsts
displayName: 'Deploy'
env:
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
- script: te test run -s "$(WORKSPACE)" -d "$(MODEL)" --auth env --non-interactive --ci vsts --trx tests.trx
displayName: 'Regression tests'
env:
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
- task: PublishTestResults@2
condition: always()
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '*.trx'
BPA gate patterns
te deploy and te save run the Best Practice Analyzer as a pre-flight gate by default. Three behaviors are worth determining up-front:
- Enforce - the default. Pipeline fails if BPA finds violations at severity ≥ error. Pair with
--fail-on warningon a standalonete bpa runstep if you want warnings to fail too. - Auto-fix -
--fix-bpaappliesfixExpressions in memory for the deployed artifact. Source files are not modified. Useful when the source of truth lives in the model and you want deploys to normalize style without developer intervention. - Bypass -
--skip-bpadisables the gate for a single command. Useful for emergency hotfixes; not recommended as a default.
# Treat warnings as failures in PR validation
te bpa run ./model --fail-on warning --ci github --trx bpa.trx
# Auto-fix during deploy (source unchanged)
te deploy ./model -s my-ws -d my-model --fix-bpa --force --ci github
# Emergency bypass
te deploy ./model -s my-ws -d my-model --skip-bpa --force --ci github
See Custom Configuration for controlling the BPA gate globally via bpa.onDeploy / bpa.onSave config keys.
Refresh patterns
Refresh in pipelines is typically a follow-up step after deployment. Use --non-interactive and pick a deterministic --type:
# Full refresh of the whole model after deploy
te refresh -s my-ws -d my-model --type full --non-interactive
# Refresh a single fact table (e.g., daily incremental pipeline)
te refresh -s my-ws -d my-model --table Sales --type full --non-interactive
# Recalculate only (useful after calculation-group changes)
te refresh -s my-ws -d my-model --type calculate --non-interactive
For incremental refresh workflows, combine --apply-refresh-policy, --effective-date <yyyy-MM-dd>, and --partition <Table.Partition> flags. See Command Reference for details.
Artifact patterns
Emit TMSL or XMLA as an artifact without deploying, so DBAs or a later job can review or apply it:
# Produce the XMLA/TMSL script that would deploy - do not deploy
te deploy ./model -s my-ws -d my-model --xmla deploy.tmsl --force
# Produce the TMSL refresh command - do not execute
te refresh -s my-ws -d my-model --type full --dry-run > refresh.tmsl
Commit these artifacts to git, upload them to the pipeline's artifact storage, or pass them between jobs. They're plain text and diff cleanly in pull requests.
Secret handling
| Approach | When to use | Notes |
|---|---|---|
Service principal via env vars (AZURE_CLIENT_ID / AZURE_CLIENT_SECRET / AZURE_TENANT_ID, --auth env) |
General CI/CD | Map pipeline secrets to environment variables at the step or job level. Never pass secrets in command arguments. |
Service principal via te auth login once per job (echo $SECRET \| te auth login -u $ID -p - -t $TENANT) |
Multi-step jobs | The login is cached, so subsequent te commands acquire tokens silently - no need to set AZURE_CLIENT_* for every step or re-pass -u/-p/-t. Pipe the secret via stdin rather than interpolating it. |
Managed identity (--auth managed-identity) |
Azure VMs, Container Apps, Azure Functions | No secrets to manage. Preferred in Azure-hosted environments. |
Certificate (--certificate <path>) |
Enterprise scenarios with cert rotation | Mount the certificate as a secure file step; pass --certificate-password via env. |
Warning
Do not echo secrets or the output of te auth status to pipeline logs. The CLI writes warnings to stderr when secrets are passed on the command line - respect those warnings in CI.
Related pages
- Authentication and Connections - authentication methods in detail.
- Custom Configuration - configuration and profile overrides.
- Automation and Scripting - general scripting patterns.
- Migrating from the TE2 Command Line - migrating an existing TE2-based pipeline.