> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# String functions

> Signatures, behavior, examples, and Rivet compatibility boundaries.

String arguments are strict strings unless a formatting verb explicitly accepts another value. `length`, `substr`, and `strrev` count Unicode grapheme clusters.

| Function                         | Result and rules                                                                                                                                    |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chomp(text)`                    | Remove trailing CR/LF characters.                                                                                                                   |
| `endswith(text, suffix)`         | Test a literal suffix; returns a boolean.                                                                                                           |
| `format(pattern, values...)`     | Produce one formatted string; see verbs below.                                                                                                      |
| `formatlist(pattern, values...)` | Produce strings by corresponding list elements; scalar arguments repeat and all list lengths must agree; scalar-only arguments produce one element. |
| `indent(spaces, text)`           | Indent every line after the first; spaces is a nonnegative integer.                                                                                 |
| `join(separator, lists...)`      | Join one or more lists and sets of strings in order.                                                                                                |
| `lower(text)`                    | Unicode lowercase.                                                                                                                                  |
| `regex(pattern, text)`           | First match; no match fails. Capture groups determine the result shape.                                                                             |
| `regexall(pattern, text)`        | List of all matches with the same capture-dependent shape; no match returns an empty list.                                                          |
| `replace(text, from, to)`        | Replace all literal occurrences; slash-delimited from selects regex replacement.                                                                    |
| `split(separator, text)`         | List of parts; an empty separator splits Unicode scalar values without empty boundaries.                                                            |
| `startswith(text, prefix)`       | Test a literal prefix; returns a boolean.                                                                                                           |
| `strcontains(text, part)`        | Test a literal substring; returns a boolean.                                                                                                        |
| `strrev(text)`                   | Reverse grapheme clusters.                                                                                                                          |
| `substr(text, offset, length)`   | Select grapheme clusters; a negative offset counts from the end and a negative length selects the rest.                                             |
| `title(text)`                    | Uppercase word starts using Rust Unicode casing.                                                                                                    |
| `trim(text, characters)`         | Remove any listed characters from both ends.                                                                                                        |
| `trimprefix(text, prefix)`       | Remove one matching literal prefix.                                                                                                                 |
| `trimsuffix(text, suffix)`       | Remove one matching literal suffix.                                                                                                                 |
| `trimspace(text)`                | Remove leading and trailing Unicode whitespace.                                                                                                     |
| `upper(text)`                    | Unicode uppercase.                                                                                                                                  |

`format` and `formatlist` accept `v`, `#v`, `t`, `b`, `d`, `o`, `x`, `X`, `e`, `E`, `f`, `g`, `G`, `s`, and `q`, explicit argument positions, width, precision, and sign/alignment/zero-padding flags. `%%` emits a percent sign. Formatting is bounded; floating-point rounding follows Rust.

Without captures, regex results are strings; unnamed captures produce lists; named captures produce maps. Mixed named and unnamed capture styles fail. Patterns use Rust's bounded regex engine, with no look-around or backreferences. Regex replacement supports capture substitutions such as `$1` and `${name}`; escape the latter as `$${name}` inside an HCL string. Rust Unicode classes can differ from Terraform's RE2 behavior.

`templatestring` is covered under [templates](/language/templates), and scalar conversion functions under [conversion and sensitivity](/language/functions/conversion).

## Example

```hcl
locals {
  strings = {
    chomped  = chomp("hello\r\n")
    suffix   = endswith("config.ini", ".ini")
    formatted = format("port=%04d", 80)
    repeated = formatlist("%s:%d", ["app", "web"], 8080)
    indented = indent(2, "first\nsecond")
    joined   = join(",", ["app", "ops"])
    lowered  = lower("RIVET")
    captured = regex("(?P<name>[a-z]+)", "rivet")
    matches  = regexall("[0-9]+", "port 80 and 443")
    replaced = replace("hello world", "world", "rivet")
    parts    = split(",", "app,ops")
    prefix   = startswith("rivet.conf", "rivet")
    contains = strcontains("rivet.conf", ".")
    reversed = strrev("abc")
    selected = substr("rivet", 0, 3)
    titled   = title("rivet docs")
    trimmed  = trim("--app--", "-")
    no_prefix = trimprefix("app-name", "app-")
    no_suffix = trimsuffix("app.conf", ".conf")
    no_space = trimspace(" app ")
    uppered  = upper("rivet")
  }
}
output "strings" {
  type  = string
  value = jsonencode(local.strings)
}
```

All calls obey the [function limits and annotation rules](/language/functions).