Ergonomic Temporal client SDK for Elixir.
Provides a high-level, protobuf-free API for interacting with Temporal workflow services. Work with plain Elixir terms — maps, keyword lists, strings — without constructing protobuf structs.
Add temporal_ex to your list of dependencies in mix.exs:
def deps do
[
{:temporal_ex, "~> 0.1.0"}
]
end# Start a client (as part of your supervision tree)
{:ok, client} = TemporalEx.connect(
target: "localhost:7233",
namespace: "default"
)
# Start a workflow
{:ok, handle} = TemporalEx.start_workflow(client, "MyWorkflow", [%{key: "value"}],
id: "my-workflow-123",
task_queue: "my-task-queue"
)
# Interact with the workflow
:ok = TemporalEx.WorkflowHandle.signal(handle, "my-signal", [%{data: 1}])
{:ok, description} = TemporalEx.WorkflowHandle.describe(handle)
{:ok, result} = TemporalEx.WorkflowHandle.result(handle)For production use, start TemporalEx.Client under your application supervisor:
children = [
{TemporalEx.Client,
name: :temporal_client,
target: "localhost:7233",
namespace: "default"
}
]
Supervisor.start_link(children, strategy: :one_for_one)Then reference the client by name:
handle = TemporalEx.get_workflow_handle(:temporal_client, "my-workflow-123")
{:ok, desc} = TemporalEx.WorkflowHandle.describe(handle)| Option | Default | Description |
|---|---|---|
:target |
"localhost:7233" |
Temporal server address |
:namespace |
"default" |
Default namespace |
:api_key |
nil |
API key or Bearer token |
:tls |
%{} |
TLS/mTLS config (see below) |
:name |
nil |
GenServer registration name |
:call_timeout |
5000 |
Default RPC timeout (ms) |
:connect_retry |
0 |
gRPC connection retries |
:data_converter |
TemporalEx.DataConverter.Json |
Custom data converter module |
:identity |
auto | Client identity string |
For Temporal Cloud or self-hosted TLS:
{TemporalEx.Client,
target: "my-ns.tmprl.cloud:7233",
namespace: "my-ns",
api_key: "my-api-key",
tls: %{
client_cert_pem_b64: System.get_env("TEMPORAL_CLIENT_CERT"),
client_key_pem_b64: System.get_env("TEMPORAL_CLIENT_KEY"),
ca_cert_file: "/path/to/ca.pem" # optional
}
}Temporal Cloud domains (.tmprl.cloud, .api.temporal.io) automatically use HTTPS.
# Start a workflow
{:ok, handle} = TemporalEx.start_workflow(client, "WorkflowType", args, opts)
# Start with an initial signal (atomic)
{:ok, handle} = TemporalEx.signal_with_start(client, "WorkflowType", args, "signal", signal_args, opts)
# Get a handle to an existing workflow
handle = TemporalEx.get_workflow_handle(client, "workflow-id")
handle = TemporalEx.get_workflow_handle(client, "workflow-id", "run-id"){:ok, description} = TemporalEx.WorkflowHandle.describe(handle)
:ok = TemporalEx.WorkflowHandle.signal(handle, "signal-name", [args])
{:ok, result} = TemporalEx.WorkflowHandle.query(handle, "query-type", [args])
:ok = TemporalEx.WorkflowHandle.cancel(handle)
:ok = TemporalEx.WorkflowHandle.terminate(handle, reason: "reason")
:ok = TemporalEx.WorkflowHandle.delete(handle)
{:ok, history} = TemporalEx.WorkflowHandle.get_history(handle)
{:ok, result} = TemporalEx.WorkflowHandle.result(handle)
{:ok, reset} = TemporalEx.WorkflowHandle.reset(handle, opts){:ok, workflows, next_token} = TemporalEx.list_workflows(client, "WorkflowType = 'MyWorkflow'")
{:ok, count} = TemporalEx.count_workflows(client, "WorkflowType = 'MyWorkflow'"){:ok, info} = TemporalEx.get_system_info(client)All errors are returned as typed structs:
TemporalEx.Error.WorkflowAlreadyStartedTemporalEx.Error.WorkflowNotFoundTemporalEx.Error.NamespaceNotFoundTemporalEx.Error.QueryFailedTemporalEx.Error.RPCError(catch-all)
case TemporalEx.start_workflow(client, "MyWorkflow", [], id: "wf-1", task_queue: "q") do
{:ok, handle} -> handle
{:error, %TemporalEx.Error.WorkflowAlreadyStarted{}} -> # handle duplicate
{:error, %TemporalEx.Error.RPCError{message: msg}} -> # handle other errors
end
Implement the TemporalEx.DataConverter behaviour to use a custom serialization format:
defmodule MyConverter do
@behaviour TemporalEx.DataConverter
@impl true
def encoding, do: "my-encoding"
@impl true
def encode(term), do: {:ok, {serialize(term), %{}}}
@impl true
def decode(binary, _metadata), do: {:ok, deserialize(binary)}
end
{TemporalEx.Client, data_converter: MyConverter, ...}MIT - see LICENSE.