ERP data is a natural fit for Dagster's asset model. A customer, an invoice, a GL entry pulled out of Acumatica or NetSuite isn't really a "task" — it's a dataset with a shape, an owner, and a freshness requirement, which is exactly what software-defined assets are built to represent. The orchestration problem in ERP pipelines is less about task sequencing and more about incremental extraction, schema drift, and not stepping on the ERP's own batch jobs.
Modeling ERP entities as assets
Instead of a script called extract_and_load.py, you model each ERP entity as its own asset: erp_customers, erp_invoices, erp_gl_entries. Downstream transforms — a cleaned invoices table, a reconciled GL fact table — declare those assets as inputs, and Dagster infers the dependency graph from the function signatures rather than you wiring it by hand.
from dagster import asset
@asset
def erp_invoices(erp_api_client) -> None:
"""Raw AR invoices pulled from the ERP REST endpoint."""
invoices = erp_api_client.get("/entity/Default/22.200.001/Invoice")
write_to_warehouse("raw.erp_invoices", invoices)
@asset
def invoices_reconciled(erp_invoices, erp_gl_entries) -> None:
"""Invoices joined against GL postings for the reconciliation report."""
...
The payoff shows up when something breaks: if invoices_reconciled looks wrong, you can look at the asset graph and see immediately whether erp_invoices or erp_gl_entries was the stale or failing input, instead of digging through a log of opaque script runs.
Incremental and partitioned loads
ERP tables like GL transactions or AR invoices are usually too large to pull in full on every run, and most ERPs throttle or slow down under a full-table extract anyway. Partitioning the asset by date (daily or monthly) lets you extract incrementally — each partition represents one day's or month's worth of records — and Dagster tracks materialization status per partition, so a failed day can be re-run in isolation instead of re-pulling the whole table.
Most ERPs expose a last-modified timestamp or a change-tracking API (Acumatica's LastModifiedDateTime filter, for instance). Partition and filter on that field rather than re-pulling by primary key range — it's what lets an incremental load stay incremental even when older records get edited.
Asset checks on financial data
Financial data is exactly where inline data-quality checks earn their keep, because a silently wrong number in a GL export is worse than a failed pipeline. Typical checks on ERP assets: debits equal credits per batch, invoice totals reconcile against line-item sums, no orphaned GL entries referencing a deleted account, row counts within an expected range of the prior partition. Attaching these as asset checks means a reconciliation failure shows up as a red check next to the asset in the UI, not as a surprise the finance team finds two weeks later.
An extraction job can complete successfully while pulling a partial page of results because the ERP's API paginated unexpectedly, or while missing records that were voided and re-issued the same day. Check row counts and balance totals, not just job exit status.
Scheduling around ERP maintenance windows
Most on-prem and even cloud-hosted ERPs run their own nightly batch jobs — period-end processing, integration syncs, database maintenance — and hitting the API mid-batch is a reliable way to get locked records, timeouts, or inconsistent reads. Use Dagster schedules to run extraction after the ERP's known batch window closes, and use sensors for anything that should react to an event instead of a fixed time — for example, a sensor that watches for a "period closed" flag before kicking off the month-end GL extraction, rather than guessing a time that's usually safe.
Handling schema drift from customizations
ERP schemas are rarely static, especially with Acumatica customizations or NetSuite custom fields — a new user-defined field, a renamed segment, a changed picklist. An extraction asset that assumes a fixed schema breaks the moment someone adds a field in the ERP. The practical approach is to extract semi-structured (land the raw API response, don't force a rigid schema at extraction time) and push schema validation into an asset check or the transform layer, where a schema-drift failure is visible and specific instead of an opaque pipeline crash three steps downstream.
| ERP concern | Dagster mechanism |
|---|---|
| Large GL/invoice tables | Partitioned assets, incremental by change-tracking field |
| Debits/credits must balance | Asset checks on the reconciled asset |
| Nightly ERP batch jobs | Schedules offset past the batch window, or sensors on a status flag |
| Custom fields / schema drift | Land raw, validate schema downstream via asset checks |
Wrapping up
The case for Dagster over a generic task scheduler in ERP pipelines is that the assets you care about — customers, invoices, GL entries — are exactly what the tool is built to model, not an awkward fit forced onto a task graph. Partition the big tables, put asset checks on anything that touches a balance, schedule around the ERP's own batch windows, and land raw data so schema drift is a visible check failure instead of a downstream crash.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.