TemporalCommunity.Templates
0.5.0
Prefix Reserved
dotnet new install TemporalCommunity.Templates@0.5.0
Temporal .NET Templates
TemporalCommunity.Templates provides dotnet new templates for scaffolding Temporal .NET code.
Generated code is plain, idiomatic Temporal .NET SDK
([Workflow]/[WorkflowRun], [Activity]) — it does not depend on the
TemporalCommunity.DurableObjects runtime. Use these templates as a starting point for any
Temporal .NET project, whether or not you adopt Durable Objects elsewhere.
Installation
dotnet new install TemporalCommunity.Templates
Update to the latest version:
dotnet new update
Uninstall:
dotnet new uninstall TemporalCommunity.Templates
Item templates
Item templates add a single file to an existing project. Run them from inside the target project's directory (or a subdirectory of it) so namespace binding resolves correctly.
temporal-workflow
Creates a new plain Temporal workflow class ([Workflow]/[WorkflowRun]).
dotnet new temporal-workflow -n OrderWorkflow
Omit -n to get the default placeholder name TemporalWorkflow1. The generated class's namespace
is bound to the target project's RootNamespace.
temporal-activity
Creates a new plain Temporal activity class ([Activity]).
dotnet new temporal-activity -n RecordViewActivity
Omit -n to get the default placeholder name TemporalActivity1. The generated class's namespace
is bound to the target project's RootNamespace. Activities (unlike workflow code) can freely do
I/O, use ILogger, and access DI — they run outside the workflow scheduler.
The generated method name is derived from the requested class name, so each activity item has a distinct Temporal activity type. The item template deliberately contains one activity method and does not configure a task queue; queue routing belongs to a Worker.
temporal-converter
Creates a custom payload converter with a nested encoding class in one file:
MyConverter.MyConverterEncoding— a nestedIEncodingConverterfor one custom wire format. ImplementTryToPayloadandToValuehere.MyConverter— aDefaultPayloadConvertersubclass that keeps the SDK's default encoding converters in their default order and putsMyConverter.MyConverterEncodingjust beforeJsonPlainConverter. Converters are tried in order, andJsonPlainConverteraccepts any value.
dotnet new temporal-converter -n MyConverter
Omit -n to get the default name TemporalConverter1. The generated classes' namespace is bound
to the target project's RootNamespace. Pass the converter through a data converter in the
Temporal client or worker options:
var dataConverter = DataConverter.Default with { PayloadConverter = new MyConverter() };
Until TryToPayload is implemented it returns false, so values fall through to the default
converters.
The generated encoding identifier includes the namespace and type name and ends in /v1. It is a
persistent wire contract; keep it stable for existing payloads and add a new version if the format
changes incompatibly.
To change only JSON serializer settings, you don't need this template; use
new DefaultPayloadConverter(jsonSerializerOptions) instead.
See Temporal data conversion best practices.
All item templates require the target project to reference the Temporalio package; they cannot
add a PackageReference to an existing .csproj. Add it first if needed:
dotnet add package Temporalio
Project templates
Project templates generate a complete, standalone project rather than a single file.
temporal-worker
Creates a single console-host project running a Temporal worker
(Temporalio.Extensions.Hosting), with a resolved connection and an optional OpenTelemetry setup.
dotnet new temporal-worker -n OrderProcessing.Worker
Options:
--framework <net8.0|net10.0>(defaultnet10.0) — the target framework.--include-otel(default off) — adds a"Temporalio"ActivitySource, the client-sideTracingInterceptor, and an OTLP exporter to the generated project.
Generated files:
| File | Purpose |
|---|---|
<Name>.csproj |
Console project referencing Temporalio, Temporalio.Extensions.Hosting, and TemporalCommunity.Extensions.Analyzers. |
Program.cs |
Host setup: resolves connection options, registers ITemporalClient, and registers the worker. |
TemporalWorkerConnection.cs |
public static class TemporalWorkerConnection with the Resolve(...) method described below. |
Workflows/SampleWorkflow.cs, Activities/SampleActivities.cs |
A starter [Workflow]/[Activity] pair, wired up in Program.cs via AddWorkflow<T>()/AddScopedActivities<T>(). |
Package version pins. Temporalio, Temporalio.Extensions.Hosting,
Temporalio.Extensions.OpenTelemetry, OpenTelemetry.Extensions.Hosting, and
OpenTelemetry.Exporter.OpenTelemetryProtocol are pinned to exact versions directly in the
generated .csproj's PackageReference items — bump them there when newer stable releases ship.
The current generated project templates use Temporalio 1.20.0.
The TemporalCommunity.Extensions.Analyzers version is pinned once, in a
TemporalCommunityAnalyzersVersion MSBuild property near the top of the same .csproj, so bumping
it is a one-line change independent of this template package's own version.
Configuring the OTLP exporter. --include-otel wires up span creation, but spans are only
exported when the OTEL_EXPORTER_OTLP_ENDPOINT environment variable is set — the generated
Program.cs checks it at startup and only calls UseOtlpExporter() when it's present (the same
gating convention Aspire's own ServiceDefaults project template uses, so the same environment
variable works whether or not this project later adopts Aspire). Point it at a local collector,
Jaeger, or Grafana Tempo endpoint before running the generated worker, e.g.:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
dotnet run
Without that variable set, --include-otel still compiles and runs — it just doesn't send spans
anywhere.
Multi-project templates
temporal-solution
Creates a Worker + Client + Shared multi-project solution running plain Temporalio workflows/activities, with optional .NET Aspire orchestration.
dotnet new temporal-solution -n Contoso.Fulfillment -o Contoso.Fulfillment
When -n is omitted, the generated name is derived from the output/current directory; it is not
automatically TemporalSolution.1 because this template does not set preferDefaultName: true.
Use -n TemporalSolution.1 explicitly when testing the digit-after-dot naming case.
Options:
--framework <net8.0|net10.0>(defaultnet10.0) — applied to Worker, Client, and Shared, plus AppHost and ServiceDefaults when--include-aspireis enabled.--include-aspire(default off) — adds anAppHostandServiceDefaultsproject. The AppHost referencesTemporalCommunity.Aspire.Hostingand callsAddTemporalLocalDevServer, which auto-provisions a local Temporal dev server when the AppHost starts — no separately installed Temporal CLI required for this path. With--include-aspireoff (the default), you run your owntemporal server start-dev(or point at Temporal Cloud), exactly liketemporal-workerand every sample insamples/.--include-otel(default off) — adds a"Temporalio"ActivitySourceand the client-sideTracingInterceptorto Worker and Client. When combined with--include-aspire, the exporter comes fromServiceDefaults; without it, a standalone OTLP exporter is added instead (see "Configuring the OTLP exporter" above — the sameOTEL_EXPORTER_OTLP_ENDPOINTconvention applies).
Generated projects:
| Project | Purpose |
|---|---|
<Name>.Shared |
Class library holding SharedTemporalConnection and the starter [Workflow]/[Activity] pair. Referenced by both Worker and Client — Client needs it for type-safe StartWorkflowAsync<SampleWorkflow>(...) calls. Carries the TemporalCommunity.Extensions.Analyzers reference, since that's where the workflow/activity code lives. |
<Name>.Worker |
Console host running the Temporal worker, referencing Shared. |
<Name>.Client |
Console host that starts the sample workflow, waits for its result, prints it, and exits — a one-shot demo, not a long-running service. |
<Name>.AppHost (--include-aspire only) |
Aspire orchestrator: provisions the local Temporal dev server and runs Worker + Client as Aspire resources. |
<Name>.ServiceDefaults (--include-aspire only) |
Service discovery, HTTP resilience, and OpenTelemetry wiring shared by Worker and Client. Trimmed from Aspire's own ServiceDefaults template to what applies to plain Generic Host console apps — no ASP.NET Core instrumentation or health-check endpoints, since neither Worker nor Client is a web application. |
Run a generated solution
For a standalone solution, start a Temporal server separately (or configure a remote one), then run the Worker and Client in separate terminals:
dotnet new temporal-solution -n Contoso.Fulfillment -o Contoso.Fulfillment
cd Contoso.Fulfillment
temporal server start-dev
# In another terminal, from the generated solution directory:
dotnet run --project Contoso.Fulfillment.Worker
# In a third terminal, from the same directory:
dotnet run --project Contoso.Fulfillment.Client
The standalone example requires the Temporal CLI for
temporal server start-dev; if using a remote server instead, configure the connection as
described below. The generated Client is a one-shot demo; the Worker keeps running.
For an Aspire-enabled solution, use one of these launch methods from the generated solution directory:
dotnet new temporal-solution -n Contoso.Fulfillment -o Contoso.Fulfillment --include-aspire
cd Contoso.Fulfillment
dotnet run --project Contoso.Fulfillment.AppHost
# Or, with the Aspire CLI installed:
aspire run --apphost Contoso.Fulfillment.AppHost/Contoso.Fulfillment.AppHost.csproj
# Or, with the Aspire CLI installed, for a background session:
dotnet build
aspire start --apphost Contoso.Fulfillment.AppHost/Contoso.Fulfillment.AppHost.csproj
The AppHost sets AspireUseCliBundle=true. For dotnet run, the Aspire SDK selects an installed
compatible aspire on PATH when available, or invokes the Aspire CLI version paired with the
AppHost SDK through DNX. The DNX fallback requires the .NET 10 SDK and access to the configured
NuGet sources. aspire run and aspire start instead require the
Aspire CLI installed and on PATH; use
aspire stop --apphost Contoso.Fulfillment.AppHost/Contoso.Fulfillment.AppHost.csproj to stop a
background session. Aspire mode provisions the Temporal dev server through
TemporalCommunity.Aspire.Hosting; it does not require a separately installed Temporal CLI.
Build the generated solution before aspire start so its resource projects have all referenced
assemblies in their output; aspire start launches those projects with --no-build.
See the official Aspire SDK CLI-bundle and launch documentation
and DNX documentation.
If dotnet run cannot find a compatible installed Aspire CLI, check that .NET 10's dnx is
available and that NuGet restore can reach its sources; alternatively install the Aspire CLI.
If aspire run or aspire start is not recognized, install the Aspire CLI first. A successful
restore or build does not, by itself, confirm the AppHost or the Temporal dev server started.
Non-trivial project names. A single sourceName token replace isn't enough for a
multi-project solution with strongly-typed Aspire project references and XML project files, so
separate symbols handle the edge cases: GeneratedNamespacePrefix preserves a valid C# namespace
prefix, GeneratedAspirePrefix sanitizes the name into a valid identifier (used for
Projects.<Name>_Worker-style references in AppHost.cs), and XmlEncodedProjectName XML-escapes
the name for use inside .csproj ProjectReference paths.
Both track the folder names on disk (which use the raw, unescaped name), so a name like
Contoso-Fulfillment&Orders produces valid C# (Contoso_Fulfillment_Orders) and valid XML
(Contoso-Fulfillment&Orders) even though the actual directory on disk keeps the literal &.
The namespace and Aspire identifiers are intentionally separate. Dots in a C# namespace are
preserved, C# keyword segments are escaped with @, and invalid or digit-leading segments are
sanitized for C#. Aspire's Projects.* identifiers use a separate flattened valid identifier:
-n |
C# namespace prefix | Aspire project prefix |
|---|---|---|
class |
@class |
class |
Acme.class |
Acme.@class |
Acme_class |
Contoso.Fulfillment |
Contoso.Fulfillment |
Contoso_Fulfillment |
TemporalSolution.1 |
TemporalSolution._1 |
TemporalSolution__1 |
Omitting -n is a separate case: the CLI derives the name from the output/current directory
instead of using the template's defaultName (TemporalSolution.1), because
preferDefaultName is not enabled.
Package version pins follow the same one-place-per-package convention as temporal-worker.
Generated project templates pin Temporalio 1.20.0. When upgrading from a version before 1.18.0,
note that workers now enforce outbound payload/memo size limits before sending: over-limit task
completions fail retryably instead of reaching the server and failing non-retryably. Review payload
size warnings (TMPRL1103) and Temporal .NET SDK 1.18.0 release notes
before upgrading workloads that use large payloads or a size-changing payload proxy.
Connecting to Temporal
temporal-worker's generated TemporalWorkerConnection.cs resolves a
TemporalClientConnectOptions using a fixed three-step precedence, implemented in
TemporalWorkerConnection.Resolve(IConfiguration, ClientEnvConfig.ProfileLoadOptions?):
- Environment variables or a Temporal CLI profile —
ClientEnvConfig.LoadClientConnectOptions(...)picks upTEMPORAL_ADDRESS/TEMPORAL_NAMESPACE/etc., or a named profile from atemporal.tomlfile. If this already supplies a target host, it wins outright — step 2 below is never consulted. Temporal:Addressconfiguration — only when step 1 found no target host. A present-but-blank value (e.g. an empty string from a config source) is treated the same as a missing one and falls through to step 3, rather than being used as-is.localhost:7233— the final fallback, matching every sample insamples/that assumes a locally runningtemporal server start-dev.
Temporal Cloud credentials, TLS settings, namespace, and RPC metadata always come from step 1
(environment variables or a profile) — never from Temporal:Address, which controls only the
target host. To connect to Temporal Cloud, configure a Temporal CLI profile or the relevant
TEMPORAL_* environment variables; do not try to express an API key or TLS setting through
Temporal:Address, since Resolve never reads anything else from it.
The temporal-solution multi-project template reuses this exact same precedence via an equivalent
SharedTemporalConnection helper in its Shared project, so Worker and Client never end up
pointed at different servers. When --include-aspire is enabled, AddTemporalLocalDevServer's
WithReference injects TEMPORAL_ADDRESS/TEMPORAL_NAMESPACE env vars into both projects, which
is what typically satisfies step 1 above — Temporal Cloud credentials, TLS, namespace, and RPC
metadata still come from environment/profile configuration, not from anything Aspire-specific.
Every generated host (temporal-worker, and temporal-solution's Worker and Client) registers
ITemporalClient through the SDK's AddTemporalClient(Action<TemporalClientConnectOptions>) from
Temporalio.Extensions.Hosting. Because that callback mutates the SDK's own options instance
rather than replacing it, the generated ApplyTo(resolved, options) helper next to Resolve
explicitly copies every resolved connection and client setting — target host, namespace, TLS,
API key, RPC metadata and binary metadata, RPC retry, keepalive, HTTP CONNECT proxy, DNS load
balancing, gRPC compression, payload limits, identity, runtime, data converter, interceptors, query reject
condition, and plugins. It deliberately leaves LoggerFactory alone so the host's
ILoggerFactory, which the SDK assigns first, is kept. With --include-otel, the
TracingInterceptor is appended after any interceptors already present, exactly once. The SDK
creates the client lazily, so it connects on first use.
Task queues and application routing
Each generated project application receives a stable, name-derived default task queue rather than
a fixed literal shared by every generated project, so independent generated applications in the
same Temporal namespace do not collide by default. The default is the template's substituted name
followed by -tq, with no additional prefix — for example, temporal-worker -n OrderProcessing.Worker defaults to OrderProcessing.Worker-tq, and temporal-solution -n Alpha
defaults to Alpha-tq for both the generated Worker and Client, since both derive the default from
the same generated name and always agree when no override is configured. All generated project
applications read the same configuration key:
Temporal:TaskQueue
A non-empty Temporal:TaskQueue value overrides the applicable default. A missing or null value
uses the default. A blank or whitespace-only value is rejected explicitly with
InvalidOperationException:
Configuration value 'Temporal:TaskQueue' must not be blank. Set it to a valid task queue name or remove it.
Configure the value through the normal .NET configuration providers when deploying. Changing a queue for an already deployed application is a routing migration: deploy compatible consumers, drain or complete work on the old queue, and only then remove the old routing.
The standalone Worker and solution Worker use separate generated connection helpers. The standalone
helper is TemporalWorkerConnection, avoiding ambiguity with the Temporal SDK's own
TemporalConnection type. The solution helper remains SharedTemporalConnection because it is
intentionally shared by the Worker and Client.
Client lifecycle and restore behavior
The generated solution Client is a one-shot BackgroundService: it starts the sample workflow,
waits for the result, logs it, and requests host shutdown. It passes the host stopping token to
both the start RPC and the separate result-wait RPC. Cancellation caused by ordinary local
shutdown is treated as a normal stop; genuine workflow or RPC failures are logged and produce a
nonzero process exit status. Local RPC cancellation does not cancel the server-side workflow.
Both temporal-worker and temporal-solution restore their generated projects after creation.
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.5.0 | 36 | 10/2/2026 |