Skip to content
Development
Skill

/extension-points

Own MSBuild import and hook discovery. USE FOR: CustomBefore/CustomAfter hooks, ordered wildcard and NuGet auto-imports, control properties, build/buildTransitive packed layout, package ID and file-name matching, per-TFM forwarders, and tracing why assets or hooks are missing,

From plugin
skills
5.8k106 skills20 agents

Claude Code

$ npx -y skills add dotnet/skills --skill extension-points --agent claude-code

SKILL.md

extension-points.SKILL.md
name: extension-points
description: "Own MSBuild import and hook discovery. USE FOR: CustomBefore/CustomAfter hooks, ordered wildcard and NuGet auto-imports, control properties, build/buildTransitive packed layout, package ID and file-name matching, per-TFM forwarders, and tracing why assets or hooks are missing, broken, or replaced. An import guard remains in scope when it is one defect in a broader hook/import flow. DO NOT USE for a focused safety verdict on whether one specific Import needs Exists or is an intentionally unguarded package contract; use msbuild-antipatterns. NEVER INVOKE when imports and hook placement already work and the request is only target Inputs/Outputs, incremental skipping, or FileWrites clean tracking; use incremental-build or target-authoring. Exclude props-versus-targets placement and non-MSBuild systems."
license: MIT

MSBuild Extension Points

How the MSBuild pipeline provides hooks for SDKs, NuGet packages, repos, and users to inject custom logic.

CustomBefore / CustomAfter Hooks

Every major `.targets` file defines import hooks:

<PropertyGroup>
  <CustomBeforeMicrosoftCommonTargets Condition="'$(CustomBeforeMicrosoftCommonTargets)' == ''">
    $(MSBuildExtensionsPath)\v$(MSBuildToolsVersion)\Custom.Before.Microsoft.Common.targets
  </CustomBeforeMicrosoftCommonTargets>
</PropertyGroup>

<Import Project="$(CustomBeforeMicrosoftCommonTargets)"
    Condition="'$(CustomBeforeMicrosoftCommonTargets)' != '' and Exists('$(CustomBeforeMicrosoftCommonTargets)')"/>
<!-- ... core targets ... -->
<Import Project="$(CustomAfterMicrosoftCommonTargets)"
    Condition="'$(CustomAfterMicrosoftCommonTargets)' != '' and Exists('$(CustomAfterMicrosoftCommonTargets)')"/>

Rules

  • Default path includes version (`v$(MSBuildToolsVersion)`) for side-by-side installations.
  • Always check `Exists()`. The file may not be present on every machine.
  • **Append** to the property (don't overwrite) to chain multiple hooks:
<PropertyGroup>
  <CustomBeforeMicrosoftCommonTargets>
    $(CustomBeforeMicrosoftCommonTargets);$(MSBuildThisFileDirectory)MyExtension.targets
  </CustomBeforeMicrosoftCommonTargets>
</PropertyGroup>

Wildcard Import Directories

MSBuild imports all files in extension directories, sorted alphabetically:

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore\*"
    Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == 'true'
               and Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore')" />

Key paths

| Property | Resolves to | Scope | |---|---|---| | `$(MSBuildUserExtensionsPath)` | `%APPDATA%\Microsoft\MSBuild` | Per-user | | `$(MSBuildExtensionsPath)` | MSBuild install directory | Machine-wide | | `$(MSBuildProjectExtensionsPath)` | `obj/` directory | Per-project (NuGet) |

Name files with numeric prefixes for ordering: `01-first.props`, `02-second.props`.

Import Gating — Control Properties

Every wildcard import is gated by a boolean property:

<PropertyGroup>
  <ImportByWildcardBeforeMicrosoftCommonProps
      Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportByWildcardBeforeMicrosoftCommonProps>
  <ImportDirectoryBuildProps
      Condition="'$(ImportDirectoryBuildProps)' == ''">true</ImportDirectoryBuildProps>
</PropertyGroup>

Available control properties

| Property | What it disables | |---|---| | `ImportDirectoryBuildProps` | Directory.Build.props auto-discovery | | `ImportDirectoryBuildTargets` | Directory.Build.targets auto-discovery | | `ImportProjectExtensionProps` | NuGet-generated `*.props` in obj/ | | `ImportProjectExtensionTargets` | NuGet-generated `*.targets` in obj/ | | `ImportByWildcardBefore*` | Machine-level ImportBefore extensions | | `ImportByWildcardAfter*` | Machine-level ImportAfter extensions |

NuGet Package Build Extension Layout

NuGet packages inject build logic via `build/` or `buildTransitive/` folders:

MyPackage/
  build/
    MyPackage.props      ← imported via *.props wildcard
    MyPackage.targets    ← imported via *.targets wildcard
  buildTransitive/
    MyPackage.props      ← imported by transitive consumers
    MyPackage.targets

Rules

  • File names **must match the package ID** exactly.
  • `build/` affects direct consumers only. `buildTransitive/` affects the entire dependency chain.
  • Props are imported early (before the project), targets are imported late (after the project).

Forwarding chain: `buildTransitive/` → `build/` → shared

Forward `buildTransitive/*.props` and `buildTransitive/*.targets` through their sibling `build/*.props` / `build/*.targets` files (chain `buildTransitive → build → shared`) instead of importing `buildMultiTargeting/` directly. This keeps `build/` as the single source of truth with a clear ownership chain, so transitive consumers stay in sync with direct consumers instead of the two layouts drifting apart.

When `build/` is packed **per-TFM** (`build/<tfm>/`, via `TfmSpecificPackageFile`, a per-TFM `<PackagePath>`, or SDK conventions) while `buildMultiTargeting/` is not, a `buildTransitive/<tfm>/` forwarder **must include the TFM segment** — dropping it resolves to a non-existent package-root `build/MyPackage.props` and fails transitive consumers with **`MSB4019`**. Derive the segment from the file's own folder, never `$(TargetFramework)` (NuGet nearest-match can serve a `net10.0` consumer the `net9.0` folder, so `$(TargetFramework)` may name a folder that was never restored):

<!-- buildTransitive/<tfm>/MyPackage.props -->
<Import Project="$(MSBuildThisFileDirectory)..\..\build\$([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName('$(MSBuildThisFileDirectory)'))))\MyPackage.props" />

Source Tree vs Packed Layout

When reviewing a NuGet build-extension package, the **source layout** in the repository can legitimately

Read more
@dotnet
Ships withskills

This repository contains the .NET team's curated set of portable skills and host-specific custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io.

5.8k106 skills20 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
5.8k
Star history of dotnet/skills on GitHub, a chart by star-history.com

Other skills on skills.

csharp-scripts

csharp-scripts

Run file-based C# apps with the .NET CLI when the user explicitly wants C#/.NET code without creating a project. Use for C#…

@dotnet@dotnetInstall Skill
dotnet-pinvoke

dotnet-pinvoke

Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string…

@dotnet@dotnetInstall Skill
nuget-trusted-publishing

nuget-trusted-publishi…

Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR:…

@dotnet@dotnetInstall Skill
vectorization

vectorization

Design, implement, optimize, and review SIMD code in .NET. USE FOR: vectorizing scalar loops with TensorPrimitives,…

@dotnet@dotnetInstall Skill
technology-selection

technology-selection

Guides technology selection and implementation of AI and ML features in .NET 8+ applications using ML.NET,…

@dotnet@dotnetInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills12d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react3d252k
@facebook@facebookInstall Skill

Questions about extension-points.

Which coding agents can use extension-points?
extension-points installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.