Skip to content

You are viewing documentation for Instruqt 2.0 Labs which is in Beta currently. Official release date - 29 September, 2026. For Tracks documentation, please visit docs.instruqt.com.

Create and edit a module


A module is created and edited in almost the same way a lab is. This page covers what is different: the registry list it lives in, the two ways to create one, what the module editor leaves out, and the settings that only a module has.

In the sidebar, click Building blocks → Modules.

The table has one row per module, with these columns:

ColumnShows
NameThe module’s slug – the second half of its team-slug/module-slug address. Links into the module.
VisibilityPrivate or Public.
Latest versionThe highest version published from the module, or nothing if none has been.
UpdatedWhen the module was last changed.

Use Search in modules to narrow the list, and click the Name, Visibility, or Updated headers to sort. The ••• menu on each row offers Open and Delete module.

This list is your own team’s registry, and it shows your private and public modules alike. Other teams’ public modules do not appear here – you discover those from the add-module picker, in a lab or in another module. See Using modules.

  1. In the sidebar, click Building blocks → Modules.
  2. Click Create module.
  3. In Module name, enter the module’s slug. This becomes half of the module’s permanent address, so it cannot be changed later. The field slugifies what you type, and warns you if your team already has a module with that name.
  4. In Description, write one line about what the module provides. This shows wherever the module is listed, and is limited to 500 characters.
  5. Leave Publish to the community registry off to keep the module Private, or turn it on to publish it to the community registry. Off is the default. See Visibility below before turning it on.
  6. Click Create module.

Instruqt provisions a Git repository for the module and opens it in the module editor, already in edit mode. There is no template to choose, unlike creating a lab. Instead, the repository starts with a small working example to replace: a variables.hcl declaring one variable, greeting, and an outputs.hcl exposing it as an output of the same name.

variables.hcl
variable "greeting" {
default = "hello"
description = "What the module says back"
}
# outputs.hcl
output "greeting" {
value = variable.greeting
}

The rest of this page is about replacing it with the module you actually want.

A module has to have at least one published version before any lab can add it, so a new module is not yet usable. See Publish and manage versions.

If the module’s HCL already lives in a repository your team has connected, import it instead of creating one.

  1. In the sidebar, click Building blocks → Modules.
  2. Click Import module.
  3. Pick the repository. If you have more than one GitHub account connected, choose the account first.
  4. In Module name, enter the module’s slug. It is prefilled from the repository name.
  5. In Description, write one line about what the module provides.
  6. Under Module directory, select the directory holding the module’s HCL configuration. Choose the repository root if the HCL is not in a subdirectory.
  7. Set Publish to the community registry as you want it.
  8. Click Import module.

The import reads the repository’s default branch. There is no branch to pick, because the directory tree you select from is listed from that same branch.

An import is refused when:

  • The directory you selected holds no .hcl files at its top level.
  • Your team already has a module with that name.
  • The directory already holds a module.
  • Another team keeps modules in that repository.

An import whose configuration does not validate is not refused. The module is imported, and a warning shows the validation message instead of the usual confirmation, so you can fix the configuration in the editor.

For connecting a repository in the first place, see Integrating external version control.

A module is authored in the same editor shell as a lab, with the same branch switcher, the same Project settings and Change history in the header, and the same edit mode. Editing and publishing applies with one difference in wording: in the module editor, the button that commits your edit session to the branch is Publish changes, and so is its dialog. You choose Publish to the current branch or Publish to new branch…, then click Publish changes. Add changes, Review changes, and conflict handling work exactly as they do for a lab.

Search, next to the branch switcher, opens a quick search over the module’s tabs, pages, activities, dynamic values, files, Project settings, Change history, Versions, and Review changes. You can also open it with Cmd+K (macOS) or Ctrl+K.

The tabs are:

Overview, Dynamic values, Pages, Sandbox, Activities, Tabs, Modules, Layouts, Files.

Compared to a lab editor, a module editor has no:

MissingWhy
Details tabThe fields it edits belong to the lab resource, which a module does not have. Overview takes the landing slot instead.
Instructions with chaptersChapters belong to a lab, so a module’s instructions are a flat Pages tab.
AssistantAI lab generation works on labs.
Play, Share, and logsA module is never run on its own – the labs that depend on it are.

Outside edit mode, the header also carries a tag icon linking to Versions, and a Publish version button. Neither is shown while you are in edit mode.

Overview is a read-only summary of the module. Nothing on it is editable – the module’s registry fields moved to Project settings → General.

