Module developer reference

This page is a reference for module developers. For step-by-step instructions, see Write a Module and Deploy a Module.

Module lifecycle

Every module, local or registry, runs as a separate child process alongside viam-server, communicating over gRPC.

  1. viam-server starts and checks for configuration updates (if online).
  2. If the module has a first_run script that hasn’t succeeded yet, viam-server runs it before starting the module.
  3. viam-server starts each configured module as a child process, passing it a socket address.
  4. The module registers its models and APIs with viam-server through the Ready RPC.
  5. For each configured resource, viam-server calls ValidateConfig to check attributes and discover dependencies.
  6. viam-server starts required dependencies first. If a required dependency fails, the resource that depends on it does not start.
  7. viam-server calls AddResource to create each resource. The module’s constructor runs, typically calling Reconfigure to read config.
  8. The resource is available for use.
  9. When the user changes configuration, viam-server calls ReconfigureResource. Your Reconfigure method should complete within the per-resource configuration timeout (default: 2 minutes, configurable with VIAM_RESOURCE_CONFIGURATION_TIMEOUT).
  10. On shutdown, viam-server sends RemoveResource for each resource, then terminates the module process.

First-run scripts

If your module needs one-time setup (installing system packages, downloading models, etc.), set the first_run field in meta.json to the path of a setup script inside your archive.

  • The script runs before the module entrypoint, with the same environment variables available to the module.
  • If the script exits with a non-zero status, reconfiguration is aborted. Currently running modules continue with their previous configuration.
  • On success, a .first_run_succeeded marker file is created next to the module binary. The script will not re-run unless this marker is deleted or a new module version is installed.
  • The default timeout is 1 hour, configurable through first_run_timeout in the module config.

Crash recovery

If a module process crashes, viam-server automatically restarts it:

  1. viam-server detects the exit and marks the module as failed.
  2. The machine’s status changes to initializing.
  3. viam-server retries every 5 seconds.
  4. On success, viam-server re-adds all resources in dependency order.
  5. The machine returns to running.

If the module keeps crashing, viam-server retries indefinitely. Check the LOGS tab for crash tracebacks.

Communication

By default, modules communicate with viam-server over a Unix domain socket with a randomized name.

TCP mode is used automatically on Windows or when the Unix socket path would exceed the OS limit (103 characters on macOS). Force TCP mode by setting "tcp_mode": true in the module config or setting VIAM_TCP_SOCKETS=true.

Data directory

Every module receives a persistent data directory at ~/.viam/module-data/<robot-id>/<module-name>/. The path is available inside the module through the VIAM_MODULE_DATA environment variable. This directory persists across module restarts and reconfigurations.

Timeouts

EventTimeoutBehavior on timeout
Module startup (ready check)5 minutes (configurable through VIAM_MODULE_STARTUP_TIMEOUT)Module marked as failed; retry begins
Config validation5 secondsValidation fails; resource does not start
Resource removal during shutdown20 seconds (all resources combined)Resources orphaned
Module closure (removal + process stop)~30 seconds totalProcess killed
First-run setup script1 hour (configurable through first_run_timeout)Module startup fails
Crash restart retry interval5 secondsNext attempt after delay

Resource interfaces (Go)

Config validation

type ConfigValidator interface {
    Validate(path string) (requiredDependencies, optionalDependencies []string, err error)
}

Return required dependency names in the first slice. viam-server ensures they are ready before calling your constructor. Return optional dependency names in the second slice. These are passed to the constructor if available, but their absence does not block startup.

Resource interface

Every resource must implement:

type Resource interface {
    Name() Name
    Reconfigure(ctx context.Context, deps Dependencies, conf Config) error
    DoCommand(ctx context.Context, cmd map[string]interface{}) (map[string]interface{}, error)
    Close(ctx context.Context) error
}

Plus the methods defined by the specific API (for example, Readings for sensor, GetImage for camera).

Constructor signature

func(ctx context.Context, deps resource.Dependencies,
    conf resource.Config, logger logging.Logger) (ResourceT, error)

Helper traits

Embed these in your resource struct to get default implementations:

TraitEffect
resource.NamedInterface for Name() and DoCommand(). Embed and set through conf.ResourceName().AsNamed().
resource.TriviallyCloseableClose() returns nil.
resource.TriviallyReconfigurableReconfigure() returns nil (no-op).
resource.AlwaysRebuildReconfigure() returns MustRebuildError (always re-create).
resource.TriviallyValidateConfigValidate() returns no deps and no error.

Useful functions

