A minimal, batteries-included .NET template for new projects and libraries. Skip the boilerplate — start from a clean, idiomatic scaffold with the house defaults, linting, CI/CD, releases, and agent tooling already wired up.
It starts as a library with tests, not a web app or executable. Your first useful result is a passing solution build and test run; there is no application to launch or deploy yet. Generated projects receive the local build/test workflow too; configure branch protection in your repository to require it before merging.
- Idiomatic scaffold — an
Example.slnxsolution wiring a library project (src/Example) to a matching xUnit test project (tests/Example.Tests). A single documented member with tests shows the house testing pattern;scripts/rename-placeholders.shrepoints theExamplescaffold to your own project name in one shot. - House defaults — every project builds with
Nullable,ImplicitUsings,AnalysisMode=All,EnforceCodeStyleInBuild, XML documentation generation, andTreatWarningsAsErrorsenabled, so code-style and analyzer findings are build errors, not warnings. Editor and analyzer rules live in.editorconfig. - Feature-flag ready — a portable OpenFeature scaffold (
src/Example/FeatureFlags.cs) so new features land behind a flag, default-off, are tested in both states, and are switched on only after validation. Ships an in-memory provider (no backend needed); swap in flagd or another provider without touching call sites. SeeAGENTS.md. - CI/CD — repository-owned .NET validation builds and tests on Ubuntu, Windows and macOS for pull requests and the merge queue;
ci.yamlalso aggregates template-specific checks. Rundotnet build/dotnet testlocally before a PR. - Release and publishing configuration — semantic-release uses eligible Conventional Commits on
mainto decide whether to cut av*tag and GitHub release (release.yaml). The local publish workflow tests that tagged source and publishes version-matched NuGet packages using the supplied configuration. App credentials and a NuGet publishing key are maintainer setup, not local-build prerequisites. These workflow definitions alone do not prove that your package is published. - Dependency management — Dependabot keeps dependencies and pinned GitHub Actions current.
- Agent-ready —
AGENTS.mdconventions and a.claude/skills/maintaincard so the autonomous Agentic Engineer (and any agentic tool) can maintain the repo.
The target framework is declared in the project files — currently net10.0 in src/Example/Example.csproj.
The SDK selection is separate: global.json requires the .NET 10 SDK at feature band
10.0.300 or later within 10.0.x (latestFeature roll-forward), not just the .NET runtime.
You need Git, a compatible .NET SDK as described above, and network access for NuGet restore. The CLI route also needs the GitHub CLI signed in to your own account. Start in the directory where you want a new project checkout.
The next command creates a public GitHub repository and clones it. Replace my-project with
your chosen repository name; use --private instead of --public if appropriate. Run it only
when you intend to create that repository:
gh repo create my-project --template devantler-tech/dotnet-template --public --clone
cd my-projectOr click Use this template on the repository page, choose its owner and visibility, then clone your new repository and enter its root.
Then make it your own — run the personalisation script to repoint the scaffold (solution, projects, namespaces, and README references) to your project's name in one shot, then replace the sample ExampleClass with your first real type. A clean build and test confirms the scaffold is wired up:
./scripts/rename-placeholders.sh Widget
dotnet build
dotnet testWidget is a synthetic project name: replace it with your valid .NET project name. The script
needs a POSIX shell and standard Unix tools; Windows users can use a suitable shell such as Git
Bash. Run it only in a generated project, never an upstream template contribution checkout.
Success means the renamed solution builds without warnings/errors and its five scaffold tests
pass. Build restores dependencies automatically; neither command publishes a package.
Run the script with no argument to derive a PascalCase name from your origin GitHub remote. It leaves the Use this template links above and the maintenance docs untouched; review the result with git diff. (Prefer to rename by hand? Repoint Example across the .slnx, src/, tests/, and README references yourself.)
A weekly template-sync workflow opens a PR in your repository whenever this template's shared plumbing changes, so instances never drift from the portfolio's CI/lint/agent-file conventions. It never touches your code: every file falls into one of three ownership classes:
- Template-owned plumbing — synced downstream by the weekly PR: the
.github/workflows/CI/publish/release workflows, the shared configs (.editorconfig,.gitattributes,.pre-commit-config.yaml), and theCLAUDE.md/GEMINI.mdshims. Change these upstream in the template, never by hand in an instance. - Instance-owned — listed in your project's own
.templatesyncignore, never touched by a sync. The supplied file protects your solution and code (the.slnx, all ofsrc/andtests/), the toolchain floor (global.json— the SDK floor and every<TargetFramework>must move in lockstep, so the whole upgrade is yours), identity and docs (README.md,CONTRIBUTING.md,AGENTS.md,LICENSE,CODEOWNERS), and the configs you tailor (.releaserc,.gitignore,dependabot.yaml). Package configuration (dotnet-releaser.toml) is also instance-owned. The rename script updates its project path; tailor your package identity and configure NuGet trusted publishing before publishing. GitHub release creation remains separate, so this NuGet publisher has no GitHub release-write permission. Build and packaging run without the publishing key; a separate runner receives only the prepared packages and pushes them to NuGet. Automatic and manual publication require an existing release tag whose commit belongs tomainand includes the local publisher migration. Earlier tags are rejected before source selection; create a new release frommaininstead of using this workflow to republish a pre-migration tag. - Scaffold-time-only — the rename script and the template's own
validate-scaffold.yamlgate arrive when the repo is created and are ignored by sync afterwards, so you can delete them and they stay gone.
Existing projects need a one-time migration before accepting these workflow updates.
The sync action does not propagate updates to .templatesyncignore itself. Add and commit
the literal lines CONTRIBUTING.md and dotnet-releaser.toml in your project's own ignore file.
Keep your tailored contribution guide, and create or update your own publication configuration
before syncing the new publisher: copy the supplied configuration, then change its project
to your existing library's actual path. Do not rerun the scaffold rename script in an established
project. For example, a library already renamed to Widget uses
project = "src/Widget/Widget.csproj".
If a sync PR is already open, restore your tailored guide and configuration and commit the ignore entries before merging it: adding an ignore entry does not undo changes already proposed. Check that the proposed publisher still targets your library and that the two documentation/config files remain yours. New projects created from this template already include the ignore entries; their first rename updates the configuration path. Review future ownership changes in the consumer's ignore file too.
NuGet publishing setup: register a trusted publishing policy in the package owner's NuGet
account for your repository and workflow file publish.yaml, then set the repository variable
NUGET_USER to that NuGet profile name. Follow NuGet's trusted publishing guide
to scope the policy to your package publisher. The clean publication job exchanges its GitHub
identity for a temporary key immediately before pushing. It does not use the former long-lived
NUGET_API_KEY secret. Until the policy and username are configured, publication fails closed;
ordinary local builds and tests need neither.
The sync workflow no-ops in this template repository itself. In devantler-tech
instances it works out of the box (the org provides the App credentials); an
instance elsewhere is off by default — opt in by supplying your own GitHub App
(one allowed to open PRs in your repository): add its private key as the
APP_PRIVATE_KEY secret, its client ID as the APP_CLIENT_ID repository
variable, and set the repository variable TEMPLATE_SYNC_ENABLED=true.
The local .NET validation workflow runs in generated projects without an organization-injected
workflow. Branch protection is separate: require .NET template tests (ubuntu-latest),
.NET template tests (windows-latest), .NET template tests (macos-latest) and
CI - Required Checks in your repository's ruleset or branch protection. The aggregate covers
the retired-link validation check; it does not replace the three .NET results. Generated repositories
do not inherit this template's ruleset, and local build/test needs no publishing credentials.
If you tailor a workflow rather than maintaining it through this template, add its path to
your .templatesyncignore so later syncs preserve your version.
dotnet new classlib --output src/AnotherLibrary
dotnet sln Widget.slnx add src/AnotherLibraryThis example assumes you renamed the scaffold to Widget above. Use your own solution filename
and new project name; the commands create a project and update that solution.
dotnet builddotnet testWarnings are treated as errors, so a clean
dotnet buildanddotnet testare required before opening a pull request.
To improve the upstream template, start with CONTRIBUTING.md. Generated projects own their guide and should adapt it to their own contribution process.
The template's CI checks documentation for links to retired repositories using the configuration in retired-repo-links.json. It requires a complete clean scan and verifies that a seeded retired link fails in a disposable copy. This gate runs only in the template repository.
Generated projects own their scan configuration and decide when to enable the shared validator; template sync preserves their paths and historical exceptions.
This template is maintained by an autonomous AI assistant. Its canonical engineering contract and full validation matrix live in AGENTS.md. Preserve the Apache-2.0 license.