The 5-Step Deploy Pipeline
AcuOps uses a CI/CD pipeline to deploy Acumatica customization projects safely and repeatably. Every deploy follows 5 steps.
Pipeline Overview
Step 1: Validate → Step 2: Package → Step 3: Import → Step 4: Publish → Step 5: Verify
Step 1: Validate
The validate-project.py script checks the project XML for 15+ known issues:
- Missing
IsActive()methods on cache/graph extensions - Invalid
<Table>elements (cause NullReferenceException on cloud) - Incorrect XML element names (
<SqlScript>vs<Sql>) - References to non-public types (e.g.,
ARCustomerClass) - Missing namespace declarations
python3 scripts/validate-project.py Customization/_project/project.xml
Step 2: Package
The project directory is packaged into a .zip file that the Customization API accepts:
cd Customization/_project && zip -r ../../package.zip . && cd ../..
Step 3: Import
The package is uploaded via the Customization API:
POST /CustomizationApi/Import
Content-Type: application/json
{
"projectName": "YourProjectName",
"projectDescription": "Deployed via CI/CD",
"projectLevel": 0,
"isReplaceIfExists": true,
"projectContentBase64": "<base64-encoded-zip>"
}
The URL path is case-sensitive: /CustomizationApi/Import (capital I). All Customization API endpoints require POST.
Step 4: Publish
Publishing compiles the C# code and applies database changes:
POST /CustomizationApi/publishBegin
{
"projectNames": ["YourProject", "ISVPackage1", "ISVPackage2"],
"isMergeConflictsForced": true,
"tenantMode": "Current"
}
Co-publishing is critical — the projectNames array must include ALL active customization projects. Otherwise, omitted projects are deactivated during publish.
The pipeline polls publishEnd until completion:
POST /CustomizationApi/publishEnd
{}
Returns HTTP 200 (still running) or 400 with isCompleted: true / isFailed: true.
Publishing restarts the Acumatica app pool. All active sessions are disconnected. Schedule deploys during maintenance windows.
Step 5: Verify
Post-publish validation confirms the deploy succeeded:
- Schema check —
GET /entity/default/24.200.001/{Entity}/$adHocSchemaverifies custom fields appear - Entity query —
GET /entity/default/24.200.001/{Entity}?$top=1confirms entities are accessible - Test suite — 66 Playwright tests covering all customized entities
GitHub Actions Workflow
The pipeline runs automatically on push to main:
on:
push:
branches: [main]
paths: ['Customization/**']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Acumatica
run: bash scripts/deploy.sh
env:
ACUMATICA_URL: ${{ secrets.ACUMATICA_PROD_URL }}
ACUMATICA_USERNAME: ${{ secrets.ACUMATICA_PROD_USERNAME }}
ACUMATICA_PASSWORD: ${{ secrets.ACUMATICA_PROD_PASSWORD }}
ACUMATICA_TENANT: ${{ secrets.ACUMATICA_PROD_TENANT }}
Multi-Project Deploys
For environments with multiple customization projects:
bash scripts/deploy.sh \
--extra-import StudioBPORelations:path/to/StudioBPORelations/_project
This imports the extra project alongside the primary project and co-publishes both.
Incremental Deploy Strategy
For complex changes, deploy incrementally:
- Step A: Deploy graph extensions only (no ASPX changes) — verify API smoke test passes
- Step B: Deploy ASPX screen changes in a separate commit — verify screen renders in browser
This isolates C# compilation failures from UI rendering failures.