workflow.Go() for creating goroutines in workflows
The Temporal Go SDK allows you to create additional goroutines (threads) in your Workflows by calling workflow.Go(). Native Go threading is never allowed in Workflow code, as it would create determinism errors.
Deterministic runner controls thread execution in workflows
Temporal's Go SDK contains a deterministic runner to control thread execution. This runner decides which Workflow thread to run in the right order, executing one at a time. Each task will execute in a loop until all threads are blocked.
workflow.Go() eliminates need for mutexes in workflows
workflow.Go() creates a new thread and adds it to the deterministic runner. This significantly minimizes the likelihood of race conditions and eliminates the need to use a mutex in Workflow code.
Only one Workflow thread can access data at a time
Although Temporal Workflows run asynchronously in Go, there is a control in place that ensures only one thread can access data at a time, preventing race conditions.
Timer Summary metadata in workflows
You can attach a Summary to timers within a Workflow using the Summary field in TimerOptions when creating a timer with workflow.NewTimerWithOptions(). The input format for Summary is a string limited to 200 bytes. The summary text is shown directly on the timer bar label in the Timeline tab.
Example: Setting static workflow summary and details
import (
"context"
"go.temporal.io/sdk/client"
)
func main() {
// Create the client
c, err := client.Dial(client.Options{})
if err != nil {
// Handle error
}
defer c.Close()
// Start workflow options with static summary and details
workflowOptions := client.StartWorkflowOptions{
ID: "your-workflow-id",
TaskQueue: "your-task-queue",
StaticSummary: "Order processing for customer #12345",
StaticDetails: "Processing premium order with expedited shipping",
}
// Start the workflow
we, err := c.ExecuteWorkflow(context.Background(), workflowOptions, YourWorkflow, "workflow input")
if err != nil {
// Handle error
}
}
Example: Getting and setting current workflow details
import (
"go.temporal.io/sdk/workflow"
)
func YourWorkflow(ctx workflow.Context, input string) (string, error) {
// Get the current details
currentDetails := workflow.GetCurrentDetails(ctx)
workflow.GetLogger(ctx).Info("Current details", "details", currentDetails)
// Set/update the current details
workflow.SetCurrentDetails(ctx, "Updated workflow details with new status")
return "Workflow completed", nil
}
Example: Setting timer summary in workflow
import (
"time"
"go.temporal.io/sdk/workflow"
)
func YourWorkflow(ctx workflow.Context, input string) (string, error) {
// Create a timer with options including summary
timerFuture := workflow.NewTimerWithOptions(ctx, 5*time.Minute, workflow.TimerOptions{
Summary: "Waiting for payment confirmation",
})
// Wait for the timer
err := timerFuture.Get(ctx, nil)
if err != nil {
return "", err
}
return "Timer completed", nil
}
StaticDetails workflow field
StaticDetails is a multi-line description that provides comprehensive information appearing in the Workflow details view. It is limited to 20K bytes. It supports standard Markdown formatting excluding images, HTML, and scripts. It is set via the StartWorkflowOptions when starting a workflow.
StaticSummary workflow field
StaticSummary is a single-line description that appears in the Workflow list view. It is limited to 200 bytes. It is set via the StartWorkflowOptions when starting a workflow.
Workflow definition in Go with activity execution
A Workflow is a Go function that orchestrates Activities and contains application logic. Workflows are resilient and can run for years even if infrastructure fails. To execute an Activity from a Workflow, use workflow.ExecuteActivity() and call .Get() to retrieve the result. Example: func SayHelloWorkflow(ctx workflow.Context, name string) (string, error) { ao := workflow.ActivityOptions{ StartToCloseTimeout: time.Second * 10, }; ctx = workflow.WithActivityOptions(ctx, ao); var result string; err := workflow.ExecuteActivity(ctx, Greet, name).Get(ctx, &result); if err != nil { return "", err }; return result, nil }
Start workflow execution in Go
To start a Workflow Execution, create a client, define StartWorkflowOptions with an ID and TaskQueue, then call c.ExecuteWorkflow() with the options, Workflow function, and parameters. Retrieve the result by calling .Get() on the returned WorkflowExecution handle. Example: options := client.StartWorkflowOptions{ ID: "greeting-workflow", TaskQueue: "my-task-queue", }; we, err := c.ExecuteWorkflow(context.Background(), options, greeting.SayHelloWorkflow, os.Args[1]); var result string; err = we.Get(context.Background(), &result)
Specify versioning intent for Continue-As-New
When using Continue-As-New, use the WithWorkflowVersioningIntent context modifier to specify the versioning intent. Example: ctx = workflow.WithWorkflowVersioningIntent(ctx, temporal.VersioningIntentUseAssignmentRules) followed by workflow.NewContinueAsNewError(ctx, "WorkflowName").
Example: Workflow with return value
type YourWorkflowResultObject struct {
WFResultFieldX string
WFResultFieldY int
}
func YourWorkflowDefinition(ctx workflow.Context, param YourWorkflowParam) (*YourWorkflowResultObject, error) {
// ... activity execution code ...
workflowResult := &YourWorkflowResultObject{
WFResultFieldX: activityResult.ResultFieldX,
WFResultFieldY: activityResult.ResultFieldY,
}
return workflowResult, nil
}
This shows a Workflow Definition that returns both a custom struct value and an error.
Example: Generate UUID with SideEffect in Workflow
var id string
encodedID := workflow.SideEffect(ctx, func(ctx workflow.Context) interface{} {
return uuid.New().String()
})
encodedID.Get(&id)
This shows how to generate a UUID inside a Side Effect, which records the result in the Event History and returns the recorded value on replay.
Example: Workflow with parameters and Activity execution
type YourWorkflowParam struct {
WorkflowParamX string
WorkflowParamY int
}
func YourWorkflowDefinition(ctx workflow.Context, param YourWorkflowParam) (*YourWorkflowResultObject, error) {
activityOptions := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Second,
}
ctx = workflow.WithActivityOptions(ctx, activityOptions)
activityParam := YourActivityParam{
ActivityParamX: param.WorkflowParamX,
ActivityParamY: param.WorkflowParamY,
}
var a *YourActivityObject
var activityResult YourActivityResultObject
err := workflow.ExecuteActivity(ctx, a.YourActivityDefinition, activityParam).Get(ctx, &activityResult)
if err != nil {
return nil, err
}
return nil
}
This shows a Workflow Definition that accepts a struct parameter, sets activity options, executes an activity, and handles the result.
Example: Detect replay in Workflow
if !workflow.IsReplaying(ctx) {
emitMetric("workflow_started", 1)
}
This shows how to use workflow.IsReplaying to guard code that should only run on the first execution, such as emitting metrics.
Use workflow.Now() for current time in Workflows
Use workflow.Now(ctx) instead of time.Now(). It returns the time of the last Workflow Task, which is consistent across replays. To wait, use workflow.Sleep(ctx, d) instead of time.Sleep().
Detect replay with workflow.IsReplaying
Use workflow.IsReplaying(ctx) to guard code that should only run on the first execution, such as emitting metrics or sending external notifications from an Interceptor. Never use this to affect Workflow business logic, as branching on replay status breaks determinism.
Example: Basic Workflow Definition in Go
func YourSimpleWorkflowDefinition(ctx workflow.Context) error {
// ...
return nil
}
This shows a basic Workflow Definition that takes workflow.Context as the first parameter and returns an error.
Workflow Definition is an exportable function in Go
In the Temporal Go SDK programming model, a Workflow Definition is an exportable function. The first parameter must be of type workflow.Context, which is used by the SDK to pass around Workflow Execution context.
Cannot iterate over maps with range in Workflows
In Go Workflow Definition code, you cannot directly iterate over maps using range because the order of the map's iteration is randomized. Instead, collect the keys of the map, sort them, and then iterate over the sorted keys to access the map. Alternatively, use a Side Effect or an Activity to process the map.
Cannot call external APIs or do file I/O directly in Workflows
Workflow Definition code cannot directly call an external API, conduct a file I/O operation, or talk to another service. Use an Activity for these operations instead.
Workflow logic must be deterministic
Workflow logic is constrained by deterministic execution requirements. Each Temporal SDK provides a set of APIs that can be used inside the Workflow to interact with application code outside the Workflow.
Customize Workflow Type with RegisterWorkflowWithOptions
In Go, by default, the Workflow Type name is the same as the function name. To customize the Workflow Type, use RegisterWorkflowWithOptions() with RegisterOptions containing the Name parameter when registering your Workflow with a Worker.
Workflow SDK replacements for Go standard library functions
The Temporal Go SDK provides these replacements for standard Go constructs to maintain determinism: workflow.Now() instead of time.Now(); workflow.Sleep() instead of time.Sleep(); workflow.GetLogger() instead of standard log package; workflow.Go() instead of go statement; workflow.Channel instead of native chan type; workflow.Selector instead of select statement; and workflow.Context instead of context.Context.
Workflow returns either value or error, not both
A Workflow Definition written in Go can return both a custom value and an error, but it is not possible to receive both in the calling process. The caller will receive either one or the other. Returning a non-nil error from a Workflow indicates that an error was encountered during execution and the Workflow Execution should be terminated, and any custom return values will be ignored by the system.
Generate random values with SideEffect in Workflows
The Go SDK does not provide a seeded random source or a UUID helper. Generate random numbers and UUIDs inside a Side Effect, which records the result in the Event History and returns the recorded value on replay. An Activity also works for this, and is the better choice when the value comes from an external system.
workflow.Context is the first required parameter
The first parameter of a Go-based Workflow Definition must be of the workflow.Context type. It is acquired from the go.temporal.io/sdk/workflow package and operates similarly to the standard context.Context entity, except that the Done() function returns workflow.Channel instead of a standard Go chan.
Workflow parameters must be serializable
All Workflow Definition parameters must be serializable and cannot be channels, functions, variadic, or unsafe pointers. The best practice is to pass a single parameter that is of a struct type for backward compatibility if new parameters are added.
Workflow return values must be serializable
Workflow return values must also be serializable. A Go-based Workflow Definition can return either just an error or a customValue, error combination. The best practice is to use a struct type to hold all custom values.
Example: Handle Workflow Cancellation with cleanup activity
```go
const WorkflowId = "example-cancellation-workflow"
const TaskQueueName = "cancellation"
func YourWorkflow(ctx workflow.Context) error {
logger := workflow.GetLogger(ctx)
var a *Activities
activityOptions := workflow.ActivityOptions{
StartToCloseTimeout: 30 * time.Minute,
HeartbeatTimeout: 5 * time.Second,
WaitForCancellation: true,
}
defer func() {
if !errors.Is(ctx.Err(), workflow.ErrCanceled) {
return
}
newCtx, _ := workflow.NewDisconnectedContext(ctx)
err := workflow.ExecuteActivity(newCtx, a.CleanupActivity).Get(ctx, nil)
if err != nil {
logger.Error("CleanupActivity failed", "Error", err)
}
}()
ctx = workflow.WithActivityOptions(ctx, activityOptions)
var result string
err := workflow.ExecuteActivity(ctx, a.ActivityToBeCanceled).Get(ctx, &result)
logger.Info(fmt.Sprintf("ActivityToBeCanceled returns %v, %v", result, err))
err = workflow.ExecuteActivity(ctx, a.ActivityToBeSkipped).Get(ctx, nil)
logger.Error("Error from ActivityToBeSkipped", "Error", err)
return err
}
```
This example shows how to use defer with workflow.NewDisconnectedContext to execute a cleanup activity when cancellation is detected.
Handle Cancellation in Workflow with defer and NewDisconnectedContext
Workflow Definitions can handle execution cancellation requests using Go's defer statement and the workflow.NewDisconnectedContext API. When a Workflow receives a Cancellation Request, check if the context error is workflow.ErrCanceled using errors.Is(ctx.Err(), workflow.ErrCanceled). If cancellation is detected, call workflow.NewDisconnectedContext(ctx) to create a new context that is not affected by the cancellation, allowing execution of cleanup activities. A cleanup activity can be executed with this disconnected context to perform graceful shutdown tasks.
Workflow Cancellation Status depends on Activity handling
If a Workflow receives a Cancellation Request but all Activities gracefully handle the Cancellation, and/or no Activities are skipped, the Workflow status will be Complete. Whether to return the Cancellation error to show a Canceled status or Complete status regardless of cancellation propagation is determined by business process needs and use case requirements.
Cancel Workflow with CancelWorkflow API
Use the client.CancelWorkflow API to cancel a Workflow Execution. The method signature is CancelWorkflow(context.Context, workflowID string, runID string). The workflowID parameter is required. The runID parameter is optional; if provided empty string, cancellation targets only by workflow ID. If a runID is supplied, it ensures the correct Workflow Execution is cancelled.
Example: Cancel Workflow using client.CancelWorkflow
```go
func main() {
temporalClient, err := client.Dial(client.Options{
HostPort: client.DefaultHostPort,
})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer temporalClient.Close()
err = temporalClient.CancelWorkflow(context.Background(), cancellation.WorkflowId, "")
if err != nil {
log.Fatalln("Unable to cancel Workflow Execution", err)
}
log.Println("Workflow Execution cancelled", "WorkflowID", cancellation.WorkflowId)
}
```
This example shows how to call CancelWorkflow API with a workflow ID and empty run ID to cancel a Workflow Execution.
Dynamic workflow function signature in Go
A dynamic workflow definition must accept a single argument of type converter.EncodedValues and return a string and error. The function signature is: func DynamicWorkflow(ctx workflow.Context, args converter.EncodedValues) (string, error).
Decode arguments from EncodedValues in dynamic workflow
In a dynamic workflow, decode the EncodedValues argument using the Get() method. Call args.Get(&arg1, &arg2) where arg1 and arg2 are pointers to the types you expect. This returns an error if decoding fails.
Register a dynamic workflow with Go SDK
A dynamic workflow in Temporal is invoked at runtime if no other workflow with the same name is registered. Use worker.RegisterDynamicWorkflow() to register a workflow as dynamic. You must register the workflow with the Worker before it can be invoked. Only one dynamic workflow can be present on a Worker.
Dynamic workflow example with Go SDK
func DynamicWorkflow(ctx workflow.Context, args converter.EncodedValues) (string, error) {
var result string
info := workflow.GetInfo(ctx)
var arg1, arg2 string
err := args.Get(&arg1, &arg2)
if err != nil {
return "", fmt.Errorf("failed to decode arguments: %w", err)
}
if info.WorkflowType.Name == "dynamic-activity" {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: 10 * time.Second})
err := workflow.ExecuteActivity(ctx, "random-activity-name", arg1, arg2).Get(ctx, &result)
if err != nil {
return "", err
}
} else {
result = fmt.Sprintf("%s - %s - %s", info.WorkflowType.Name, arg1, arg2)
}
return result, nil
}
This example shows a dynamic workflow that checks its own name to determine behavior, decodes two string arguments, and either executes a dynamic activity or formats a string response.
Access workflow type name in dynamic workflow
Use workflow.GetInfo(ctx) to get workflow information, then access the WorkflowType.Name field to determine which workflow was invoked. This allows dynamic workflows to behave differently based on the invoked workflow name.
Example: Continue-As-New implementation in Go
return ClusterManagerResult{}, workflow.NewContinueAsNewError(
ctx,
ClusterManagerWorkflow,
ClusterManagerInput{
State: &cm.state,
TestContinueAsNew: cm.testContinueAsNew,
},
)
This example shows how to return NewContinueAsNewError with the Workflow function, current state in the input parameters, and test hook flag.
Check current history length using GetCurrentHistoryLength
Call workflow.GetInfo(ctx).GetCurrentHistoryLength() to get the current history length. This can be used in a helper method to determine if Continue-As-New should be triggered, especially when using a test hook with a custom maxHistoryLength threshold.
Return NewContinueAsNewError to trigger Continue-As-New in Go
Inside your Workflow, return the workflow.NewContinueAsNewError error. This stops the Workflow right away and starts a new one with the specified Workflow function and input parameters.
Go SDK Workflow documentation topics
The Go SDK Workflow documentation covers the following topics: Workflow basics, Child Workflows, Continue-As-New, Cancellation, Timeouts, Message passing, Selectors, Side effects, Schedules, Timers, Dynamic Workflow, Versioning, and Workflow Streams.
Describe a Schedule in Go
To describe a Schedule in Go, use `Describe()` on the ScheduleHandle. This retrieves information about the current Schedule configuration, including details about the Schedule Spec (such as Intervals), CronExpressions, and Schedule State.
List all Schedules in Go
To return information on all Schedules in Go, use `ScheduleClient.List()`. This method returns all available Schedules and their respective Schedule IDs. The List method accepts `client.ScheduleListOptions{}` with a PageSize field to control pagination.
Update a Schedule in Go
To update a Schedule in Go, use `Update()` on ScheduleHandle. This method accepts `client.ScheduleUpdateOptions{}` with a DoUpdate field that is a function taking `client.ScheduleUpdateInput` and returning `*client.ScheduleUpdate`. Changes can be made to Workflow Actions, Action parameters, Memos, and the Workflow's Cancellation Policy.
Temporal Cron Jobs deprecation recommendation
Schedules are recommended over Temporal Cron Jobs. Schedules were built to provide a better developer experience, including more configuration options and the ability to update or pause running Schedules.
Pause and Unpause a Schedule in Go
To pause a Schedule in Go, use `Pause()` on ScheduleHandle with `client.SchedulePauseOptions{}` which includes a Note field. To unpause, use `Unpause()` on ScheduleHandle with `client.ScheduleUnpauseOptions{}` which includes a Note field. Pausing halts all future Workflow Runs on a Schedule; unpausing allows the Workflow to execute as planned. A Schedule can also be created in paused state by setting `Paused: true` in `client.ScheduleOptions{}`.
CronSchedule field in StartWorkflowOptions
The CronSchedule field in StartWorkflowOptions is a string type with no default value. It specifies a cron expression for recurring Workflow Executions. Example: `workflowOptions := client.StartWorkflowOptions{CronSchedule: "15 8 * * *", ...}; workflowRun, err := c.ExecuteWorkflow(context.Background(), workflowOptions, YourWorkflowDefinition)`.
Backfill a Schedule in Go
To backfill a Schedule in Go, use `Backfill()` on ScheduleHandle. Specify the start and end times to execute the Workflow, along with the overlap policy. Backfilling executes Workflow Tasks ahead of the Schedule's specified time range, useful for executing a missed or delayed Action or for testing the Workflow ahead of time.
Start Delay for one-time scheduled Workflow execution
Use StartDelay to schedule a Workflow Execution at a specific one-time future point rather than on a recurring schedule. Create an instance of StartWorkflowOptions from the go.temporal.io/sdk/client package, set the StartDelay field to a time.Duration value, and pass the instance to the ExecuteWorkflow call.
StartDelay example for scheduled execution
Example of using StartDelay to start a workflow in 12 hours: `workflowOptions := client.StartWorkflowOptions{StartDelay: time.Hours * 12}; workflowRun, err := c.ExecuteWorkflow(context.Background(), workflowOptions, YourWorkflowDefinition)`.
Create a Schedule in Go
To create a Schedule in Go, use `Create()` on the ScheduleClient. Schedules must be initialized with a Schedule ID, Spec, and Action in `client.ScheduleOptions{}`. Example: `temporalClient.ScheduleClient().Create(ctx, client.ScheduleOptions{ID: scheduleID, Spec: client.ScheduleSpec{}, Action: &client.ScheduleWorkflowAction{ID: workflowID, Workflow: schedule.ScheduleWorkflow, TaskQueue: "schedule"}})`.
Delete a Schedule in Go
To delete a Schedule in Go, use `Delete()` on the ScheduleHandle. Deleting a Schedule erases the Schedule configuration but does not affect any Workflows that were already started by the Schedule.
Trigger a Schedule in Go
To trigger a Scheduled Workflow Execution in Go, use `Trigger()` on ScheduleHandle with `client.ScheduleTriggerOptions{}`. Triggering immediately executes an Action defined in that Schedule. By default, trigger is subject to the Schedule's Overlap Policy. The Overlap field in ScheduleTriggerOptions can specify the overlap policy for that trigger.
SideEffect incorrect implementation pitfall
Do not modify a variable inside the SideEffect function and expect it to persist. In this incorrect approach, the variable is modified inside the function but returns nil, which means on replay the variable will always have its zero value, breaking determinism:
```go
// Warning: This is an incorrect example.
var random int
workflow.SideEffect(func(ctx workflow.Context) interface{} {
random = rand.Intn(100)
return nil
})
// random will always be 0 in replay, so this code is non-deterministic.
```
SideEffect correct implementation example
The correct way to use SideEffect is to capture the returned EncodedValue and call Get() on it with a pointer to store the result:
```go
encodedRandom := workflow.SideEffect(ctx, func(ctx workflow.Context) interface{} {
return rand.Intn(100)
})
var random int
encodedRandom.Get(&random)
```
Go SDK SideEffect function signature and usage
Use the SideEffect function from the go.temporal.io/sdk/workflow package to execute a Side Effect directly in your Workflow. Pass it an instance of context.Context and the function to execute. The SideEffect API returns a Future, an instance of converter.EncodedValue. Use the Get method on the Future to retrieve the result of the Side Effect.