Files
coder/docs/about/contributing/modules.md
T
Nick Vigilante c84aa564ba docs: normalize code-fence languages for Shiki compatibility (#27161)
Normalizes non-standard code-fence language tags across `docs/**` so a
strict highlighter (Shiki, used by Fumadocs) won't fail the build on an
unrecognized language, and unifies redundant synonym tags onto one
canonical form per language. The current renderer (Speed-Highlight)
detects the language from the code content, not the fence label, so this
drift wasn't visible until now.

## Changes

- `hcl` -> `tf` (199 fences, including indented ones nested in
numbered/bulleted lists). Shiki ships `hcl` and `terraform` as two
distinct grammars (not aliases); every `hcl`-tagged fence in `docs/**`
is actually Terraform resource/data/provider syntax, so the more
specific `terraform` grammar is correct for all of them. `tf` is Shiki's
own alias for that grammar, and it's also what GitHub's own markdown
renderer resolves to the same HCL/Terraform highlighting.
- `pwsh`/`powershell` -> `ps1`. Both `ps` and `ps1` are registered
PowerShell aliases in Shiki, but on GitHub's renderer only `.ps1` is a
registered file extension (`.ps` isn't), so `ps1` renders identically to
`powershell` there today while bare `ps` would silently lose
highlighting.
- `env` -> `dotenv` (a dedicated Shiki grammar for `KEY=VALUE` files)
- `text`/`output`/`none`/`url` -> `txt`. Same built-in plain-text
fallback either way, just shorter.
- `Dockerfile` -> `dockerfile` (lowercase)
- `bash`/`shell` -> `sh` (732 fences). Shiki and GitHub both alias all
three to a single shell grammar; this was already the style guide's
stated preference, just not enforced across the existing corpus until
now.
- `markdown` -> `md` (4 fences). Alias of the same grammar in both Shiki
and GitHub.
- `jsonc` -> `json` (1 fence). The block has no comments or trailing
commas, so it doesn't need the comments-capable grammar.
- `ts` -> `tsx` (2 fences, `docs/about/contributing/frontend.md`).
Verified the actual content tokenizes identically under both grammars,
and a sibling block in the same file already needs `tsx` for real JSX,
so unifying to one tag is safe for this file. Documented a caveat: `tsx`
mis-tokenizes the legacy angle-bracket type-assertion syntax
(`<Type>value`), which is invalid in real `.tsx` files anyway, so use
`value as Type` instead.
- `yml` -> `yaml` (1 fence)
- Updated `docs/.style/style-guide/formatting.md` to document all
canonical tags

`promql` (2 fences) and `caddyfile` (2 fences) are left as-is. Shiki
doesn't bundle a grammar for either, so they need a custom grammar
registration when the site adopts Shiki, rather than degrading to `txt`.
Tracked as follow-up work under DOCS-118 and
[DOCS-544](https://linear.app/codercom/issue/DOCS-544/vendor-a-local-promql-grammar-for-shiki-syntax-highlighting)
(promql).

Does not touch `offlinedocs/`.

Linear:
[DOCS-476](https://linear.app/codercom/issue/DOCS-476/normalize-docs-code-fence-languages-de-risk-shikifumadocs)

<details>
<summary>How the fence tags were verified</summary>

Each tag was tested against a real `shiki@latest` highlighter instance
(`codeToHtml`/`codeToTokens`) and cross-checked against GitHub's
`@wooorm/starry-night` grammar sources (the renderer that actually
displays these `.md` files today, in repo browsing and PR diffs), since
that's what determines whether brevity is safe before Shiki adoption:

```text
FAIL  env        -- Language `env` is not included in this bundle.
FAIL  Dockerfile -- Language `Dockerfile` is not included in this bundle.
FAIL  promql     -- Language `promql` is not included in this bundle.
FAIL  caddyfile  -- Language `caddyfile` is not included in this bundle.
FAIL  pwsh       -- Language `pwsh` is not included in this bundle.
FAIL  output     -- Language `output` is not included in this bundle.
```

`hcl` doesn't error in Shiki, since it's a real grammar, but that's
exactly the trap: it was silently rendering every fence with the generic
HCL grammar instead of the Terraform-specific one. Every `hcl`-tagged
fence in `docs/**` was manually checked against `origin/main` and is
genuinely Terraform content.

For `ts`/`tsx`, tokenizing the actual doc content confirmed identical
output under both grammars; a synthetic test with the legacy
angle-bracket cast syntax confirmed `tsx` degrades on that specific
construct, which the style guide now calls out.

The first normalization pass only matched fence tags at column 0
(`^```tag$`), missing tags indented inside numbered/bulleted lists. A
follow-up pass caught the remaining occurrences at any indentation
level.

</details>


---

*This PR description and the underlying changes were prepared with Coder
Agents assistance.*
2026-07-15 14:07:09 -04:00

11 KiB

Contributing modules

Learn how to create and contribute Terraform modules to the Coder Registry. Modules provide reusable components that extend Coder workspaces with IDEs, development tools, login tools, and other features.

What are Coder modules

Coder modules are Terraform modules that integrate with Coder workspaces to provide specific functionality. They are published to the Coder Registry at registry.coder.com and can be consumed in any Coder template using standard Terraform module syntax.

Examples of modules include:

Prerequisites

Before contributing modules, ensure you have:

Setup your development environment

  1. Fork and clone the repository:

    git clone https://github.com/your-username/registry.git
    cd registry
    
  2. Install dependencies:

    bun install
    
  3. Understand the structure:

    registry/[namespace]/
    ├── modules/         # Your modules
    ├── .images/         # Namespace avatar and screenshots
    └── README.md        # Namespace description
    

Create your first module

1. Set up your namespace

If you're a new contributor, create your namespace directory:

mkdir -p registry/[your-username]
mkdir -p registry/[your-username]/.images

Add your namespace avatar by downloading your GitHub avatar and saving it as avatar.png:

curl -o registry/[your-username]/.images/avatar.png https://github.com/[your-username].png

Create your namespace README at registry/[your-username]/README.md:

---
display_name: "Your Name"
bio: "Brief description of what you do"
github: "your-username"
avatar: "./.images/avatar.png"
linkedin: "https://www.linkedin.com/in/your-username"
website: "https://your-website.com"
support_email: "support@your-domain.com"
status: "community"
---

# Your Name

Brief description of who you are and what you do.

Note

The linkedin, website, and support_email fields are optional and can be omitted or left empty if not applicable.

2. Generate module scaffolding

Use the provided script to generate your module structure:

./scripts/new_module.sh [your-username]/[module-name]
cd registry/[your-username]/modules/[module-name]

This creates:

  • main.tf - Terraform configuration template
  • README.md - Documentation template with frontmatter
  • run.sh - Optional execution script

3. Implement your module

Edit main.tf to build your module's features. Here's an example based on the git-clone module structure:

terraform {
  required_providers {
    coder = {
      source = "coder/coder"
    }
  }
}

# Input variables
variable "agent_id" {
  description = "The ID of a Coder agent"
  type        = string
}

variable "url" {
  description = "Git repository URL to clone"
  type        = string
  validation {
    condition = can(regex("^(https?://|git@)", var.url))
    error_message = "URL must be a valid git repository URL."
  }
}

variable "base_dir" {
  description = "Directory to clone the repository into"
  type        = string
  default     = "~"
}

# Resources
resource "coder_script" "clone_repo" {
  agent_id     = var.agent_id
  display_name = "Clone Repository"
  script = <<-EOT
    #!/bin/bash
    set -e
    
    # Ensure git is installed
    if ! command -v git &> /dev/null; then
        echo "Installing git..."
        sudo apt-get update && sudo apt-get install -y git
    fi
    
    # Clone repository if it doesn't exist
    if [ ! -d "${var.base_dir}/$(basename ${var.url} .git)" ]; then
        echo "Cloning ${var.url}..."
        git clone ${var.url} ${var.base_dir}/$(basename ${var.url} .git)
    fi
  EOT
  run_on_start = true
}

# Outputs
output "repo_dir" {
  description = "Path to the cloned repository"
  value       = "${var.base_dir}/$(basename ${var.url} .git)"
}

4. Write complete tests

Create main.test.ts to test your module features:

import { runTerraformApply, runTerraformInit, testRequiredVariables } from "~test"

describe("git-clone", async () => {
  await testRequiredVariables("registry/[your-username]/modules/git-clone")
  
  it("should clone repository successfully", async () => {
    await runTerraformInit("registry/[your-username]/modules/git-clone")
    await runTerraformApply("registry/[your-username]/modules/git-clone", {
      agent_id: "test-agent-id",
      url: "https://github.com/coder/coder.git",
      base_dir: "/tmp"
    })
  })
  
  it("should work with SSH URLs", async () => {
    await runTerraformInit("registry/[your-username]/modules/git-clone")
    await runTerraformApply("registry/[your-username]/modules/git-clone", {
      agent_id: "test-agent-id",
      url: "git@github.com:coder/coder.git"
    })
  })
})

5. Document your module

Update README.md with complete documentation:

---
display_name: "Git Clone"
description: "Clone a Git repository into your Coder workspace"
icon: "../../../../.icons/git.svg"
verified: false
tags: ["git", "development", "vcs"]
---

# Git Clone

This module clones a Git repository into your Coder workspace and ensures Git is installed.

## Usage

```tf
module "git_clone" {
  source   = "registry.coder.com/[your-username]/git-clone/coder"
  version  = "~> 1.0"
  
  agent_id = coder_agent.main.id
  url      = "https://github.com/coder/coder.git"
  base_dir = "/home/coder/projects"
}

Module best practices

Design principles

  • Single responsibility: Each module should have one clear purpose
  • Reusability: Design for use across different workspace types
  • Flexibility: Provide sensible defaults but allow customization
  • Safe to rerun: Ensure modules can be applied multiple times safely

Terraform conventions

  • Use descriptive variable names and include descriptions
  • Provide default values for optional variables
  • Include helpful outputs for working with other modules
  • Use proper resource dependencies
  • Follow Terraform style conventions

Documentation standards

Your module README should include:

  • Frontmatter: Required metadata for the registry
  • Description: Clear explanation of what the module does
  • Usage example: Working Terraform code snippet
  • Additional context: Setup requirements, known limitations, etc.

Note

Do not include variables tables in your README. The registry automatically generates variable documentation from your main.tf file.

Test your module

Run tests to ensure your module works correctly:

# Test your specific module
bun test -t 'git-clone'

# Test all modules
bun test

# Format code
bun fmt

Important

Tests require Docker with --network=host support, which typically requires Linux. macOS users can use Colima or OrbStack instead of Docker Desktop.

Contribute to existing modules

Types of contributions

Bug fixes:

  • Fix installation or configuration issues
  • Resolve compatibility problems
  • Correct documentation errors

Feature additions:

  • Add new configuration options
  • Support additional platforms or versions
  • Add new features

Maintenance:

  • Update dependencies
  • Improve error handling
  • Optimize performance

Making changes

  1. Identify the issue: Reproduce the problem or identify the improvement needed
  2. Make focused changes: Keep modifications minimal and targeted
  3. Maintain compatibility: Ensure existing users aren't broken
  4. Add tests: Test new features and edge cases
  5. Update documentation: Reflect changes in the README

Backward compatibility

When modifying existing modules:

  • Add new variables with sensible defaults
  • Don't remove existing variables without a migration path
  • Don't change variable types or meanings
  • Test that basic configurations still work

Versioning

When you modify a module, update its version following semantic versioning:

  • Patch (1.0.0 → 1.0.1): Bug fixes, documentation updates
  • Minor (1.0.0 → 1.1.0): New features, new variables
  • Major (1.0.0 → 2.0.0): Breaking changes, removing variables

Use the version bump script to update versions:

./.github/scripts/version-bump.sh patch|minor|major

Submit your contribution

  1. Create a feature branch:

    git checkout -b feat/modify-git-clone-module
    
  2. Test thoroughly:

    bun test -t 'git-clone'
    bun fmt
    
  3. Commit with clear messages:

    git add .
    git commit -m "feat(git-clone):add git-clone module"
    
  4. Open a pull request:

    • Use a descriptive title
    • Explain what the module does and why it's useful
    • Reference any related issues

Common issues and solutions

Testing problems

Issue: Tests fail with network errors Solution: Ensure Docker is running with --network=host support

Module development

Issue: Icon not displaying Solution: Verify icon path is correct and file exists in .icons/ directory

Documentation

Issue: Code blocks not syntax highlighted Solution: Use tf language identifier for Terraform code blocks

Get help

Next steps

After creating your first module:

  1. Share with the community: Announce your module on Discord or social media
  2. Iterate based on feedback: Improve based on user suggestions
  3. Create more modules: Build a collection of related tools
  4. Contribute to existing modules: Help maintain and improve the ecosystem

Happy contributing! 🚀