# Build and deploy modules with the CLI

Scaffold, build, upload, and version modules from the command line.
> Source: https://docs.viam.com/cli/build-and-deploy-modules/


Scaffold a new module, iterate on it locally with hot-reload, upload it to the registry, and manage versions and cloud builds.

**Prerequisites**


You need the Viam CLI installed and authenticated.
See [Viam CLI overview](/cli/overview/) for installation and authentication instructions.



## Find your IDs

To find the part ID for a running machine (needed for reload and restart):

```sh {class="command-line" data-prompt="$"}
viam machines list --organization=<org-id> --location=<location-id>
```

```sh {class="command-line" data-prompt="$"}
viam machines part list --machine=<machine-id>
```

To find your organization and location IDs:

```sh {class="command-line" data-prompt="$"}
viam organizations list
```

```sh {class="command-line" data-prompt="$"}
viam locations list
```

## Scaffold a new module

Generate a module project with boilerplate code, a `meta.json` manifest, and a build script.
This command does not require authentication, so you can scaffold a module before logging in.

```sh {class="command-line" data-prompt="$"}
viam module generate
```

The generator first asks whether you want to generate a **module** or an **app**.
Choose **Module**, then follow the interactive prompts to choose:

- Module name
- Programming language (Python, Go, or C++)
- Namespace and visibility
- Resource type (component or service) and API

To generate a hosted web application instead of a module, choose **App** or see [Deploy a Viam application](/build-apps/hosting/deploy/).

You can also pass flags to skip the interactive prompts:

```sh {class="command-line" data-prompt="$"}
viam module generate \
  --generate-type=module \
  --name=my-sensor-module \
  --language=python \
  --visibility=public
```

