Skip to main content
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 and is a big step towards bringing JavaScript/TypeScript and Go to the same high level of developer experience and stability.
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.

Main changes

The v0.5.0 release introduces several significant architectural improvements:
  • Central Modal Client object: The SDKs now expose a central Modal Client object as the main entry point for interacting with Modal resources.
  • Consistent interface: The interface for working with Modal object instances (Functions, Sandboxes, Images, etc.) is largely the same as before, with some naming changes.
  • New serialization protocol: 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.
  • No global client: Internally removed the global client (and config/profile data in global scope), moving all that to the Client type.
  • Consistent parameter naming: All Options structs/interfaces renamed to Params across both SDKs.

Calling deployed Modal Functions and classes

Starting with this version, invoking remote Functions and class methods through .remote() and similar uses a new serialization protocol.
This requires the referenced modal Apps to be deployed using:
  • Modal Python SDK 1.2 or newer
  • 2025.06 image builder version or newer (see image config) OR have the cbor2 Python package installed in their image

JavaScript/TypeScript migration

Client initialization

Before (v0.4 and earlier):
import { initializeClient } from "modal";
initializeClient(...);
After (v0.5+):
import { ModalClient } from "modal";

const modal = new ModalClient();
// or customized:
const modal = new ModalClient({ tokenId: "...", tokenSecret: "..." });

Complete example

Here’s a complete example showing 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!"]));

API changes by resource

  • 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(...)

Parameter name changes

Type renames (Options → Params)

All parameter types have been renamed from *Options to *Params for consistency:
  • ClsOptionsClsWithOptionsParams
  • ClsConcurrencyOptionsClsWithConcurrencyParams
  • ClsBatchingOptionsClsWithBatchingParams
  • DeleteOptions → specific *DeleteParams types: QueueDeleteParams
  • EphemeralOptions → specific *EphemeralParams types: QueueEphemeralParams, VolumeEphemeralParams
  • ExecOptionsSandboxExecParams
  • LookupOptions → specific *FromNameParams types: AppFromNameParams, ClsFromNameParams, FunctionFromNameParams, QueueFromNameParams
  • And many more…

Unit suffixes

Parameters now include explicit unit suffixes to make the API more self-documenting and prevent confusion about units.
Time parameters (now in milliseconds with Ms suffix):
  • timeouttimeoutMs
  • idleTimeoutidleTimeoutMs
  • scaledownWindowscaledownWindowMs
  • itemPollTimeoutitemPollTimeoutMs
  • partitionTtlpartitionTtlMs
Memory parameters (now in MiB with MiB suffix):
  • memorymemoryMiB
  • memoryLimitmemoryLimitMiB

Go migration

Client initialization

Before (v0.4 and earlier):
import "github.com/modal-labs/libmodal/modal-go"
modal.InitializeClient(modal.ClientOptions{...})
After (v0.5+):
import "github.com/modal-labs/libmodal/modal-go"

mc, err := modal.NewClient()
// or customized:
mc, err := modal.NewClientWithOptions(&modal.ClientParams{
    TokenID:     "...",
    TokenSecret: "...",
})

Complete example

Here’s a complete example showing the new API (with err handling omitted for brevity):
package main

import (
	"context"
	"fmt"
	"io"

	"github.com/modal-labs/libmodal/modal-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)
}

Go-specific changes

Go SDK v0.5 introduces several Go-specific improvements for better idiomaticity.

Context passing

  • Many methods now require ctx context.Context as the first parameter
  • Contexts now only affect the current operation and are not used for lifecycle management of the created resources

All Params are now pointers

  • All Params structs are now passed as pointers for consistency and to support optional parameters
  • Example: &modal.SandboxCreateParams{...} instead of modal.SandboxCreateParams{...}

Field naming conventions

Field names now follow Go casing conventions:
  • TokenIdTokenID
  • AppIdAppID
  • SandboxIdSandboxID
  • ImageIdImageID
  • FunctionIdFunctionID
  • UrlURL
  • BucketEndpointUrlBucketEndpointURL
  • And similar changes for all ID and URL fields

API changes by resource

  • 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 methods:
  • 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 methods:
  • 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 methods:
  • functionCall.Get(&modal.FunctionCallGetOptions{...})functionCall.Get(ctx, &modal.FunctionCallGetParams{...})
  • functionCall.Cancel(&modal.FunctionCallCancelOptions{...})functionCall.Cancel(ctx, &modal.FunctionCallCancelParams{...})
  • 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 methods:
  • image.DockerfileCommands(..., &modal.ImageDockerfileCommandsOptions{...})image.DockerfileCommands(..., &modal.ImageDockerfileCommandsParams{...})
  • image.Build(app)image.Build(ctx, app)
  • 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 methods:
  • queue.Clear(&modal.QueueClearOptions{...})queue.Clear(ctx, &modal.QueueClearParams{...})
  • queue.Get(&modal.QueueGetOptions{...})queue.Get(ctx, &modal.QueueGetParams{...})
  • queue.Put(..., &modal.QueuePutOptions{...})queue.Put(ctx, ..., &modal.QueuePutParams{...})
  • queue.Len(&modal.QueueLenOptions{...})queue.Len(ctx, &modal.QueueLenParams{...})
  • 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 methods:
  • sandbox.Exec(..., modal.ExecOptions{...})sandbox.Exec(ctx, ..., &modal.SandboxExecParams{...})
  • sandbox.Terminate()sandbox.Terminate(ctx)
  • sandbox.Wait()sandbox.Wait(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{...})

Parameter name changes

Unit suffixes

Memory parameters now include explicit unit suffixes:
  • memorymemoryMiB
  • memoryLimitmemoryLimitMiB

Getting help

If you encounter any issues during migration, please:

Build docs developers (and LLMs) love