Authoring Templates

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The C# Reference bibliography lists the reference material consulted while preparing these pages.

A dotnet new template is an ordinary, buildable project (or set of files) plus a .template.config/template.json that tells the template engine what to rename, which options to offer and what to do afterwards. Because the source is a working project, the template is tested by building it. This page extends Ledgerline's ledgerline-app template with .NET SDK 10.0.402. Using templates is on dotnet new and Templates; packing and publishing the template package is on Tool and Template Packages.

Structure

src/Ledgerline.Templates/
  Ledgerline.Templates.csproj          the packaging project (PackageType=Template)
  templates/
    ledgerline-app/                    one template: a working console project
      .template.config/
        template.json                  the template definition
      LedgerlineApp.csproj             "LedgerlineApp" is replaced by the user's -n name
      Program.cs
      journal.csv                      optional content

Each folder that contains a .template.config is one template; a package can hold many.

template.json

{
  "$schema": "http://json.schemastore.org/template",
  "author": "Ledgerline contributors",
  "classifications": [ "Console", "Accounting" ],
  "identity": "Ledgerline.Templates.App",
  "name": "Ledgerline console app",
  "shortName": "ledgerline-app",
  "sourceName": "LedgerlineApp",
  "preferNameDirectory": true,
  "tags": { "language": "C#", "type": "project" },
  "symbols": {
    "Framework": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [ { "choice": "net10.0" }, { "choice": "net8.0" } ],
      "defaultValue": "net10.0",
      "replaces": "net10.0",
      "description": "The target framework."
    },
    "IncludeSample": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Add a sample journal.csv and print its trial balance."
    }
  },
  "sources": [
    {
      "modifiers": [
        { "condition": "(!IncludeSample)", "exclude": [ "journal.csv" ] }
      ]
    }
  ],
  "primaryOutputs": [ { "path": "LedgerlineApp.csproj" } ],
  "postActions": [
    {
      "description": "Restore NuGet packages required by this project.",
      "manualInstructions": [ { "text": "Run 'dotnet restore'" } ],
      "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
      "continueOnError": true
    }
  ]
}
Key Meaning

identity

Unique, stable ID of the template (not shown to users).

name, shortName

Display name, and the name typed after dotnet new.

sourceName

Text replaced everywhere — file names and content — by the -n value (LedgerlineApp.csproj becomes Shop.csproj, and namespace LedgerlineApp would become namespace Shop).

preferNameDirectory

Create a folder named after -n when -o is not given.

classifications, tags

Shown in dotnet new list (Console/Accounting); tags.type is project, item or solution.

symbols

Options. A parameter has a datatype (string, bool, choice, …); replaces substitutes its value for a literal in the source.

sources.modifiers

Include, exclude or rename files under a condition.

primaryOutputs

The files post actions operate on.

postActions

Steps after creation; 210D431B-… is "restore".

Conditional content

Inside source files, the template engine processes #if (Symbol) / #else / #endif — in C# they look like preprocessor directives, so the template source still compiles:

using Ledgerline;

#if (IncludeSample)
var path = args.Length > 0 ? args[0] : "journal.csv";
#else
var path = args[0];
#endif
var lines = LedgerReader.ReadFile(path);
Console.WriteLine(Ledger.ToJson(Ledger.TrialBalance(lines)));

Whole files are included or excluded with sources.modifiers, as journal.csv is above.

Testing from the folder

During development, install the template from its folder — no package needed — and instantiate it both ways:

dotnet new install ~/ledgerline/src/Ledgerline.Templates/templates/ledgerline-app
dotnet new ledgerline-app -h
dotnet new ledgerline-app -n Shop
dotnet new ledgerline-app -n Bare --IncludeSample false
Success: ~/ledgerline/src/Ledgerline.Templates/templates/ledgerline-app installed the following templates:
Template Name           Short Name      Language  Tags
----------------------  --------------  --------  ------------------
Ledgerline console app  ledgerline-app  [C#]      Console/Accounting

Template options:
  -F, --Framework <net10.0|net8.0>  The target framework.
                                    Type: choice
                                      net10.0
                                      net8.0
                                    Default: net10.0
  -I, --IncludeSample               Add a sample journal.csv and print its trial balance.
                                    Type: bool
                                    Default: true

The template "Ledgerline console app" was created successfully.

Processing post-creation actions...
Restoring ~/try/Shop/Shop.csproj:
  Determining projects to restore...
  Restored ~/try/Shop/Shop.csproj (in 381 ms).
Restore succeeded.

The template "Ledgerline console app" was created successfully.

The results:

  • Shop/ held Shop.csproj, Program.cs and journal.csv; its Program.cs kept the #if (IncludeSample) branch, and dotnet run printed the sample’s trial balance.

  • Bare/ held only Bare.csproj and Program.cs, with var path = args[0]; — the #else branch, and no journal.

The short options (-F, -I) were generated from the symbol names. dotnet new uninstall <folder> removes a folder-installed template. Template creation can run the restore and other actions, so — as the documentation warns — install templates only from sources you trust.

Localization

The name, description and symbol texts can be translated with one file per culture in .template.config/localize/templatestrings.<culture>.json (for example templatestrings.de.json). dotnet new and Visual Studio show the strings for the current UI culture. The templatepack project template from Microsoft.TemplateEngine.Authoring.Templates generates a packaging project with template validation and localization tasks already wired in.

Common mistakes

  • A sourceName that also appears in unrelated text — every occurrence is replaced.

  • Changing identity between versions — users end up with two templates.

  • Template source that does not build — build it in CI like any other project.

  • Relying on the restore post action in CI — restore explicitly; the action can be skipped (--no-restore).