Returns and Outputs in MSBuild Target Authoring: Key Differences Explained
In MSBuild target authoring, Returns exposes items to calling projects for project-to-project communication, while Outputs declares produced files for incremental build tracking and up-to-date checks.
When authoring custom targets in MSBuild, choosing between the Returns and Outputs attributes determines how your build graph behaves and whether MSBuild can skip work efficiently. According to the dotnet/skills repository, these two mechanisms serve fundamentally different purposes—one for inter-project data flow and one for file-based incrementality. Understanding when to use each attribute is critical for writing high-performance build scripts that avoid stale data or unnecessary rebuilds.
Core Differences Between Returns and Outputs
The Returns and Outputs attributes on MSBuild Target elements serve distinct architectural purposes, as documented in the plugins/dotnet-msbuild/skills/target-authoring/SKILL.md file within the dotnet/skills repository.
Purpose and Communication Semantics
Returns communicates items to the calling project when one project references another. This mechanism is designed for query targets that simply retrieve or compute values without side effects. The calling project receives the item list directly and can consume it in its own build logic.
Outputs declares files or items that the target physically produces on disk. This metadata enables MSBuild to determine whether a target needs to execute based on file timestamps. According to the source documentation, "Outputs on inner targets is for incrementality (timestamp checks)."
Impact on Incremental Builds
The attributes differ fundamentally in their effect on the build graph:
-
Returns: Has no impact on incremental builds. MSBuild does not track timestamps for returned items. The target executes every time it is invoked, and the caller simply receives the current values. -
Outputs: MSBuild records timestamps for each declared output. If all outputs are newer than their inputs (specified via theInputsattribute), the target is considered up-to-date and is skipped entirely.
When to Use Returns vs Outputs in MSBuild Target Authoring
Using Returns for Query Targets
Use Returns when creating lightweight, side-effect-free query targets that provide data to referencing projects. Common examples include GetTargetPath or GetTargetFrameworks targets.
<!-- Returns the computed target paths to the calling project -->
<Target Name="GetTargetPath"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
The calling project can reference these items via @(TargetPathWithTargetPlatformMoniker) after an MSBuild project reference. As noted in the dotnet/skills skill documentation, "Returns specifies what the MSBuild task receives when calling this project. Use for inter-project communication."
Using Outputs for File-Generating Targets
Use Outputs when your target writes files to disk and you want MSBuild to skip execution when those files are current. This is essential for code generation, compilation, and asset processing targets.
<Target Name="GenerateSource"
Inputs="@(MyModelFiles)"
Outputs="@(GeneratedSourceFiles)">
<Exec Command="my-generator --input %(MyModelFiles.Identity) --out $(IntermediateOutputPath)Generated.cs" />
<ItemGroup>
<GeneratedSourceFiles Include="$(IntermediateOutputPath)Generated.cs" />
</ItemGroup>
</Target>
If Generated.cs is newer than the model files in MyModelFiles, MSBuild skips GenerateSource during incremental builds.
Common Pitfalls in MSBuild Target Authoring
Stale Data from Misplaced Outputs
Using Outputs on a pure query target creates a dangerous pitfall. Since MSBuild thinks the target produces files, it may skip execution if the (non-existent or unrelated) outputs appear up-to-date, returning stale or empty data to the caller.
<!-- ❌ Wrong: Using Outputs on a pure query – can cause stale results -->
<Target Name="GetTargetPath"
Outputs="@(TargetPathWithTargetPlatformMoniker)" />
Defeated Incrementality from Misplaced Returns
Conversely, using Returns on a target that produces files defeats incremental build optimization. Because MSBuild does not track timestamps for Returns, it cannot determine when to skip the target, causing unnecessary rebuilds even when output files are current.
The skill documentation explicitly warns: "Never mix the two purposes. Query targets (GetTargetPath, GetTargetFrameworks) should use Returns, not Outputs."
Summary
Returnsis for project-to-project communication, exposing items to callers without affecting incremental build logic.Outputsis for declaring produced files, enabling MSBuild to skip targets when outputs are up-to-date.- Query targets like
GetTargetPathmust useReturns; file-generating targets must useOutputs. - Mixing these attributes causes either stale data (Outputs on queries) or unnecessary rebuilds (Returns on file generators).
- Reference the definitive guidance in
plugins/dotnet-msbuild/skills/target-authoring/SKILL.mdfor canonical patterns.
Frequently Asked Questions
Can a single MSBuild target use both Returns and Outputs?
Yes, a target can technically declare both attributes, but they serve different callers. Outputs affects MSBuild's internal incrementality checks for the current project, while Returns affects what data flows to external projects that reference this target. However, according to the dotnet/skills source, you should avoid conflating the purposes—query targets should use Returns exclusively, while production targets should use Outputs with the appropriate Inputs.
Why does MSBuild skip my query target when I use Outputs?
If you use Outputs on a target that doesn't actually create files, MSBuild may skip it during incremental builds if the declared outputs exist and appear fresh. This causes the target to return stale data or empty collections to calling projects. The solution is to use Returns instead, which ensures the target executes every time it is invoked.
How do project references access data from a target with Returns?
When a project references another MSBuild project, MSBuild can invoke specific targets from the referenced project. If that target uses Returns, the items specified in the Returns attribute become available to the calling project as if they were items in the caller's own scope. This enables cross-project data sharing without file-based intermediaries.
What happens if I don't specify either Returns or Outputs?
If a target specifies neither attribute, MSBuild will execute the target every time the build reaches it (assuming no other conditions prevent execution), and no items will flow back to calling projects. The target will not participate in incremental build optimization, and projects referencing this target won't automatically receive any computed data from it.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →