Claude Code Plugins

Community-maintained marketplace

Feedback

Develop with Gleam using idiomatic patterns, TDD, and type-driven design. Activate when working with .gleam files, gleam.toml, or user mentions Gleam, BEAM, or Erlang.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name gleam
description Develop with Gleam using idiomatic patterns, TDD, and type-driven design. Activate when working with .gleam files, gleam.toml, or user mentions Gleam, BEAM, or Erlang.

Gleam Development

Idiomatic Gleam with type-driven design and TDD.

Workflow

1. MODEL    → Define domain types first (make illegal states unrepresentable)
2. RED      → Write failing test
3. GREEN    → Minimal implementation
4. REFACTOR → Clean up, use pipelines
5. RUN      → gleam test && gleam run

CLI

gleam check                    # Fast type feedback (use often)
gleam test                     # Run tests
gleam run                      # Execute main
gleam format                   # Format all
gleam add pkg --dev            # Dev dependency

Patterns

Error Propagation with use

pub fn process(path: String) -> Result(Data, Error) {
  use content <- result.try(read(path))
  use parsed <- result.try(parse(content))
  use valid <- result.try(validate(parsed))
  Ok(transform(valid))
}

// Wrap external errors
use raw <- result.map_error(read(path), FileError)

// Early return
use <- bool.guard(string.is_empty(name), Error(EmptyName))

Opaque Types (Enforce Invariants)

pub opaque type Email {
  Email(String)
}

pub fn from_string(s: String) -> Result(Email, String) {
  case string.contains(s, "@") {
    True -> Ok(Email(s))
    False -> Error("Invalid email")
  }
}

Make Illegal States Unrepresentable

// BAD: allows invalid combinations
pub type Request {
  Request(status: String, data: Option(Data), error: Option(Error))
}

// GOOD: impossible states are unrepresentable
pub type Request {
  Pending
  Success(Data)
  Failed(Error)
}

Subject-First for Pipelines

pub fn add(to num: Int, value: Int) -> Int

items
|> list.filter(fn(x) { x > 0 })
|> list.map(fn(x) { x * 2 })
|> int.sum

Pattern Matching

// Multi-subject
case x, y {
  0, 0 -> "origin"
  _, 0 -> "x-axis"
  0, _ -> "y-axis"
  _, _ -> "plane"
}

// List destructuring
case items {
  [] -> empty()
  [only] -> single(only)
  [first, ..rest] -> many(first, rest)
}

// Guards
case n {
  x if x > 0 -> "positive"
  x if x < 0 -> "negative"
  _ -> "zero"
}

// let assert for guaranteed matches only
let assert Ok(config) = load_required_config()

Custom Error Types

pub type UserError {
  InvalidEmail(String)
  InvalidAge(Int)
  NotFound(UserId)
}

// Wrap externals
pub type AppError {
  DbError(postgres.Error)
  ValidationError(UserError)
}

Labelled Arguments

Proactively use labels for readability.

pub fn create(name name: String, email email: String) -> User

// Shorthand when var matches label
let name = "Alice"
create(name:, email: "a@b.com")

Project Structure

app/
├── gleam.toml
├── src/
│   ├── app.gleam           # Entry
│   └── app/
│       ├── domain/         # Types + constructors
│       └── internal/       # Private (configure in gleam.toml)
└── test/
    └── app_test.gleam
# gleam.toml
internal_modules = ["app/internal", "app/internal/*"]

[dependencies]
...

[dev-dependencies]
...

Anti-Patterns

Avoid Do Instead
import gleam/io.{println} io.println(...)
panic for expected failures Return Result
let assert on user input Pattern match + handle
list.at(items, n) indexing Pattern match or fold
Nested result.try callbacks use expressions
Boolean flags for states Sum types