FunctionDescription
resource.NativeConfig[*T](conf)Convert config attributes to a typed struct.
<api>.FromProvider(deps, name)Type-safe dependency lookup (for example, sensor.FromProvider(deps, "my-sensor")).
conf.ResourceName().AsNamed()Create a Named implementation from config.
module.ModularMain(models...)Convenience entry point for simple modules.
module.NewModuleFromArgs(ctx)Create a module from CLI args (for custom entry points).
module.NewLoggerFromArgs(name)Create a logger that routes to viam-server.

Resource interfaces (Python)

Config validation

@classmethod
def validate_config(cls, config: ComponentConfig) -> Tuple[Sequence[str], Sequence[str]]:
    # Return (required_deps, optional_deps)
    return [], []

Constructor

@classmethod
def new(cls, config: ComponentConfig,
        dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
    instance = cls(config.name)
    instance.reconfigure(config, dependencies)
    return instance

Reconfigure

def reconfigure(self, config: ComponentConfig,
                dependencies: Mapping[ResourceName, ResourceBase]) -> None:
    # Update internal state from new config
    pass

Close

async def close(self):
    # Clean up connections, stop background tasks, release hardware.
    # Must be idempotent (safe to call multiple times).
    pass

The default close() on ResourceBase is a no-op.

EasyResource base class

For simple modules, inherit from both the API base class and EasyResource to get default new(), validate_config(), and automatic model registration:

from viam.components.sensor import Sensor
from viam.resource.easy_resource import EasyResource
from viam.module.module import Module


class MySensor(Sensor, EasyResource):
    MODEL = "my-org:my-module:my-sensor"

    async def get_readings(self, **kwargs):
        return {"temperature": 23.5}


if __name__ == '__main__':
    import asyncio
    asyncio.run(Module.run_from_registry())

Useful functions

FunctionDescription
Module.from_args()Create a module from CLI args.
Module.run_from_registry()Discover and register all imported resource classes, then start.
Module.run_with_models(*models)Register explicit model classes, then start.
getLogger(name)Create a logger (from viam.logging import getLogger).
config.attributes.fieldsAccess raw config attributes (no typed config equivalent to Go).

Python and Go defaults

In Python, the default behavior when you don’t implement a method differs from Go:

BehaviorGoPython
Seamless reconfigureImplement Reconfigure()Implement reconfigure() (called if your class satisfies the Reconfigurable protocol)
Rebuild on config changeEmbed resource.AlwaysRebuildOmit reconfigure() (default: module destroys and re-creates the resource)
No-op reconfigureEmbed resource.TriviallyReconfigurableNo equivalent: implement an empty reconfigure() instead
No-op closeEmbed resource.TriviallyCloseableDefault on ResourceBase
Skip config validationEmbed resource.TriviallyValidateConfigDefault on EasyResource

Logging

From modules you can log at the resource level or at the machine level. Resource-level logging is recommended because it makes it easier to identify which component or service produced a message. Resource-level error logs also appear in the Error logs section of each resource’s configuration card.

# Resource-level logging (recommended):
self.logger.debug("debug info")
self.logger.info("info")
self.logger.warn("warning info")
self.logger.error("error info")
self.logger.exception("error info", exc_info=True)
self.logger.critical("critical info")

For machine-level logging instead of resource-level:

from viam.logging import getLogger

LOGGER = getLogger(__name__)

LOGGER.debug("debug info")
LOGGER.info("info")
LOGGER.warn("warning info")
LOGGER.error("error info")
func (c *component) someFunction(ctx context.Context, a int) {
  // Log with severity info:
  c.logger.CInfof(ctx, "performing some function with a=%v", a)
  // Log with severity debug (using value wrapping):
  c.logger.CDebugw(ctx, "performing some function", "a" ,a)
  // Log with severity warn:
  c.logger.CWarnw(ctx, "encountered warning for component", "name", c.Name())
  // Log with severity error without a parameter:
  c.logger.CError(ctx, "encountered an error")
}

To see debug-level logs, run viam-server with the -debug flag or configure debug logging for your machine or individual resource.

Common gotchas

Always call Reconfigure from your constructor. Your constructor and Reconfigure should share the same config-reading logic. The typical pattern is for the constructor to create the struct, then call Reconfigure to populate it from config. This avoids duplicating config parsing and ensures a newly created resource is fully configured.

Clean up in Close(). If your resource starts background goroutines, opens connections, or holds hardware handles, Close() must stop them. Leaked goroutines accumulate across reconfigurations and can cause instability. In Python, close() must be idempotent (it may be called more than once).

Return the right dependency names from Validate. Dependencies listed as required in Validate (Go) or validate_config (Python) must match actual resource names in the machine config. If the name is wrong, viam-server waits for a resource that will never exist, and your resource will not start. Use optional dependencies for resources that improve functionality but aren’t strictly needed.

Prefer Reconfigure over AlwaysRebuild. AlwaysRebuild (Go) or omitting reconfigure() (Python) causes the resource to be destroyed and re-created on every config change. This is simpler but causes a brief availability gap. Implementing Reconfigure to update state in-place provides seamless reconfiguration.

Module protocol

Modules communicate with viam-server over gRPC using the ModuleService defined in proto/viam/module/v1/module.proto:

RPCDirectionPurpose
Readyserver → moduleHandshake: module returns its supported API/model pairs.
AddResourceserver → moduleCreate a new resource instance from config.
ReconfigureResourceserver → moduleUpdate an existing resource with new config.
RemoveResourceserver → moduleDestroy a resource instance.
ValidateConfigserver → moduleValidate config and return implicit dependencies.

The module also connects back to the parent viam-server to access other resources (dependencies) on the machine.

meta.json schema

Every module has a meta.json file that describes the module to the registry. The full schema is available at https://dl.viam.dev/module.schema.json.

{
  "$schema": "https://dl.viam.dev/module.schema.json",
  "module_id": "my-org:my-module",
  "visibility": "private",
  "url": "https://github.com/my-org/my-module",
  "description": "Short description of the module.",
  "models": [
    {
      "api": "rdk:component:sensor",
      "model": "my-org:my-module:my-sensor",
      "short_description": "A short description of this model.",
      "markdown_link": "README.md#my-sensor"
    }
  ],
  "entrypoint": "run.sh",
  "first_run": "setup.sh",
  "markdown_link": "README.md",
  "build": {
    "setup": "./setup.sh",
    "build": "./build.sh",
    "path": "module.tar.gz",
    "arch": ["linux/amd64", "linux/arm64"]
  }
}
FieldTypeRequiredDescription
$schemastringNoJSON Schema URL for editor validation.
module_idstringYesnamespace:name or org-id:name.
visibilitystringYesprivate, public, or public_unlisted.
urlstringNoSource repo URL. Required for cloud builds.
descriptionstringYesShort description shown in the registry.
modelsarrayNoList of API/model pairs the module provides. Deprecated: models are now inferred from the module binary.
models[].apistringYesResource API (for example, rdk:component:sensor).
models[].modelstringYesModel triplet (for example, my-org:my-module:my-sensor).
models[].short_descriptionstringNoShort model description (max 100 chars).
models[].markdown_linkstringNoPath to model docs within the repo.
entrypointstringYesPath to the executable inside the archive.
first_runstringNoPath to a one-time setup script. Runs before the entrypoint on first install and after version updates. See First-run scripts.
markdown_linkstringNoPath to README used as registry description.
buildobjectNoBuild configuration for local and cloud builds.
build.setupstringNoOne-time setup command (for example, install dependencies).
build.buildstringNoBuild command (for example, make module.tar.gz).
build.pathstringNoPath to built artifact. Default: module.tar.gz.
build.archarrayNoTarget platforms. Default: ["linux/amd64", "linux/arm64"].
build.darwin_depsarrayNoHomebrew dependencies for macOS builds (for example, ["go", "pkg-config"]).
applicationsarrayNoViam applications provided by the module. See Applications.

Applications

If your module provides a Viam application, define it in the applications array in meta.json.

Each application object has the following properties:

PropertyTypeDescription
namestringThe application name, used in the application’s URL (name_publicnamespace.viamapps.com). Must be all-lowercase, alphanumeric and hyphens only, cannot start or end with a hyphen, and must be unique within your organization’s namespace.
typestring"single_machine" or "multi_machine". Whether the application can access one machine or multiple machines.
entrypointstringPath to the HTML entry point (for example, "dist/index.html").
fragmentIds[]stringFragment IDs a machine must contain to be selectable from the machine picker. Single-machine applications only.
logoPathstringURL or relative path to the logo for the machine picker screen. Single-machine applications only.
customizationsobjectOverride branding on the authentication screen. Contains a machinePicker object with properties: heading (max 60 chars), subheading (max 256 chars).

For example, if your organization namespace is acme and your application name is dashboard, your application is accessible at:

https://dashboard_acme.viamapps.com

Organization namespace

When uploading modules to the Viam Registry, you must set a unique namespace for your organization.

Create a namespace: In the Viam app, click your organization name in the top navigation bar, then click Settings, then click Set a public namespace. Enter a name and click Set namespace. Namespaces may only contain letters, numbers, and hyphens (-).

Rename a namespace:

  1. Navigate to your organization settings page.
  2. Click Rename next to your current namespace.
  3. Enter the new namespace name and click Rename.
  4. Update the module code and meta.json for each module your organization owns to reflect the new namespace.
  5. (Recommended) Update the model field in machine configurations that reference the old namespace. Old references continue to work, but updating avoids confusion.

When you rename a namespace, Viam reserves the old namespace for backwards compatibility: it cannot be reused.

CLI commands

All module CLI commands are under viam module. You must be logged in (viam login) to use commands that interact with the registry.

Create and generate

CommandDescription
viam module create --name <name>Register a module in the registry and generate meta.json.
viam module generateScaffold a complete module project with templates (interactive prompts).

generate flags: --name, --language (python or go), --visibility, --public-namespace, --resource-subtype, --model-name, --register, --dry-run.

Build

CommandDescription
viam module build localRun the build command from meta.json locally.
viam module build start --version <semver>Start a cloud build for all configured platforms.
viam module build listList cloud build jobs and their status.
viam module build logs --id <build-id>Stream logs from a cloud build job.

build start flags: --ref (git ref, default: main), --platforms, --token (for private repos), --workdir.

During builds, the environment variables VIAM_BUILD_OS and VIAM_BUILD_ARCH are set to the target platform. See Environment variables.

Upload and update

CommandDescription
viam module upload --version <semver> --platform <platform>Upload a built archive to the registry.
viam module updatePush updated meta.json to the registry.
viam module update-models --binary <path>Auto-detect models from a binary and update meta.json.
viam module download --id <module-id>Download a module from the registry.

upload flags: --tags (platform constraints), --force (skip validation), --upload (path to archive).

Development loop

CommandDescription
viam module reload-local --part-id <id>Build locally, transfer to machine, configure, and restart.
viam module reload --part-id <id>Build in cloud, transfer to machine, configure, and restart.
viam module restart --part-id <id>Restart a running module without rebuilding.

reload-local flags: --part-id (target machine part), --no-build (skip build), --local (run entrypoint directly on localhost instead of bundling), --model-name (add a resource to config with this model triple), --name (name the added resource), --resource-name (name the resource instance), --id (module ID, alternative to --name), --cloud-config (path to viam.json, alternative to --part-id), --workdir (subdirectory containing meta.json), --home-dir (remote user’s home directory), --no-progress (hide transfer progress).

Environment variables

Runtime

These environment variables are available inside a running module process (including first-run scripts):

VariableDescription
VIAM_MODULE_NAMEThe module’s name from config.
VIAM_MODULE_DATAPath to the module’s persistent data directory.
VIAM_MODULE_ROOTParent directory of the module executable.
VIAM_MODULE_IDRegistry module ID (registry modules only).
VIAM_HOMEPath to the Viam home directory (~/.viam).

Cloud-connected

When the machine is connected to Viam Cloud, these additional variables are available inside the module process:

VariableDescription
VIAM_API_KEYAPI key (if an API key auth handler is configured).
VIAM_API_KEY_IDAPI key ID (if an API key auth handler is configured).
VIAM_MACHINE_IDCloud machine ID.
VIAM_MACHINE_PART_IDCloud machine part ID.
VIAM_MACHINE_FQDNMachine’s fully qualified domain name.
VIAM_LOCATION_IDCloud location ID.
VIAM_PRIMARY_ORG_IDPrimary organization ID.

Custom environment variables can be added in the module’s machine config under the env field.

Build-time

The following variables are set during cloud builds, not at runtime:

VariableDescription
VIAM_BUILD_OSTarget operating system (for example, linux, darwin).
VIAM_BUILD_ARCHTarget architecture (for example, amd64, arm64).

Server-side

The following variables control viam-server startup behavior (not passed to modules):

VariableDescription
VIAM_MODULE_STARTUP_TIMEOUTOverride the default 5-minute startup timeout (for example, 10m, 30s).
VIAM_RESOURCE_CONFIGURATION_TIMEOUTOverride the default 2-minute per-resource configuration timeout.

Supported platforms

PlatformCloud build support
linux/amd64Yes
linux/arm64Yes
linux/arm32v6No
linux/arm32v7No
darwin/amd64No
darwin/arm64Yes
windows/amd64Yes
anyNo (use for platform-independent modules)

Registry validation rules

RuleConstraint
Module name1-200 characters, ^[a-zA-Z0-9][-\w]*$ (must start with alphanumeric; may contain hyphens, underscores, letters, digits)
Module versionSemantic versioning 2.0.0 (for example, 1.2.3)
Package name^[\w-]+$
Metadata fieldsMax 16 key-value pairs
Metadata key/value sizeMax 500 KB each
Compressed packageMax 50 GB
Decompressed contentsMax 250 GB
Single file in packageMax 25 GB
Model namespaceMust match org namespace if org has one. Cannot use reserved namespace rdk.
Public modulesRequire org to have a public namespace. Cannot use public_unlistedprivate if external orgs are using the module.