To add a new resource model to an existing module, use [`viam module add-model`](/cli/reference/#module-add-model) from within the module directory.
To add a web application to an existing Go module, use [`viam module add-app`](/cli/reference/#module-add-app).

## Iterate during development

After making code changes, reload your module on a running machine without restarting the entire machine. By default, the CLI builds the module in the cloud and syncs the new binary to the target:

```sh {class="command-line" data-prompt="$"}
viam module reload --part-id=<part-id>
```

If your development machine is also running the module (for example, developing a macOS module on the Mac that runs `viam-server`), use `reload-local` to build and reload without a cloud build:

```sh {class="command-line" data-prompt="$"}
viam module reload-local --part-id=<part-id>
```

If a reload is not sufficient, restart the module process:

```sh {class="command-line" data-prompt="$"}
viam module restart --part-id=<part-id>
```

## Update model definitions

After adding or changing models in your module, update the model definitions in `meta.json`.
This command runs the module's executable (binary or script) in a sandbox, queries it for the API-model pairs it advertises, and updates the manifest.
It also auto-detects markdown documentation files named `namespace_module_model.md`.

If you omit `--binary`, the CLI uses the entrypoint declared in `meta.json`:

```sh {class="command-line" data-prompt="$"}
viam module update-models
```

To point at a specific executable, pass `--binary`:

```sh {class="command-line" data-prompt="$"}
viam module update-models --binary=./bin/module
```

Then push the updated `meta.json` to the registry:

```sh {class="command-line" data-prompt="$"}
viam module update
```

## Upload to the registry

Upload a module version for a specific platform.
The CLI validates the tarball before uploading: it checks for an executable at the declared entrypoint, verifies file permissions, and warns about platform mismatches or symlinks escaping the archive.
Pass `--force` to skip validation.

```sh {class="command-line" data-prompt="$"}
viam module upload \
  --version=1.0.0 \
  --platform=linux/amd64 \
  dist/archive.tar.gz
```

On success, the CLI prints a link to your module in the registry:

```sh {class="command-line" data-prompt="$" data-output="1"}
Version successfully uploaded! you can view your changes online here: https://app.viam.com/module/my-org/my-module
```

Upload for multiple platforms by running the command once per platform:

```sh {class="command-line" data-prompt="$"}
viam module upload --version=1.0.0 --platform=linux/amd64 dist/archive-amd64.tar.gz
```

```sh {class="command-line" data-prompt="$"}
viam module upload --version=1.0.0 --platform=linux/arm64 dist/archive-arm64.tar.gz
```

## Cloud builds

For CI/CD workflows, use cloud builds to compile your module on Viam's build infrastructure.

> **C++ build environment:**
> 
> On Linux, cloud builds compile C++ modules in a Debian-based environment, the same one that builds the Viam [RDK](/reference/glossary/rdk/)
> .
> To match this environment locally and add any libraries your module needs, install build dependencies with `apt-get` in your module’s setup step.
> The [C++ example module](https://github.com/viamrobotics/module-example-cpp) follows this pattern in its `apt-setup.sh` script.

Start a cloud build:

```sh {class="command-line" data-prompt="$"}
viam module build start --version=1.0.0
```

Build for multiple platforms in one command:

```sh {class="command-line" data-prompt="$"}
viam module build start --version=1.0.0 --platforms=linux/amd64,linux/arm64
```

Build from a specific git ref:

```sh {class="command-line" data-prompt="$"}
viam module build start --version=1.0.0 --ref=main
```

Build from your local source directory without pushing to GitHub first:

```sh {class="command-line" data-prompt="$"}
viam module build start --version=1.0.0 --from-source --platforms=linux/amd64,linux/arm64 --wait
```

Build locally to test before pushing:

```sh {class="command-line" data-prompt="$"}
viam module build local
```

List recent builds (the output includes build IDs you need for `build logs`):

```sh {class="command-line" data-prompt="$"}
viam module build list
```

View build logs:

```sh {class="command-line" data-prompt="$"}
viam module build logs --id=<build-id>
```

Wait for a build to complete and stream logs:

```sh {class="command-line" data-prompt="$"}
viam module build logs --id=<build-id> --wait
```

## Download a module

Download a module from the registry for local testing or inspection.
The `--id` flag takes the format `org-namespace:module-name`:

```sh {class="command-line" data-prompt="$"}
viam module download \
  --id=my-org:my-sensor-module \
  --version=1.0.0 \
  --platform=linux/amd64 \
  --destination=./downloaded-module
```

## Create a module

If you need to register a module in the registry before uploading (for example, to reserve a name), use `create`:

```sh {class="command-line" data-prompt="$"}
viam module create --name=my-new-module
```

Most users should use `viam module generate` instead, which handles both creation and scaffolding.

## Convert xacro files to URDF

If your module works with a robot described in [xacro](https://wiki.ros.org/xacro) format (the ROS XML macro language), convert it to URDF with the CLI.
The conversion runs in a Docker container with the specified ROS distribution.

```sh {class="command-line" data-prompt="$"}
viam xacro convert \
  --input-file=./robot.xacro \
  --output-file=./robot.urdf
```

If the xacro file uses `<xacro:arg>` tags, pass the required arguments:

```sh {class="command-line" data-prompt="$"}
viam xacro convert \
  --input-file=./robot.xacro \
  --output-file=./robot.urdf \
  --args name:=ur20
```

To collapse fixed joint chains (useful when the URDF must have a single end-effector):

```sh {class="command-line" data-prompt="$"}
viam xacro convert \
  --input-file=./robot.xacro \
  --output-file=./robot.urdf \
  --collapse-fixed-joints
```

By default, the conversion uses the `osrf/ros:humble-desktop` Docker image.
To use a different ROS distribution or a custom image:

```sh {class="command-line" data-prompt="$"}
viam xacro convert \
  --input-file=./robot.xacro \
  --output-file=./robot.urdf \
  --docker-image=osrf/ros:jazzy-desktop
```

Use `--dry-run` to print the Docker command without running it.

## Related pages

- [Write a driver module](/build-modules/write-a-driver-module/) for a complete guide to writing a hardware driver
- [Write a logic module](/build-modules/write-a-logic-module/) for writing automation and monitoring logic
- [Deploy a module](/build-modules/deploy-a-module/) for deployment with GitHub Actions
- [CLI reference](/cli/) for the complete `module` command reference

