Validating a project
dcupl validate is a one-shot health check. It loads a project, classifies any errors it hits, and reports empty or auto-generated models — without starting a long-running daemon. Run it locally against your working files or remotely against a Console version before you push, deploy, or merge.
Validating remotely? The --source remote mode loads a Console version, so it needs project credentials. Pass --project-id / --api-key inline, or configure them once with dcupl config set (see Cloud Sync). Local validation needs no credentials.
Choosing a source
--source selects where the project is loaded from. If you omit it, the CLI infers the source from the flags you pass: --project-id selects remote, otherwise it is local. For a remote run, pass --source remote anyway: the report's source block follows the flag, so an inferred remote run is reported as "mode": "local".
dcupl validate --source local --lc-json dcupl/dcupl.lc.jsondcupl validate \
--source remote \
--project-id my-project \
--api-key <api-key> \
--version draft| Flag | Source | Purpose |
|---|---|---|
--source <local|remote> | both | Where to load from (inferred if omitted, see above) |
--lc-json <path> | local | Path to the loader config |
--project-id <str> | remote | Console project id |
--api-key <str> | remote | API key |
--version <id> | remote | Version to load |
--cdn-base-url <url> | remote | CDN host to load the version from. Defaults to the dcupl CDN, also when consoleApiUrl points at a self-hosted Console, so set it for a self-hosted Console (also DCUPL_CDN_BASE_URL, or cdnBaseUrl in dcupl.config.json) |
Processing & performance
Shape how the project is built — apply an application, environments, tags, and variables — and bound the loader's resources.
dcupl validate \
--app-key web \
--env production \
--tag eu,published \
--var REGION=eu --var TIER=proProcessing
| Flag | Purpose |
|---|---|
--app-key <str> | Application key to load |
--env <key> | Environment to apply (repeatable) |
--tag <tag> | Tag filter (repeatable; comma-separate for AND) |
--var <KEY=VALUE> | Loader variable (repeatable) |
Performance
| Flag | Default | Purpose |
|---|---|---|
--auto-serve | true | Auto-spawn dcupl serve to load the project |
--max-memory <MB> | — | Cap the loader's memory |
--quality <bool> | true | Run quality checks |
--limit <n> | 10 | Max error records in the report |
Reading the report
validate exits 0 even when the project has errors — it runs in report mode, and the report's top-level ok is always true (it only means "a report was produced"). Do not gate on ok. Parse the JSON and check summary.critical (and load.ok) instead. When no report can be produced — for example a missing or malformed loader config, or dcupl serve not starting — validate prints an error envelope ({"success":false,"error":…,"code":…}) and exits 1, so check the exit code too.
Emit machine-readable output with --json:
one-shot health check, machine-readable: dcupl validate --json — output: { "ok": true, "source": { "mode": "local", "loaderPath": "dcupl/dcupl.lc.json" }, "load": { "ok": true }, "models": { "total": 4, "empty": ["Review"], "autoGenerated": [] }, "errors": { "total": 3, "bySeverity": { "critical": 1, "warning": 2 }, "byGroup": { "ResourceError": 1, "PropertyDataError": 1, "ReferenceDataError": 1 }, "byType": { "InvalidResource": 1, "WrongDataType": 1, "RemoteReferenceKeyNotFound": 1 }, "truncatedGroups": [], "records": [ { "title": "http://127.0.0.1:8083/data/missing.data.json", "model": "Review", "errorGroup": "ResourceError", "errorType": "InvalidResource" }, { "title": "Wrong Data Type", "model": "Order", "attribute": "total", "errorGroup": "PropertyDataError", "errorType": "WrongDataType" }, { "title": "Remote Reference Key Not Found", "model": "Order", "attribute": "customer", "errorGroup": "ReferenceDataError", "errorType": "RemoteReferenceKeyNotFound" } ] }, "summary": { "critical": 1, "warnings": 2 } }
critical http://127.0.0.1:8083/data/missing.data.json
Review · ResourceError / InvalidResource
warning Wrong Data Type
Order.total · PropertyDataError / WrongDataType
warning Remote Reference Key Not Found
Order.customer · ReferenceDataError / RemoteReferenceKeyNotFound
The same report, decoded — errors.records[] with the severity of each errorGroup, plus the empty and auto-generated model signals (models.empty, models.autoGenerated).
The counts are exact: errors.total, bySeverity, byGroup, and byType are summed from the SDK's $DcuplErrorTrackingSummary model. errors.records[] is only a sample (--limit, default 10), and error tracking stores at most 1000 records per error group. Every group that hit that cap is listed in errors.truncatedGroups as { model, attribute, errorType, totalCount, storedCount }, and the text output repeats them under Truncated groups. An empty array means nothing was capped.
Gate a script on the exit code, load.ok and the critical count — and on summary.warnings too when warnings should block:
# exit 1 = no report (e.g. the loader config could not be loaded)
report=$(dcupl validate --json) || { echo "$report"; exit 1; }
# gate on load + critical count — warnings won't block
loaded=$(echo "$report" | jq -r '.load.ok')
critical=$(echo "$report" | jq -r '.summary.critical')
if [ "$loaded" != "true" ] || [ "$critical" -gt 0 ]; then
echo "Validation failed: load.ok=$loaded, $critical critical error(s)"; exit 1
fi
# stricter: warnings block too
warnings=$(echo "$report" | jq -r '.summary.warnings')
[ "$warnings" -gt 0 ] && { echo "$warnings warning(s)"; exit 1; }Error severity
Each record carries an errorGroup, errorType, model, attribute, and title. Groups roll up into two severities.
| Severity | Error groups | Meaning |
|---|---|---|
| Critical | ModelDefinitionError, ResourceError (and any error group not listed here) | The workspace can't load or a model is malformed — blocks everything downstream. Must fix. |
| Warning | ReferenceDataError, PropertyDataError, DataContainerError | Data-level issues (stray foreign keys, type mismatches). May resolve as more data loads. |