---
description: |-
  The module registry protocol is implemented by a host intending to be the
  host of one or more OpenTofu modules, specifying which modules are available
  and where to find their distribution packages.
---

# Module Registry Protocol

The module registry protocol is what OpenTofu CLI uses to discover metadata
about modules available for installation and to locate the distribution
package for a selected module.

The primary implementation of this protocol is the public
[OpenTofu Registry](https://registry.opentofu.org/) at `registry.opentofu.org`.
By writing and deploying your own implementation of this protocol, you can
create a separate registry to distribute your own modules, as an alternative to
publishing them on the public OpenTofu Registry.

## Module Addresses

Each OpenTofu module has an associated address. A module address has the
syntax `hostname/namespace/name/system`, where:

- `hostname` is the hostname of the module registry that serves this module.
- `namespace` is the name of a namespace, unique on a particular hostname, that
  can contain one or more modules that are somehow related. On the public
  OpenTofu Registry the "namespace" represents the organization that is
  packaging and distributing the module.
- `name` is the module name, which generally names the abstraction that the
  module is intending to create.
- `system` is the name of a remote system that the module is primarily written
  to target. For multi-cloud abstractions, there can be multiple modules with
  addresses that differ only in "system" to reflect provider-specific
  implementations of the abstraction, like
  `registry.opentofu.org/hashicorp/consul/aws` vs.
  `registry.opentofu.org/hashicorp/consul/azurerm`. The system name commonly
  matches the type portion of the address of an official provider, like `aws`
  or `azurerm` in the above examples, but that is not required and so you can
  use whichever system keywords make sense for the organization of your
  particular registry.

The `hostname/` portion of a module address (including its slash delimiter)
is optional, and if omitted defaults to `registry.opentofu.org/`.

For example:

- `hashicorp/consul/aws` is a shorthand for
  `registry.opentofu.org/hashicorp/consul/aws`, which is a module on the
  public registry for deploying Consul clusters in Amazon Web Services.
- `example.com/awesomecorp/consul/happycloud` is a hypothetical module published
  on a third-party registry.

If you intend to share a module you've developed for use by all OpenTofu
users, please consider publishing it into the public
[OpenTofu Registry](https://registry.opentofu.org/) to make your module more
discoverable. You only need to implement this module registry protocol if you
wish to publish modules whose addresses include a different hostname that is
under your control.

## Module Versions

Each distinct module address has associated with it a set of versions, each
of which has an associated version number. OpenTofu assumes version numbers
follow the [Semantic Versioning 2.0](https://semver.org/) conventions, with
the user-facing behavior of the module serving as the "public API".

Each `module` block may select a distinct version of a module, even if multiple
blocks have the same source address.

## Service Discovery

The module registry protocol begins with OpenTofu CLI using
[OpenTofu's remote service discovery protocol](remote-service-discovery.mdx),
with the hostname in the module address acting as the "User-facing Hostname".

The service identifier for the module registry protocol is `modules.v1`.
Its associated string value is the base URL for the relative URLs defined in
the sections that follow.

For example, the service discovery document for a host that _only_ implements
the module registry protocol might contain the following:

```json
{
  "modules.v1": "/tofu/modules/v1/"
}
```

If the given URL is a relative URL then OpenTofu will interpret it as relative
to the discovery document itself. The specific module registry protocol
endpoints are defined as URLs relative to the given base URL, and so the
specified base URL should generally end with a slash to ensure that those
relative paths will be resolved as expected.

The following sections describe the various operations that a module
registry must implement to be compatible with OpenTofu CLI's module
installer. The indicated URLs are all relative to the URL resulting from
service discovery, as described above. We use a hypothetical URL for a provider
registry, assuming that the caller already performed service discovery on a hypothetical
`registry.example.io` to learn the base URL.

The URLs are shown with the convention that a path portion with a colon `:`
prefix is a placeholder for a dynamically-selected value, while all other
path portions are literal. For example, in `:namespace/:type/versions`,
the first two path portions are placeholders while the third is literally
the string "versions".

## List Available Versions for a Specific Module

This is the primary endpoint for resolving module sources, returning the
available versions for a given fully-qualified module.

| Method | Path                                | Produces           |
| ------ | ----------------------------------- | ------------------ |
| `GET`  | `:namespace/:name/:system/versions` | `application/json` |

### Parameters

- `namespace` `(string: <required>)` - The user or organization the module is
  owned by. This is required and is specified as part of the URL path.

- `name` `(string: <required>)` - The name of the module.
  This is required and is specified as part of the URL path.

- `system` `(string: <required>)` - The name of the target system.
  This is required and is specified as part of the URL path.

### Sample Request

```text
$ curl 'https://registry.opentofu.org/v1/modules/hashicorp/consul/aws/versions'
```

### Sample Response

The `modules` array in the response always includes the requested module as the
first element.

OpenTofu does not use the other elements of this list. However, third-party implementations should always use a single-element list for forward compatibility.

Each returned module has an array of available versions, which OpenTofu
matches against any version constraints given in configuration.

```json
{
   "modules": [
      {
         "versions": [
            {"version": "1.0.0"},
            {"version": "1.1.0"},
            {"version": "2.0.0"}
         ]
      }
   ]
}
```

Return `404 Not Found` to indicate that no module is available with the
requested namespace, name, and target system.

## Download Source Code for a Specific Module Version

This endpoint downloads the specified version of a module for a single target system.

| Method | Path                                         | Produces           |
| ------ | -------------------------------------------- | ------------------ |
| `GET`  | `:namespace/:name/:system/:version/download` | `application/json` |

### Parameters

- `namespace` `(string: <required>)` - The user the module is owned by.
  This is required and is specified as part of the URL path.

- `name` `(string: <required>)` - The name of the module.
  This is required and is specified as part of the URL path.

- `system` `(string: <required>)` - The name of the target system.
  This is required and is specified as part of the URL path.

- `version` `(string: <required>)` - The version of the module.
  This is required and is specified as part of the URL path.

### Sample Request

```text
$ curl -i 'https://registry.opentofu.org/v1/modules/foo/bar/baz/0.0.1/download'
```

### Sample Response

A successful response contains the location from which the module version's source can be downloaded.

It is expected to be found in the JSON encoded body as the value for the key `location`:

```text
HTTP/2 200
Content-Length: 81

{"location": "git::https://github.com/foo/terraform-baz-bar?ref=v0.0.1"}
```

In the absence of a response body, OpenTofu will use the `X-Terraform-Get` header as the module location:

```text
HTTP/2 204 No Content
Content-Length: 0
X-Terraform-Get: git::https://github.com/foo/terraform-baz-bar?ref=v0.0.1
```

:::warning
OpenTofu will prioritize reading the response body content if both, the body and the `X-Terraform-Get` header, are received from the registry server.
:::

The module location value accepts the same values as the `source` argument in a
`module` block in OpenTofu configuration, as described in [Module Sources](../language/modules/sources.mdx),
except that it may not recursively refer to another module registry address.
The value of the module location may instead be a relative URL, indicated by beginning with `/`, `./` or `../`,
in which case it is resolved relative to the full URL of the download endpoint to
produce [an HTTP URL module source](../language/modules/sources.mdx#http-urls).