On the left:

  • The module’s slug, its full team-slug/module-slug address, and its description. The heading links to Project settings → General, where the description is written.
  • Contents – a count per tab: Pages, Activities, Dynamic values, and Layouts.
  • Sandbox – a tile per sandbox resource the module declares. Click one to open that resource.
  • Tabs – a tile per tab the module declares. Click one to open that tab.

In the right rail:

  • Change history – recent commits on the current branch.
  • Versions – the versions published from this module, newest first, with a View button to the full Versions page.
  • Used by – the labs that depend on this module. A lab is listed once it has been created, imported, synced, or published since it started depending on the module, so a lab that has not changed for a while may be missing.

A module’s variable and output blocks are its interface, and they are the whole of it.

A variable block declares an input. The lab that adds the module sets a value for it, and the value it sets is an HCL expression evaluated in the lab, so it can reference the lab’s own resources. Give every variable a description and, where it makes sense, a default – the description is what a consumer sees on the form.

# variables.hcl, in the module
variable "database_name" {
description = "Name of the database to create"
default = "app"
}

An output block exposes a value to the lab that pulled the module in. Outputs are the only way anything inside a module is reachable from outside it, so whatever a consuming lab needs has to be exposed as one.

# outputs.hcl, in the module
output "host" {
description = "Hostname the database is reachable at"
value = resource.container.postgres.meta.name
}

The lab then reads that as module.<block>.output.host.

Keep the interface small and describe it. Adding an output is a minor version; removing or renaming one is a breaking change, and every lab on a constraint that reaches your new version will feel it.

For all fields, see the variable and output references.

A module editor has the same Modules tab a lab does, and it works the same way – see Using modules.

A module’s own module dependencies are its own. A lab that depends on your module does not see them as its own dependencies, and it cannot reach into them: a module’s resources are reachable exactly one level up, through that module’s outputs. If a nested module exposes something your consumers need, re-expose it as an output of your own.

Nesting is limited to 10 levels.

Open Project settings from the module editor header.

FieldWhat it does
DescriptionOne line about what the module provides. Shown wherever the module is listed. Maximum 500 characters.
ReadmeLong-form Markdown, shown to anyone deciding whether to depend on the module. This is where the variables you expect, the secrets you need, and an example of use belong. Toggle between Visual, a rich-text editor, and Code, the raw Markdown.
VisibilityPrivate: only this team or Public: the community registry.
ProtectedPrevents the module and its published versions from being deleted.

These are written straight to the registry when you click Save, outside the edit session entirely. They are not part of a publish, and there is no version of them.

Making a module public is hard to take back. Once it is in the community registry, teams you cannot see can add it and pin a version of it, and their labs then depend on you. Switching it back to private does not undo that – it withdraws the module from every team outside your own, including the ones already depending on it, and their labs stop resolving it. Treat going public as a commitment, and prefer keeping a module private until you are willing to support it.

Nothing about visibility affects versions that are already published. Making a module private does not delete or revoke anything; it changes who is allowed to resolve it.

Connect or disconnect the repository backing the module. This works exactly as it does for a lab – see Integrating external version control.

Holds Delete module. See below.

From the registry list, click ••• on the row and select Delete module. From inside the module, open Project settings → Danger zone and click Delete module. Both open the same dialog.

Type the module’s slug to confirm, then click Delete module.

Delete module is disabled, in both places, while the module is Protected. Clear Protected in Project settings → General first.

What deletion does, and does not do:

  • The module leaves your registry. No lab can add it again.
  • Every lab that depends on it stops resolving it, whether it pins a version or uses a constraint. Find them under Used by on the module’s Overview before you delete.
  • Its slug is freed, so a new module can be created at the same address later.
  • Version numbers are never reissued. A new module created at the freed address cannot publish a number the old one used.
  • If the module was connected to a Git repository, and no other lab or module lives in that repository, the repository is released so it can be connected to new content.

A module can declare a secret resource, and it references a team secret by name – exactly as a lab does. What is worth knowing is whose secret it resolves against: the team whose lab is running, not the team that wrote the module.

# In the module
resource "secret" "api_key" {
reference = "MY_API_KEY"
}

So the two sides have a job each:

  • If you write the module, document every secret name your module expects. The readme is the place for it. There is no way for a consuming lab to discover them otherwise.
  • If you use the module, create secrets with those names in your own team before running a lab that depends on it.

See Secrets for creating and managing them.