Guides creation and validation of custom dotnet new templates from existing projects. Generates a .template.config/template.json that preserves the source project's conventions. USE FOR: creating a reusable dotnet new template from an existing project, bootstrapping .template.config/template.json with correct identity, shortName, parameters, and post-actions, adding parameters or conditional content to a template you are authoring, validating the template.json you are authoring before publish...
Scanned 9/5/2026
Install to Claude Code
npx -y skills add managedcode/dotnet-skills --skill template-authoring --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Template Authoring?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/managedcode-template-authoring-dotnet-skills)More formats (shields.io, HTML) on the badges page.
---
name: template-authoring
description: >
Guides creation and validation of custom dotnet new templates from existing projects.
Generates a .template.config/template.json that preserves the source project's conventions.
USE FOR: creating a reusable dotnet new template from an existing project, bootstrapping
.template.config/template.json with correct identity, shortName, parameters, and
post-actions, adding parameters or conditional content to a template you are authoring,
validating the template.json you are authoring before publishing,
packaging templates as NuGet packages for distribution.
DO NOT USE FOR: validating an existing template.json as a standalone task (use
template-validation), finding or using existing templates (use template-discovery and
template-instantiation), MSBuild project file issues unrelated to template authoring,
NuGet package publishing (only template packaging structure).
license: MIT
---
# Template Authoring
This skill helps an agent create and validate custom `dotnet new` templates. It guides bootstrapping templates from existing projects and validates `template.json` files for authoring issues before publishing.
## When to Use
- User wants to create a reusable template from an existing .csproj
- User wants to validate a template.json they are authoring before publishing
- User is setting up `.template.config/template.json` from scratch
- User wants to package a template for NuGet distribution
## When Not to Use
- User wants to find or use existing templates — route to `template-discovery` or `template-instantiation`
- User has MSBuild issues unrelated to template authoring — route to `dotnet-msbuild` plugin
## Inputs
| Input | Required | Description |
|-------|----------|-------------|
| Source project path | For creation | Path to the .csproj to use as template source |
| template.json path | For validation | Path to an existing template.json to validate |
| Template name | For creation | Human-readable name for the template |
| Short name | Recommended | Short name for `dotnet new <shortname>` usage |
## Workflow
### Rules that change the answer
Use these structures exactly; do not invent fields from other template features.
**Deliver the requested artifact.** When the user says "show", "write", or "give me the
content", put the complete JSON/XML in the final response even if you also wrote it to disk.
Never edit this skill's `SKILL.md` or plugin documentation as a substitute for authoring the
user's template. Only create or modify template files when the user requested file changes and
the target template/project is present.
| Need | Correct structure | Never use |
|------|-------------------|-----------|
| Conditional XML in a `.csproj` | XML comments such as `<!--#if (database == "SqlServer") -->` and `<!--#endif -->` around the complete element | bare `#if` lines, which make the XML invalid |
| Restore generated projects | restore action `210D431B-A78B-4D2F-B762-4ED3E3EA9025`; use `primaryOutputs`, or `args.files` containing source-template paths/globs | run-script fields such as `executable` on the restore action |
| Restrict to the SDK host | a `host` constraint whose `args` is an array containing `{ "hostname": "dotnetcli" }`; its optional `version` restricts the host/CLI version | using host `version` when the requirement is specifically the active SDK version, the invalid host ID `dotnet-cli`, or unrelated `pattern` / `value` fields |
| Restrict the active SDK version | an `sdk-version` constraint with a NuGet version/range string in `args` | a machine-specific exact patch unless the template truly requires it |
| Preserve CPM | keep generated `PackageReference` items versionless and package the owning `Directory.Packages.props` when the template is self-contained | adding inline `Version` attributes |
| Package templates | a pack project with `<PackageType>Template</PackageType>` and template content packed below `content/` | describing a layout without showing the requested project file |
### Step 1: Bootstrap from existing project
Analyze the source `.csproj` and create a `.template.config/template.json`:
1. Copy the source project into a dedicated template-source directory by default, preserving
the original project untouched. Modify the original in place only when the user explicitly
asks for that layout.
2. Create `.template.config` inside the template-source directory.
3. Generate `template.json` with `identity` (reverse-DNS), `name`, `shortName`, `sourceName` (project name for replacement), `classifications`, and `tags`
4. Preserve from source — generic `dotnet new` templates frequently get these wrong, so verify each is carried over from the original `.csproj`:
1. **SDK type** — `Microsoft.NET.Sdk`, `Microsoft.NET.Sdk.Web`, `Microsoft.NET.Sdk.Worker`, etc.
2. **Analyzer/package reference metadata** — `PrivateAssets`, `IncludeAssets`, `ExcludeAssets`
3. **`OutputType` and other key properties** — `TreatWarningsAsErrors`, `Nullable`, `LangVersion`
4. **CPM participation** — no inline `Version` attributes when a `Directory.Packages.props` is present
5. **Custom build props/targets** and `Directory.Build.props` conventions
6. **Repo conventions** — folder layout, naming, `global.json` SDK pin
Minimal example:
```json
{
"$schema": "http://json.schemastore.org/template",
"author": "MyOrg",
"classifications": ["Library"],
"identity": "MyOrg.Templates.MyLib",
"name": "My Library Template",
"shortName": "mylib",
"sourceName": "MyLib",
"tags": { "language": "C#", "type": "project" }
}
```
**Required output — do not stop at a minimal stub.** Write the *complete* `.template.config/template.json` for the actual source project, then emit a short **conventions-preserved** confirmation so the user can see nothing was silently dropped. This carry-over is the whole value of the skill; a generic `dotnet new` template that loses these is why authoring ties with a hand-written stub.
| Source `.csproj` setting | Carried over? | How |
|--------------------------|---------------|-----|
| SDK (`Microsoft.NET.Sdk.*`) | ✅ | template content `.csproj` uses same SDK |
| `TreatWarningsAsErrors` / `Nullable` / `LangVersion` | ✅ | preserved verbatim in template `.csproj` |
| PackageReference `PrivateAssets` / `IncludeAssets` / `ExcludeAssets` | ✅ | metadata kept on each reference |
| CPM (`Directory.Packages.props` present) | ✅ | `ManagePackageVersionsCentrally` remains enabled and no inline `Version` attributes are emitted |
Mark any row you intentionally omitted as ⚠️ with a reason — never leave it implicit.
### Step 2: Validate template.json
Validate the generated `template.json` using the **template-validation** skill (it owns the full rule set — required fields, identity format, reserved shortName conflicts, parameter datatypes, post-actions, constraints, and tags).
Quick summary of what gets checked:
- **Required fields** — `identity`, `name`, and `shortName` must be present.
- **ShortName conflicts** — avoid names that collide with `dotnet new` subcommands. Read the authoritative set from the `Commands:` section of `dotnet new --help` for the installed SDK and do not hardcode it (it can change between versions); illustrative examples from current SDKs are `install`, `uninstall`, `update`, `list`, `search`, `details`, `create`. A conflict happens because `dotnet new <name>` would be parsed as the subcommand of the same name. Top-level `dotnet` verbs like `build`, `run`, `test`, and `publish` do NOT conflict. Run `dotnet new list` to confirm the name is not already taken.
- **Parameters, post-actions, tags** — see template-validation for the complete rules, including the valid datatype list.
### Step 3: Refine the template
Based on validation results and user requirements:
1. **Add parameters** with appropriate types (string, bool, choice), defaults, and descriptions
2. **Add conditional content** using the file type's valid syntax. In XML use template
directives inside XML comments, not bare preprocessor lines:
```xml
<!--#if (database == "SqlServer") -->
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<!--#endif -->
<!--#if (database == "Postgres") -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<!--#endif -->
```
3. **Configure post-actions** for solution add, restore, or custom scripts
4. **Set constraints** to restrict which SDKs or workloads the template supports
5. **Add classifications** and tags for discoverability
For a restore post-action, prefer `primaryOutputs` when the project path is known:
```json
"primaryOutputs": [{ "path": "MyProject.csproj" }],
"postActions": [{
"description": "Restore NuGet packages.",
"manualInstructions": [{ "text": "Run 'dotnet restore'." }],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}]
```
If `args.files` is needed, its paths are matched against the **source template** before
renames, for example `"files": ["**/*.csproj"]`. Explain that distinction.
### Step 4: Test the template locally
For a create-from-existing-project request, this step is required rather than optional:
install the authored template, run a dry-run, instantiate it into a temporary output folder,
and build the generated project. Report each observed result; inspecting `template.json` alone
does not prove the reusable template works.
```bash
dotnet new install ./path/to/template/root
dotnet new mylib --name TestProject --dry-run
dotnet new mylib --name TestProject --output ./test-output
dotnet build ./test-output/TestProject
```
When packaging is requested, include the complete pack project, not only a directory tree:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Contoso.ProjectTemplates</PackageId>
<PackageType>Template</PackageType>
<TargetFramework>net8.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Remove="**\*" />
<Content Include="templates\**\*" Pack="true" PackagePath="content\" />
</ItemGroup>
</Project>
```
The pack project's target framework applies only to the content-only packaging project; it
does not retarget projects inside `templates/`. Prefer a broadly available supported framework
unless the packaging project itself uses newer build features.
For a self-contained CPM template, the packaged `Directory.Packages.props` must include
`<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>` and every versionless
`PackageReference` must have a matching `PackageVersion`. Keep the props file at the intended
generated repository root; do not place a duplicate nearer the project where it changes lookup.
## Validation
- [ ] `template.json` passes manual validation with zero errors
- [ ] Template identity and shortName are unique and meaningful
- [ ] All parameters have descriptions and appropriate defaults
- [ ] Template can be installed, dry-run, and instantiated successfully
- [ ] Created projects build cleanly with `dotnet build`
- [ ] Conditional content produces correct output for all parameter combinations
- [ ] XML template directives are wrapped in XML comments and the generated project parses
- [ ] Host constraints use `args[].hostname`; restore actions use `primaryOutputs` or `args.files`
## Common Pitfalls
| Pitfall | Solution |
|---------|----------|
| Identity format issues | Use reverse-DNS format (e.g., `MyOrg.Templates.WebApi`). Avoid spaces or special characters. |
| ShortName conflicts with CLI commands | Avoid names that match a `dotnet new` subcommand; read the live set from `dotnet new --help` and don't hardcode it (illustrative examples: `install`, `uninstall`, `update`, `list`, `search`, `details`, `create`). Top-level verbs like `build`/`run`/`test`/`publish` are fine. Run `dotnet new list` to see if the name is already taken. |
| Missing parameter descriptions | Every parameter should have a `description` and `displayName` for discoverability. |
| Not testing all parameter combinations | Use `dotnet new <template> --dry-run` with different parameter values to verify conditional content works correctly. |
| Hardcoded versions in template | Use `sourceName` replacement for project names and consider parameterizing framework versions. |
| Not setting classifications | Add appropriate `classifications` (e.g., `["Web", "API"]`) for template discovery. |
| Reusing fields from a different constraint or post-action | Follow the exact schema: `host.args[].hostname`, and restore `args.files` rather than run-script fields. |
## More Info
- [Custom templates for dotnet new](https://learn.microsoft.com/dotnet/core/tools/custom-templates) — official authoring guide
- [template.json reference](https://github.com/dotnet/templating/wiki/Reference-for-template.json) — full schema reference
- [Template Engine Wiki](https://github.com/dotnet/templating/wiki) — template engine internals
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!