The Modal JS and Go SDKs went into beta with the version 0.5 release in October 2025. This release brings us closer to feature parity with the Python SDK (with notable exceptions like defining functions, Volume filesystem API, some Image building APIs, and Dicts not yet supported). It's a big step towards bringing JavaScript/TypeScript and Go to the same high level of developer experience and stability as the Python SDK.
The beta release includes breaking changes to improve SDK ergonomics and align with general SDK best practices. While adapting requires some code changes, we believe these improvements make Modal easier to use going forward.
The main changes are:
- The SDKs now expose a central Modal Client object as the main entry point for interacting with Modal resources.
- The interface for working with Modal object instances (Functions, Sandboxes, Images, etc.) is largely the same as before, with some naming changes.
- Calling deployed Functions and classes now uses a new protocol for payload serialization which requires the deployed apps to use the Modal Python SDK 1.2 or newer.
- Internally removed the global client (and config/profile data in global scope), moving all that to the Client type.
- Consistent parameter naming across both SDKs: all
Optionsstructs/interfaces renamed toParams. - Go-specific changes:
- Changed how we do context passing, so contexts now only affect the current operation and are not used for lifecycle management of the created resources.
- All
Paramsstructs are now passed as pointers for consistency and to support optional parameters. - Field names follow Go casing conventions (e.g.,
Id→ID,Url→URL,TokenId→TokenID).
Starting with this version, invoking remote Functions and class methods through
.remote() and similar uses a new serialization protocol that requires the
referenced modal Apps to be deployed using the Modal Python SDK 1.2 or newer. In
addition, your deployed Apps need to be on the 2025.06 image builder version or
newer (see https://modal.com/settings/image-config for more information) or have
the cbor2 Python package installed in their image.
See below for a list of all changes in JavaScript/TypeScript and Go. See also the updated examples in JS and Go for a sense of how the API has changed.
Brief example of using the new API:
import { ModalClient } from "modal";
const modal = new ModalClient();
const app = await modal.apps.fromName("libmodal-example", {
createIfMissing: true,
});
const image = modal.images.fromRegistry("alpine:3.21");
const volume = await modal.volumes.fromName("libmodal-example-volume", {
createIfMissing: true,
});
const sb = await modal.sandboxes.create(app, image, {
volumes: { "/mnt/volume": volume },
});
const p = await sb.exec(["cat", "/mnt/volume/message.txt"]);
console.log(`Message: ${await p.stdout.readText()}`);
await sb.terminate();
const echo = await modal.functions.fromName("libmodal-example", "echo");
console.log(await echo.remote(["Hello world!"]));import { ModalClient } from "modal";
const client = new ModalClient();
// or customized:
const client = new ModalClient({ tokenId: "...", tokenSecret: "..." });initializeClient(...)->new ModalClient(...)
App.lookup(...)->modal.apps.fromName(...)
Cls.lookup(...)->modal.cls.fromName(...)
Function_.lookup(...)->modal.functions.fromName(...)
FunctionCall.fromId(...)->modal.functionCalls.fromId(...)
app.imageFromRegistry(...)->modal.images.fromRegistry(...)app.imageFromAwsEcr(...)->modal.images.fromAwsEcr(...)app.imageFromGcpArtifactRegistry(...)->modal.images.fromGcpArtifactRegistry(...)Image.fromRegistry(...)->modal.images.fromRegistry(...)Image.fromAwsEcr(...)->modal.images.fromAwsEcr(...)Image.fromGcpArtifactRegistry(...)->modal.images.fromGcpArtifactRegistry(...)Image.fromId(...)->modal.images.fromId(...)Image.delete(...)->modal.images.delete(...)
Proxy.fromName(...)->modal.proxies.fromName(...)
Queue.lookup(...)->modal.queues.fromName(...)Queue.fromName(...)->modal.queues.fromName(...)Queue.ephemeral(...)->modal.queues.ephemeral(...)Queue.delete(...)->modal.queues.delete(...)
app.createSandbox(image, { ... })->modal.sandboxes.create(app, image, { ... })Sandbox.fromId(...)->modal.sandboxes.fromId(...)Sandbox.fromName(...)->modal.sandboxes.fromName(...)Sandbox.list(...)->modal.sandboxes.list(...)
Secret.fromName(...)->modal.secrets.fromName(...)Secret.fromObject(...)->modal.secrets.fromObject(...)
Volume.fromName(...)->modal.volumes.fromName(...)Volume.ephemeral(...)->modal.volumes.ephemeral(...)
ClsOptions->ClsWithOptionsParamsClsConcurrencyOptions->ClsWithConcurrencyParamsClsBatchingOptions->ClsWithBatchingParamsDeleteOptions-> specific*DeleteParamstypes:QueueDeleteParamsEphemeralOptions-> specific*EphemeralParamstypes:QueueEphemeralParams,VolumeEphemeralParamsExecOptions->SandboxExecParamsUpdateAutoscalerOptions->FunctionUpdateAutoscalerParamsFunctionCallGetOptions->FunctionCallGetParamsFunctionCallCancelOptions->FunctionCallCancelParamsImageDockerfileCommandsOptions->ImageDockerfileCommandsParamsImageDeleteOptions->ImageDeleteParamsLookupOptions-> specific*FromNameParamstypes:AppFromNameParams,ClsFromNameParams,FunctionFromNameParams,QueueFromNameParamsProxyFromNameOptions->ProxyFromNameParamsQueueClearOptions->QueueClearParamsQueueGetOptions->QueueGetParamsandQueueGetManyParamsQueuePutOptions->QueuePutParamsandQueuePutManyParamsQueueLenOptions->QueueLenParamsQueueIterateOptions->QueueIterateParamsSandboxCreateOptions->SandboxCreateParamsSandboxFromNameOptions->SandboxFromNameParamsSandboxListOptions->SandboxListParamsSecretFromNameOptions->SecretFromNameParamsSecretFromObjectParams-> new export (no previous equivalent)VolumeFromNameOptions->VolumeFromNameParams
Parameters now include explicit unit suffixes to make the API more self-documenting and prevent confusion about units:
-
timeout→timeoutMs -
idleTimeout→idleTimeoutMs -
scaledownWindow→scaledownWindowMs -
itemPollTimeout→itemPollTimeoutMs -
partitionTtl→partitionTtlMs -
memory→memoryMiB -
memoryLimit→memoryLimitMiB
Brief example of using the new API (with err handling omitted for brevity):
package main
import (
"context"
"fmt"
"io"
"github.com/modal-labs/modal-client/go"
)
func main() {
ctx := context.Background()
mc, _ := modal.NewClient()
app, _ := mc.Apps.FromName(ctx, "libmodal-example", &modal.AppFromNameParams{CreateIfMissing: true})
image := mc.Images.FromRegistry("alpine:3.21", nil)
volume, _ := mc.Volumes.FromName(ctx, "libmodal-example-volume", &modal.VolumeFromNameParams{CreateIfMissing: true})
sb, _ := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Volumes: map[string]*modal.Volume{"/mnt/volume": volume},
})
defer sb.Terminate(context.Background())
p, _ := sb.Exec(ctx, []string{"cat", "/mnt/volume/message.txt"}, nil)
stdout, _ := io.ReadAll(p.Stdout)
fmt.Printf("Message: %s\n", stdout)
echo, _ := mc.Functions.FromName(ctx, "libmodal-example", "echo", nil)
result, _ := echo.Remote(ctx, []any{"Hello world!"}, nil)
fmt.Println(result)
}- Many methods now require
ctx context.Contextas the first parameter - Field renames in structs:
TokenId->TokenIDAppId->AppIDSandboxId->SandboxIDImageId->ImageIDSecretId->SecretIDVolumeId->VolumeIDQueueId->QueueIDFunctionId->FunctionIDClsId->ClsIDFunctionCallId->FunctionCallIDProxyId->ProxyIDServerUrl->ServerURLBucketEndpointUrl->BucketEndpointURL
import "github.com/modal-labs/modal-client/go"
client, err := modal.NewClient()
// or customized:
client, err := modal.NewClientWithOptions(&modal.ClientParams{
TokenID: "...",
TokenSecret: "...",
})modal.InitializeClient(modal.ClientOptions{...})->modal.NewClient()ormodal.NewClientWithOptions(&modal.ClientParams{...})
modal.AppLookup(ctx, "my-app", &modal.LookupOptions{...})->mc.Apps.FromName(ctx, "my-app", &modal.AppFromNameParams{...})
modal.NewCloudBucketMount(..., &modal.CloudBucketMountOptions{...})->mc.CloudBucketMounts.New(..., &modal.CloudBucketMountParams{...})
modal.ClsLookup(ctx, ..., &modal.LookupOptions{...})->mc.Cls.FromName(ctx, ..., &modal.ClsFromNameParams{...})
cls.Instance(...)->cls.Instance(ctx, ...)cls.WithOptions(modal.ClsOptions{...})->cls.WithOptions(&modal.ClsWithOptionsParams{...})cls.WithConcurrency(modal.ClsConcurrencyOptions{...})->cls.WithConcurrency(&modal.ClsWithConcurrencyParams{...})cls.WithBatching(modal.ClsBatchingOptions{...})->cls.WithBatching(&modal.ClsWithBatchingParams{...})
modal.FunctionLookup(ctx, ..., &modal.LookupOptions{...})->mc.Functions.FromName(ctx, ..., &modal.FunctionFromNameParams{...})
function.Remote(...)->function.Remote(ctx, ...)function.Spawn(...)->function.Spawn(ctx, ...)function.GetCurrentStats()->function.GetCurrentStats(ctx)function.UpdateAutoscaler(modal.UpdateAutoscalerOptions{...})->function.UpdateAutoscaler(ctx, &modal.FunctionUpdateAutoscalerParams{...})
modal.FunctionCallFromId(ctx, "call-id")->mc.FunctionCalls.FromID(ctx, "call-id")
functionCall.Get(&modal.FunctionCallGetOptions{...})->functionCall.Get(ctx, &modal.FunctionCallGetParams{...})functionCall.Cancel(&modal.FunctionCallCancelOptions{...})->functionCall.Cancel(ctx, &modal.FunctionCallCancelParams{...})
app.ImageFromRegistry(..., &modal.ImageFromRegistryOptions{...})->mc.Images.FromRegistry(ctx, ..., &modal.ImageFromRegistryParams{...})modal.NewImageFromRegistry(..., &modal.ImageFromRegistryOptions{...})->mc.Images.FromRegistry(ctx, ..., &modal.ImageFromRegistryParams{...})modal.NewImageFromAwsEcr(..., secret)->mc.Images.FromAwsEcr(ctx, ..., secret)modal.NewImageFromGcpArtifactRegistry(..., secret)->mc.Images.FromGcpArtifactRegistry(ctx, ..., secret)modal.NewImageFromId(ctx, ...)->mc.Images.FromID(ctx, ...)modal.ImageDelete(ctx, ..., &modal.ImageDeleteOptions{...})->mc.Images.Delete(ctx, ..., &modal.ImageDeleteParams{...})
image.DockerfileCommands(..., &modal.ImageDockerfileCommandsOptions{...})->image.DockerfileCommands(..., &modal.ImageDockerfileCommandsParams{...})image.Build(app)->image.Build(ctx, app)
modal.ProxyFromName(..., &modal.ProxyFromNameOptions{...})->mc.Proxies.FromName(..., &modal.ProxyFromNameParams{...})
modal.QueueLookup(ctx, ..., &modal.LookupOptions{...})->mc.Queues.FromName(ctx, ..., &modal.QueueFromNameParams{...})modal.QueueEphemeral(ctx, &modal.EphemeralOptions{...})->mc.Queues.Ephemeral(ctx, &modal.QueueEphemeralParams{...})modal.QueueDelete(ctx, ..., &modal.DeleteOptions{...})->mc.Queues.Delete(ctx, ..., &modal.QueueDeleteParams{...})
queue.Clear(&modal.QueueClearOptions{...})->queue.Clear(ctx, &modal.QueueClearParams{...})queue.Get(&modal.QueueGetOptions{...})->queue.Get(ctx, &modal.QueueGetParams{...})queue.GetMany(..., &modal.QueueGetOptions{...})->queue.GetMany(ctx, ..., &modal.QueueGetManyParams{...})queue.Put(..., &modal.QueuePutOptions{...})->queue.Put(ctx, ..., &modal.QueuePutParams{...})queue.PutMany(..., &modal.QueuePutOptions{...})->queue.PutMany(ctx, ..., &modal.QueuePutManyParams{...})queue.Len(&modal.QueueLenOptions{...})->queue.Len(ctx, &modal.QueueLenParams{...})queue.Iterate(&modal.QueueIterateOptions{...})->queue.Iterate(ctx, &modal.QueueIterateParams{...})
modal.NewRetries(..., &modal.RetriesOptions{...})->modal.NewRetries(..., &modal.RetriesParams{...})
app.CreateSandbox(image, &modal.SandboxOptions{...})->mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{...})modal.SandboxFromId(ctx, "sandbox-id")->mc.Sandboxes.FromID(ctx, "sandbox-id")modal.SandboxFromName(ctx, "app-name", "sandbox-name", &modal.SandboxFromNameOptions{...})->mc.Sandboxes.FromName(ctx, "app-name", "sandbox-name", &modal.SandboxFromNameParams{...})modal.SandboxList(ctx, &modal.SandboxListOptions{...})->mc.Sandboxes.List(ctx, &modal.SandboxListParams{...})
sandbox.Exec(..., modal.ExecOptions{...})->sandbox.Exec(ctx, ..., &modal.SandboxExecParams{...})sandbox.Open(...)->sandbox.Open(ctx, ...)sandbox.Terminate()->sandbox.Terminate(ctx)sandbox.Wait()->sandbox.Wait(ctx)sandbox.Tunnels(...)->sandbox.Tunnels(ctx, ...)sandbox.SnapshotFilesystem(...)->sandbox.SnapshotFilesystem(ctx, ...)sandbox.Poll()->sandbox.Poll(ctx)sandbox.SetTags(...)->sandbox.SetTags(ctx, ...)sandbox.GetTags()->sandbox.GetTags(ctx)
modal.SecretFromName(ctx, ..., &modal.SecretFromNameOptions{...})->mc.Secrets.FromName(ctx, ..., &modal.SecretFromNameParams{...})modal.SecretFromMap(ctx, ..., &modal.SecretFromMapOptions{...})->mc.Secrets.FromMap(ctx, ..., &modal.SecretFromMapParams{...})
modal.VolumeFromName(ctx, ..., &modal.VolumeFromNameOptions{...})->mc.Volumes.FromName(ctx, ..., &modal.VolumeFromNameParams{...})modal.VolumeEphemeral(ctx, &modal.EphemeralOptions{...})->mc.Volumes.Ephemeral(ctx, &modal.VolumeEphemeralParams{...})
ClientOptions->ClientParamsCloudBucketMountOptions->CloudBucketMountParamsClsBatchingOptions->ClsWithBatchingParamsClsConcurrencyOptions->ClsWithConcurrencyParamsClsOptions->ClsWithOptionsParamsDeleteOptions-> specific*DeleteParamstypes:QueueDeleteParamsEphemeralOptions-> specific*EphemeralParamstypes:QueueEphemeralParams,VolumeEphemeralParamsExecOptions->SandboxExecParamsFunctionCallCancelOptions->FunctionCallCancelParamsFunctionCallGetOptions->FunctionCallGetParamsImageDeleteOptions->ImageDeleteParamsImageDockerfileCommandsOptions->ImageDockerfileCommandsParamsImageFromRegistryOptions->ImageFromRegistryParamsLookupOptions-> specific*FromNameParamstypes:AppFromNameParams,ClsFromNameParams,FunctionFromNameParams,QueueFromNameParamsProxyFromNameOptions->ProxyFromNameParamsQueueClearOptions->QueueClearParamsQueueGetOptions->QueueGetParamsandQueueGetManyParamsQueueIterateOptions->QueueIterateParamsQueueLenOptions->QueueLenParamsQueuePutOptions->QueuePutParamsandQueuePutManyParamsRetriesOptions->RetriesParamsSandboxFromNameOptions->SandboxFromNameParamsSandboxListOptions->SandboxListParamsSandboxOptions->SandboxCreateParamsSecretFromMapOptions->SecretFromMapParamsSecretFromNameOptions->SecretFromNameParamsUpdateAutoscalerOptions->FunctionUpdateAutoscalerParamsVolumeFromNameOptions->VolumeFromNameParams
Parameters now include explicit unit suffixes to make the API more self-documenting and prevent confusion about units:
memory→memoryMiBmemoryLimit→memoryLimitMiB