OpenTofu Extensions

One module extension lives in //tofu:extensions.bzl, tofu, with the same two tag classes as the terraform extension.

tofu.toolchain

The extension always materializes @tofu_toolchains, a hub declaring a toolchain per platform for one OpenTofu release, the root module's toolchain tag or else the newest shipped in //tofu/private:versions.bzl, and @tofu, the CLI hub. rules_terraform's own MODULE.bazel registers the hub so downstream users get toolchain resolution for free.

# Nothing to do beyond `bazel_dep` to use the newest shipped release.
bazel_dep(name = "rules_terraform", version = "...")

# To use another release:
tofu = use_extension("@rules_terraform//tofu:extensions.bzl", "tofu")
tofu.toolchain(version = "1.9.1")

tofu.toolchain takes the same version attribute as the Terraform extension's tag, with the same root-only rule; see the Terraform page. For a tagged release the extension fetches that release's SHA256SUMS once and records the checksums as facts in MODULE.bazel.lock.

tofu.deps

The same deps tag class as the terraform extension, defaulting to registry.opentofu.org. An OpenTofu-only repository uses it for everything and never loads //terraform:extensions.bzl:

tofu = use_extension("@rules_terraform//tofu:extensions.bzl", "tofu")
tofu.deps(
    name = "app",
    lock = "//path/to/module:.terraform.lock.hcl",   # written by tofu
    roots = ["//path/to/module:main.tf"],
)
use_repo(tofu, "app")

The attributes are those documented for the terraform extension; only the default registry differs. That default matters: registry.opentofu.org serves OpenTofu's own builds of the hashicorp/* providers, whose archive hashes differ from HashiCorp's, so a lock file written by tofu has to be resolved against it. Write the lock with the module's tofu_providers_lock updater and it will be.

Each extension pools its own archives. A repository using both engines against the same dependencies should declare them through one extension rather than both, or the same archive is fetched twice.