An SSIS package converts to Azure Data Factory by splitting it in two. Each Data Flow Task becomes one Mapping Data Flow, where sources, sinks and transformations are declared as named streams and the logic itself is written as Data Flow Script. The control flow around it - precedence constraints, Execute SQL Task, loops, package configurations - belongs to the ADF pipeline layer instead, and that layer is not generated for you. Azure Data Factory is currently a beta target on Deflows, which means the output is worth reviewing rather than deploying unread.
- A Mapping Data Flow covers one Data Flow Task, so a package with three of them produces three data flows and not one merged graph.
- Data Flow Script is not SQL: iif() replaces CASE WHEN, toInteger() replaces CAST, and a SELECT keyword anywhere in the output is a sign the conversion drifted.
- Datasets come out as placeholder references. Linked services, integration runtimes and triggers are deliberately left for you, because guessing them would produce JSON that looks deployable and is not.
Why this is the migration Microsoft points you at
SSIS to Azure Data Factory is the documented path off SQL Server Integration Services, which is why so many teams end up on it whether or not they chose Azure deliberately. The lift-and-shift option, running the SSIS runtime inside ADF, keeps the packages alive but keeps the dependency too. Rewriting into native Mapping Data Flows is what actually retires the old engine.
The reason that rewrite stalls is rarely the volume of packages. It is that a .dtsx file mixes two different kinds of logic in one document, and only one of them has an ADF equivalent that can be generated.
One package, two migrations
A Data Flow Task is row-level work: read, derive, filter, join, aggregate, write. That maps onto a Mapping Data Flow almost concept for concept. The control flow wrapped around it is a different animal: precedence constraints, Execute SQL Task, Foreach Loop Container, package configurations and event handlers describe orchestration, not row transformation.
In ADF that orchestration lives in the pipeline, one level above the data flow. A converter that folded control-flow tasks into the data flow would be inventing behavior, so the honest output covers the data flow and hands the pipeline layer back to you with the tasks named rather than silently dropped.
- OLE DB / Flat File / Excel Source and Destination map to source() and sink() streams
- Derived Column maps to derive()
- Conditional Split maps to filter() branches, one per named case output
- Merge Join and Lookup map to join() with an explicit joinType
- Aggregate maps to aggregate() with groupBy()
- Union All maps to union()
- Data Conversion maps to type functions inside a derive(), not to a cast keyword
- Execute SQL Task and Script Task map to nothing in the data flow, and are surfaced as reference instead
Data Flow Script is not SQL, and that is where output goes wrong
The single most common defect in a hand-written or naively generated ADF conversion is SQL leaking into the data flow. ADF expressions have their own vocabulary: iif() or case() instead of CASE WHEN, toInteger() and toDate() instead of CAST, isNull() and iifNull() for null handling, daysBetween() for date arithmetic. A SELECT or GROUP BY keyword inside scriptLines will not fail loudly at generation time. It fails later, in the Azure portal, after someone has already assumed the file was correct.
The structural half matters just as much. Every stream name used in Data Flow Script has to exist in the sources, sinks or transformations list of the same JSON document, and every source needs a dataset reference. A definition that violates either rule is well-formed JSON that ADF will not accept.
What the output deliberately leaves empty
Dataset references come out as placeholders derived from the physical source names in the package, so a source reading dbo.orders_2017 becomes a reference named after it. Linked services, integration runtimes, credentials and triggers are not invented. This looks like a gap and is actually the point: a definition with fabricated connection details reads as ready to deploy, and the failure only surfaces once someone tries.
Python and Script Task logic gets the same treatment. Rather than guessing at a translation, the affected transformation carries a warning in its description and emits placeholder columns for whatever the script created, so the schema downstream still lines up while the gap stays visible.
Where Deflows fits
Deflows parses each Data Flow Task in a .dtsx package into the same internal flow structure it uses for Tableau Prep, Alteryx and Power Query, then emits an Azure Data Factory Mapping Data Flow definition as a single JSON document: sources, sinks and transformations as named streams, with the logic in Data Flow Script. Parsing happens in your browser, so the package file itself never reaches a server.
Azure Data Factory is a beta target, which is a statement about review effort rather than a disclaimer. The generated definition is a reviewed draft that saves the transcription work, not a deployment artifact. The same package can also be converted to SQL or PySpark if you want a second reading of the same logic to compare against.
Questions teams ask
Does the conversion produce an ADF pipeline as well as a data flow?
No. The output is the Mapping Data Flow definition. The pipeline that schedules it, and the activities that replace SSIS control flow such as Execute SQL Task or Foreach Loop Container, are yours to build. Those tasks are surfaced as reference so nothing is lost, but generating a pipeline would mean inventing triggers and dependencies that are not in the package.
Should I use the SSIS Integration Runtime instead?
Running packages on the SSIS Integration Runtime inside ADF is a valid lift and shift, and it is the faster move if the goal is only to leave on-premises hardware. It keeps the SSIS dependency, the .dtsx files and the skills needed to maintain them. Converting to Mapping Data Flows is what removes that dependency, which is why teams often do both: shift first to hit a datacenter deadline, then convert package by package.
What happens to a package with several Data Flow Tasks?
Each Data Flow Task is parsed as its own graph and converts to its own Mapping Data Flow definition, clearly attributed to the task it came from. They are not merged, because merging them would join streams the original package deliberately kept separate.