Claude Code Plugins

Community-maintained marketplace

Feedback

Write idiomatic Gleam code for the BEAM VM and JavaScript. Use when working with .gleam files, Gleam projects, or when the user mentions Gleam, BEAM, OTP actors, or type-safe functional programming.

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 Write idiomatic Gleam code for the BEAM VM and JavaScript. Use when working with .gleam files, Gleam projects, or when the user mentions Gleam, BEAM, OTP actors, or type-safe functional programming.

Gleam

Gleam is a statically typed functional programming language for the Erlang VM (BEAM) and JavaScript. It combines Erlang's legendary fault tolerance with a modern type system that catches errors at compile time.

Core Philosophy: Explicitness Over Implicitness

Gleam rejects implicit behavior in favor of transparency:

  • No implicit casting: Int cannot be treated as Float. Conversions are explicit functions.
  • No operator overloading: + for Int, +. for Float. Reading code reveals types immediately.
  • No exceptions for control flow: Errors are values (Result type), not thrown exceptions.
  • No null: Absence is modeled with Option(a).
// Explicit conversion required
let x: Int = 42
let y: Float = int.to_float(x)  // Not just `x + 1.0`

// Operators are type-specific
let sum_int = 1 + 2       // Int addition
let sum_float = 1.0 +. 2.0  // Float addition (note the dot)

Naming Conventions (Parser-Enforced)

Entity Case Example Notes
Variables snake_case user_id, is_valid Must start lowercase
Functions snake_case get_user, parse_json Verb-noun phrases
Modules snake_case gleam/io, my_app/user Matches filename
Types PascalCase User, HttpRequest Must start uppercase
Constructors PascalCase Ok, Error, Some Same as types
Constants snake_case max_retries SCREAMING_SNAKE also common

Hard constraint: The parser enforces casing. type user fails. let User = fails.

Essential Syntax

Variables and Immutability

// Immutable binding
let name = "Gleam"

// Variable shadowing (idiomatic for transformations)
let user = "  alice  "
let user = string.trim(user)
let user = string.uppercase(user)
// Previous `user` values are discarded

Functions

// Private function
fn add(a: Int, b: Int) -> Int {
  a + b
}

// Public function with documentation
/// Greets a user by name.
pub fn greet(name: String) -> String {
  "Hello, " <> name <> "!"
}

// Anonymous function (closure)
let double = fn(x) { x * 2 }

// Labelled arguments (for clarity at call site)
pub fn create_user(name name: String, age age: Int) -> User {
  User(name: name, age: age)
}

// Called with labels
create_user(name: "Alice", age: 30)

The Pipe Operator |>

Gleam's signature feature—data flows left to right through transformation pipelines:

"  hello world  "
|> string.trim
|> string.uppercase
|> string.replace(" ", "_")
// => "HELLO_WORLD"

// Subject always goes to FIRST argument
// Use capture `_` for other positions
10
|> int.add(5, _)  // Passes 10 as second arg

Pattern Matching with case

The primary control flow construct. Replaces if/else, switch, and try/catch:

case value {
  0 -> "zero"
  1 -> "one"
  n if n > 0 -> "positive"
  _ -> "negative"
}

// Destructuring
case user {
  User(name: n, age: a) if a >= 18 -> "Adult: " <> n
  User(name: n, ..) -> "Minor: " <> n
}

// Matching on Result
case fetch_user(id) {
  Ok(user) -> render(user)
  Error(NotFound) -> "User not found"
  Error(DbError(msg)) -> "Database error: " <> msg
}

// Tuple matching (multiple values)
case role, is_logged_in {
  Admin, True -> render_admin()
  User, True -> render_user()
  _, False -> render_login()
}

The use Expression

Flattens callbacks and monadic chains. The code below use becomes a callback function:

// Without use (pyramid of doom)
result.try(step1(), fn(a) {
  result.try(step2(a), fn(b) {
    Ok(a + b)
  })
})

// With use (flat and readable)
use a <- result.try(step1())
use b <- result.try(step2(a))
Ok(a + b)

// Resource management pattern
use file <- with_file("data.txt")
// File automatically closed when block ends

Data Types

Primitives

// Bool
let flag: Bool = True  // or False

// Int (arbitrary precision on BEAM)
let count: Int = 1_000_000
let hex: Int = 0xFF

// Float (64-bit)
let pi: Float = 3.14159
let sci: Float = 1.0e-10

// String (UTF-8)
let greeting: String = "Hello"
let multiline = "Line 1
Line 2"
let concat = "Hello" <> " " <> "World"  // Not +

Custom Types (Algebraic Data Types)

// Record (product type)
pub type User {
  User(
    id: Int,
    name: String,
    email: String,
  )
}

// Union (sum type) - replaces enums
pub type ConnectionState {
  Disconnected
  Connecting(attempt: Int)
  Connected(ip: String, port: Int)
}

// Opaque type (encapsulation)
pub opaque type Email {
  Email(String)
}

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

Option and Result

import gleam/option.{type Option, Some, None}
import gleam/result

// Option for nullable values
pub type Profile {
  Profile(
    name: String,
    bio: Option(String),  // May be absent
  )
}

// Result for fallible operations
pub fn divide(a: Int, b: Int) -> Result(Int, String) {
  case b {
    0 -> Error("Division by zero")
    _ -> Ok(a / b)
  }
}

Error Handling (Railway Oriented)

Errors are values, not exceptions. Chain with result.try or use:

pub type AppError {
  NotFound
  InvalidInput(String)
  DbError(String)
}

pub fn get_user_email(id: Int) -> Result(String, AppError) {
  use user <- result.try(fetch_user(id))
  use profile <- result.try(fetch_profile(user.id))
  Ok(profile.email)
}

// Map errors to unified type
pub fn load_config() -> Result(Config, AppError) {
  read_file("config.json")
  |> result.map_error(fn(_) { InvalidInput("Config not found") })
  |> result.try(parse_json)
}

Modules and Imports

// Import module
import gleam/io
import gleam/list

// Import with alias
import gleam/string as str

// Import specific items
import gleam/option.{type Option, Some, None}

// Qualified access
io.println("Hello")
list.map([1, 2, 3], fn(x) { x * 2 })

Common Gotchas

  1. Operators are typed: + for Int, +. for Float, <> for String
  2. No implicit truthiness: if list is invalid; use if list.length(x) > 0
  3. Exhaustive matching: case must handle ALL variants
  4. No return keyword: Last expression is the return value
  5. Guards are limited: Only comparisons/type checks, no function calls
  6. String concat is <>: Not +
  7. No null: Use Option(a) for optional values

Quick Reference

// List operations
[1, 2, 3] |> list.map(fn(x) { x * 2 })  // [2, 4, 6]
[1, 2, 3] |> list.filter(fn(x) { x > 1 })  // [2, 3]
[1, 2, 3] |> list.fold(0, fn(acc, x) { acc + x })  // 6

// Option operations
Some(42) |> option.map(fn(x) { x * 2 })  // Some(84)
None |> option.unwrap(0)  // 0

// Result operations
Ok(42) |> result.map(fn(x) { x * 2 })  // Ok(84)
Error("fail") |> result.unwrap(0)  // 0

Advanced Topics

Running Gleam

gleam new my_project     # Create project
gleam run                # Run project
gleam test               # Run tests
gleam build              # Build
gleam add gleam_json     # Add dependency