# String Built-ins

| Function | Description | OPA | Wasm | [Swift](https://github.com/open-policy-agent/swift-opa) | [Java](https://github.com/open-policy-agent/java-opa-sdk) |
| --- | --- | --- | --- | --- | --- |
| `concat` | `output := concat(delimiter, collection)`  Joins a set or array of strings with a delimiter.  **Arguments:**  `delimiter` (string)  string to use as a delimiter  `collection` (any<array\[string\], set\[string\]>)  strings to join  **Returns:**  `output` (string)  the joined string | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `contains` | `result := contains(haystack, needle)`  Returns `true` if the search string is included in the base string  **Arguments:**  `haystack` (string)  string to search in  `needle` (string)  substring to look for  **Returns:**  `result` (boolean)  result of the containment check | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `endswith` | `result := endswith(search, base)`  Returns true if the search string ends with the base string.  **Arguments:**  `search` (string)  search string  `base` (string)  base string  **Returns:**  `result` (boolean)  result of the suffix check | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `format_int` | `output := format_int(number, base)`  Returns the string representation of the number in the given base after rounding it down to an integer value.  **Arguments:**  `number` (number)  number to format  `base` (number)  base of number representation to use  **Returns:**  `output` (string)  formatted number | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `indexof` | `output := indexof(haystack, needle)`  Returns the index of a substring contained inside a string.  **Arguments:**  `haystack` (string)  string to search in  `needle` (string)  substring to look for  **Returns:**  `output` (number)  index of first occurrence, `-1` if not found | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `indexof_n` | `output := indexof_n(haystack, needle)`  Returns a list of all the indexes of a substring contained inside a string.  **Arguments:**  `haystack` (string)  string to search in  `needle` (string)  substring to look for  **Returns:**  `output` (array\[number\])  all indices at which `needle` occurs in `haystack`, may be empty | [v0.37.0](https://github.com/open-policy-agent/opa/releases/v0.37.0) | SDK | 0.0.1 | 0.1.0 |
| `lower` | `y := lower(x)`  Returns the input string but with all characters in lower-case.  **Arguments:**  `x` (string)  string that is converted to lower-case  **Returns:**  `y` (string)  lower-case of x | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `replace` | `y := replace(x, old, new)`  Replace replaces all instances of a sub-string.  **Arguments:**  `x` (string)  string being processed  `old` (string)  substring to replace  `new` (string)  string to replace `old` with  **Returns:**  `y` (string)  string with replaced substrings | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `split` | `ys := split(x, delimiter)`  Split returns an array containing elements of the input string split on a delimiter.  **Arguments:**  `x` (string)  string that is split  `delimiter` (string)  delimiter used for splitting  **Returns:**  `ys` (array\[string\])  split parts | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `sprintf` | `output := sprintf(format, values)`  Returns the given string, formatted.  **Arguments:**  `format` (string)  string with formatting verbs  `values` (array\[any\])  arguments to format into formatting verbs  **Returns:**  `output` (string)  `format` formatted by the values in `values` | v0.17.0 | SDK | 0.0.1 | 0.1.0 |
| `startswith` | `result := startswith(search, base)`  Returns true if the search string begins with the base string.  **Arguments:**  `search` (string)  search string  `base` (string)  base string  **Returns:**  `result` (boolean)  result of the prefix check | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `strings.any_prefix_match` | `result := strings.any_prefix_match(search, base)`  Returns true if any of the search strings begins with any of the base strings.  **Arguments:**  `search` (any<string, array\[string\], set\[string\]>)  search string(s)  `base` (any<string, array\[string\], set\[string\]>)  base string(s)  **Returns:**  `result` (boolean)  result of the prefix check | [v0.44.0](https://github.com/open-policy-agent/opa/releases/v0.44.0) | SDK | 0.0.5 | 0.1.0 |
| `strings.any_suffix_match` | `result := strings.any_suffix_match(search, base)`  Returns true if any of the search strings ends with any of the base strings.  **Arguments:**  `search` (any<string, array\[string\], set\[string\]>)  search string(s)  `base` (any<string, array\[string\], set\[string\]>)  base string(s)  **Returns:**  `result` (boolean)  result of the suffix check | [v0.44.0](https://github.com/open-policy-agent/opa/releases/v0.44.0) | SDK | 0.0.5 | 0.1.0 |
| `strings.count` | `output := strings.count(search, substring)`  Returns the number of non-overlapping instances of a substring in a string.  **Arguments:**  `search` (string)  string to search in  `substring` (string)  substring to look for  **Returns:**  `output` (number)  count of occurrences, `0` if not found | [v0.67.0](https://github.com/open-policy-agent/opa/releases/v0.67.0) | SDK | 0.0.1 | 0.1.0 |
| `strings.render_template` | `result := strings.render_template(value, vars)`  Renders a templated string with given template variables injected. For a given templated string and key/value mapping, values will be injected into the template where they are referenced by key. For examples of templating syntax, see https://pkg.go.dev/text/template  **Arguments:**  `value` (string)  a templated string  `vars` (object\[string: any\])  a mapping of template variable keys to values  **Returns:**  `result` (string)  rendered template with template variables injected | [v0.59.0](https://github.com/open-policy-agent/opa/releases/v0.59.0) | SDK | 0.0.5 | ✗ |
| `strings.replace_n` | `output := strings.replace_n(patterns, value)`  Replaces a string from a list of old, new string pairs. Replacements are performed in the order they appear in the target string, without overlapping matches. The old string comparisons are done in argument order.  **Arguments:**  `patterns` (object\[string: string\])  replacement pairs  `value` (string)  string to replace substring matches in  **Returns:**  `output` (string)  string with replaced substrings | v0.17.0 | ✓ | 0.0.5 | 0.1.0 |
| `strings.reverse` | `y := strings.reverse(x)`  Reverses a given string.  **Arguments:**  `x` (string)  string to reverse  **Returns:**  `y` (string)  reversed string | [v0.36.0](https://github.com/open-policy-agent/opa/releases/v0.36.0) | ✓ | 0.0.1 | 0.1.0 |
| `strings.split_n` | `ys := strings.split_n(x, delimiter, n)`  Returns an array of at most `n` parts of `x` split on `delimiter`. If `n` is positive, returns the first `n` parts. If `n` is negative, returns the last `abs(n)` parts. If `n` is zero, returns an empty array. If `abs(n)` exceeds the number of parts, all parts are returned.  **Arguments:**  `x` (string)  string that is split  `delimiter` (string)  delimiter used for splitting  `n` (number)  number of parts to return; positive selects from the left, negative from the right, zero returns an empty array  **Returns:**  `ys` (array\[string\])  split parts | [v1.19.0](https://github.com/open-policy-agent/opa/releases/v1.19.0) | SDK | ✗ | ✗ |
| `substring` | `output := substring(value, offset, length)`  Returns the portion of a string for a given `offset` and a `length`. If `length < 0`, `output` is the remainder of the string.  **Arguments:**  `value` (string)  string to extract substring from  `offset` (number)  offset, must be positive  `length` (number)  length of the substring starting from `offset`  **Returns:**  `output` (string)  substring of `value` from `offset`, of length `length` | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `trim` | `output := trim(value, cutset)`  Returns `value` with all leading or trailing instances of the `cutset` characters removed.  **Arguments:**  `value` (string)  string to trim  `cutset` (string)  string of characters that are cut off  **Returns:**  `output` (string)  string trimmed of `cutset` characters | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `trim_left` | `output := trim_left(value, cutset)`  Returns `value` with all leading instances of the `cutset` characters removed.  **Arguments:**  `value` (string)  string to trim  `cutset` (string)  string of characters that are cut off on the left  **Returns:**  `output` (string)  string left-trimmed of `cutset` characters | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `trim_prefix` | `output := trim_prefix(value, prefix)`  Returns `value` without the prefix. If `value` doesn't start with `prefix`, it is returned unchanged.  **Arguments:**  `value` (string)  string to trim  `prefix` (string)  prefix to cut off  **Returns:**  `output` (string)  string with `prefix` cut off | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `trim_right` | `output := trim_right(value, cutset)`  Returns `value` with all trailing instances of the `cutset` characters removed.  **Arguments:**  `value` (string)  string to trim  `cutset` (string)  string of characters that are cut off on the right  **Returns:**  `output` (string)  string right-trimmed of `cutset` characters | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `trim_space` | `output := trim_space(value)`  Return the given string with all leading and trailing white space removed.  **Arguments:**  `value` (string)  string to trim  **Returns:**  `output` (string)  string leading and trailing white space cut off | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `trim_suffix` | `output := trim_suffix(value, suffix)`  Returns `value` without the suffix. If `value` doesn't end with `suffix`, it is returned unchanged.  **Arguments:**  `value` (string)  string to trim  `suffix` (string)  suffix to cut off  **Returns:**  `output` (string)  string with `suffix` cut off | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |
| `upper` | `y := upper(x)`  Returns the input string but with all characters in upper-case.  **Arguments:**  `x` (string)  string that is converted to upper-case  **Returns:**  `y` (string)  upper-case of x | v0.17.0 | ✓ | 0.0.1 | 0.1.0 |

info

When using `sprintf`, values are pre-processed and may have an unexpected type. For example, `%T` evaluates to `string` for both `string` and `boolean` types. In such cases, use `type_name` to accurately evaluate the underlying type.

## Examples

### `contains`

`contains` is a commonly used Rego built-in function that checks if a string contains a substring. The function returns `true` if the string contains the substring and `false` otherwise.

Some examples of policy use cases where `contains` might be used include:

*   Simple validation, such as checking if an email contains a `@` symbol.
*   Checking if user input contains restricted words or phrases for content moderation.

caution

If you're looking to check a string that's expected to be at the start or end of a value, you might be better served by one of the following functions:

*   [Starts With](#builtin-strings-startswith): Checks if a string starts with a specified prefix.
*   [Any Prefix Match](#builtin-strings-stringsany_prefix_match): Checks if a string starts with any of the specified prefixes.
*   [Ends With](#builtin-strings-endswith): Checks if a string ends with a specified suffix.
*   [Any Suffix Match](#builtin-strings-stringsany_suffix_match): Checks if a string ends with any of the specified suffixes.

These are safer and potentially faster too.

danger

`contains` only operates on strings, if you're looking to check for the presence of a value in a list you cannot use this function.

note

If you're looking for the Rego keyword `contains` for building multi-value rules, you can read about it in the [keywords section](/docs/policy-reference/keywords/contains).

Simple email validation

In the example that follows, `contains` is used to test if the `@` symbol is contained in the supplied email address. This can be useful as a first check on raw user data.

policy.rego

```
package playexample1 if contains("alice@example.com", "@")example2 if contains("bob[at]example.com", "@")example3 if contains(input.email, "@")
```

Output

{
  "example1": true
}

input.json

```
{  "email": "hello at example.com"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcImVtYWlsXCI6IFwiaGVsbG8gYXQgZXhhbXBsZS5jb21cIlxufSIsImQiOiJ7fSIsInAiOiJwYWNrYWdlIHBsYXlcblxuZXhhbXBsZTEgaWYgY29udGFpbnMoXCJhbGljZUBleGFtcGxlLmNvbVwiLCBcIkBcIilcblxuZXhhbXBsZTIgaWYgY29udGFpbnMoXCJib2JbYXRdZXhhbXBsZS5jb21cIiwgXCJAXCIpXG5cbmV4YW1wbGUzIGlmIGNvbnRhaW5zKGlucHV0LmVtYWlsLCBcIkBcIilcbiJ9)

Keyword checking for content moderation

The `contains` function is also useful for checking user text for keywords, certain keywords might not be allowed. In this example, `reasons` will be a list of the banned words found in the `input.message`.

policy.rego

```
package playbanned_words := {"hate", "kill"}reasons contains word if {	some word in banned_words	contains(input.message, word)}
```

Output

{
  "banned\_words": \[
    "hate",
    "kill"
  \],
  "reasons": \[
    "hate"
  \]
}

input.json

```
{  "message": "i hate bananas"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcIm1lc3NhZ2VcIjogXCJpIGhhdGUgYmFuYW5hc1wiXG59IiwiZCI6Int9IiwicCI6InBhY2thZ2UgcGxheVxuXG5iYW5uZWRfd29yZHMgOj0ge1wiaGF0ZVwiLCBcImtpbGxcIn1cblxucmVhc29ucyBjb250YWlucyB3b3JkIGlmIHtcblx0c29tZSB3b3JkIGluIGJhbm5lZF93b3Jkc1xuXHRjb250YWlucyhpbnB1dC5tZXNzYWdlLCB3b3JkKVxufVxuIn0=)

### `startswith`

`startswith` reports whether a string begins with a given prefix. Prefer it over `contains` when the match must be at the front of the value (HTTP paths, registry prefixes, file paths).

Restricting requests to an API path prefix

When a policy only cares about the start of a string — for example an HTTP path or a registry prefix — `startswith` is clearer (and usually safer) than a loose `contains` check.

This example allows only paths under `/api/v1/`.

policy.rego

```
package playdefault allow := falseallow if startswith(input.path, "/api/v1/")deny contains msg if {	not allow	msg := sprintf("path %q is outside /api/v1/", [input.path])}
```

Output

{
  "allow": false,
  "deny": \[
    "path \\"/api/v2/users\\" is outside /api/v1/"
  \]
}

input.json

```
{  "path": "/api/v2/users"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInBhdGhcIjogXCIvYXBpL3YyL3VzZXJzXCJcbn0iLCJkIjoie30iLCJwIjoicGFja2FnZSBwbGF5XG5cbmRlZmF1bHQgYWxsb3cgOj0gZmFsc2VcblxuYWxsb3cgaWYgc3RhcnRzd2l0aChpbnB1dC5wYXRoLCBcIi9hcGkvdjEvXCIpXG5cbmRlbnkgY29udGFpbnMgbXNnIGlmIHtcblx0bm90IGFsbG93XG5cdG1zZyA6PSBzcHJpbnRmKFwicGF0aCAlcSBpcyBvdXRzaWRlIC9hcGkvdjEvXCIsIFtpbnB1dC5wYXRoXSlcbn1cbiJ9)

### `sprintf`

`sprintf` builds a string from a format and a list of values. Deny rules use it to put the failing field and value into the message returned to the caller.

See also the note at the top of this page about how `sprintf` pre-processes values (for example with `%T`).

Building a clear deny message

`sprintf` formats a string with values from the policy. Admission and authorization policies use it so users see _which_ field failed and _what_ value was rejected, not only a bare `false`.

policy.rego

```
package play# Guests may read, but nothing else.deny contains msg if {	input.role == "guest"	input.action != "read"	msg := sprintf(		"user %v with role %v cannot %v %v",		[input.user, input.role, input.action, input.resource],	)}
```

Output

{
  "deny": \[
    "user alice with role guest cannot delete orders/42"
  \]
}

input.json

```
{  "user": "alice",  "role": "guest",  "action": "delete",  "resource": "orders/42"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInVzZXJcIjogXCJhbGljZVwiLFxuICBcInJvbGVcIjogXCJndWVzdFwiLFxuICBcImFjdGlvblwiOiBcImRlbGV0ZVwiLFxuICBcInJlc291cmNlXCI6IFwib3JkZXJzLzQyXCJcbn0iLCJkIjoie30iLCJwIjoicGFja2FnZSBwbGF5XG5cbiMgR3Vlc3RzIG1heSByZWFkLCBidXQgbm90aGluZyBlbHNlLlxuZGVueSBjb250YWlucyBtc2cgaWYge1xuXHRpbnB1dC5yb2xlID09IFwiZ3Vlc3RcIlxuXHRpbnB1dC5hY3Rpb24gIT0gXCJyZWFkXCJcblx0bXNnIDo9IHNwcmludGYoXG5cdFx0XCJ1c2VyICV2IHdpdGggcm9sZSAldiBjYW5ub3QgJXYgJXZcIixcblx0XHRbaW5wdXQudXNlciwgaW5wdXQucm9sZSwgaW5wdXQuYWN0aW9uLCBpbnB1dC5yZXNvdXJjZV0sXG5cdClcbn1cbiJ9)

### `split`

`split` returns a list of substrings separated by a delimiter. Use it when a path, hostname, or other structured string needs to be broken into parts.

Pull a path segment with split

`split` breaks a string into a list of parts. Policies often use it to pull a segment out of a path or dotted name before comparing against a list of allowed values.

policy.rego

```
package play# "/teams/payments/deploy" -> ["", "teams", "payments", "deploy"]parts := split(input.path, "/")team := parts[2]default allow := falseallow if team == "payments"
```

Output

{
  "allow": true,
  "parts": \[
    "",
    "teams",
    "payments",
    "deploy"
  \],
  "team": "payments"
}

input.json

```
{  "path": "/teams/payments/deploy"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInBhdGhcIjogXCIvdGVhbXMvcGF5bWVudHMvZGVwbG95XCJcbn0iLCJkIjoie30iLCJwIjoicGFja2FnZSBwbGF5XG5cbiMgXCIvdGVhbXMvcGF5bWVudHMvZGVwbG95XCIgLT4gW1wiXCIsIFwidGVhbXNcIiwgXCJwYXltZW50c1wiLCBcImRlcGxveVwiXVxucGFydHMgOj0gc3BsaXQoaW5wdXQucGF0aCwgXCIvXCIpXG50ZWFtIDo9IHBhcnRzWzJdXG5cbmRlZmF1bHQgYWxsb3cgOj0gZmFsc2VcblxuYWxsb3cgaWYgdGVhbSA9PSBcInBheW1lbnRzXCJcbiJ9)

### `lower`

`lower` converts a string to lowercase. It is useful for normalisation and formatting when values may arrive in mixed case.

Normalize roles with lower

`lower` returns the lowercase form of a string. Use it when identity providers send mixed-case roles or emails and you want a stable comparison.

policy.rego

```
package playrole := lower(input.role)default allow := falseallow if role in data.admin_roles
```

Output

{
  "allow": true,
  "role": "admin"
}

input.json

```
{  "role": "Admin"}
```

data.json

```
{  "admin_roles": [    "admin",    "owner"  ]}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInJvbGVcIjogXCJBZG1pblwiXG59IiwiZCI6IntcbiAgXCJhZG1pbl9yb2xlc1wiOiBbXG4gICAgXCJhZG1pblwiLFxuICAgIFwib3duZXJcIlxuICBdXG59IiwicCI6InBhY2thZ2UgcGxheVxuXG5yb2xlIDo9IGxvd2VyKGlucHV0LnJvbGUpXG5cbmRlZmF1bHQgYWxsb3cgOj0gZmFsc2VcblxuYWxsb3cgaWYgcm9sZSBpbiBkYXRhLmFkbWluX3JvbGVzXG4ifQ==)

### `endswith`

`endswith` reports whether a string ends with a given suffix. Use this function to valid matches at the ends of string values (file extensions, email domains, path suffixes etc.).

Allow only approved file extensions

`endswith` checks whether a string ends with a given suffix. Use it for file extensions, email domains, or other trailing markers where `contains` would match in the wrong place.

This example denies filenames that do not end with `.json` or `.yaml`.

policy.rego

```
package playdeny contains $"disallowed ext: {input.filename}" if {	not _valid_file_ext}_valid_file_ext if endswith(input.filename, ".json")_valid_file_ext if endswith(input.filename, ".yaml")
```

Output

{
  "deny": \[
    "disallowed ext: report.exe"
  \]
}

input.json

```
{  "filename": "report.exe"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcImZpbGVuYW1lXCI6IFwicmVwb3J0LmV4ZVwiXG59IiwiZCI6Int9IiwicCI6InBhY2thZ2UgcGxheVxuXG5kZW55IGNvbnRhaW5zICRcImRpc2FsbG93ZWQgZXh0OiB7aW5wdXQuZmlsZW5hbWV9XCIgaWYge1xuXHRub3QgX3ZhbGlkX2ZpbGVfZXh0XG59XG5cbl92YWxpZF9maWxlX2V4dCBpZiBlbmRzd2l0aChpbnB1dC5maWxlbmFtZSwgXCIuanNvblwiKVxuX3ZhbGlkX2ZpbGVfZXh0IGlmIGVuZHN3aXRoKGlucHV0LmZpbGVuYW1lLCBcIi55YW1sXCIpXG4ifQ==)

### `replace`

`replace` returns a string with every occurrence of a substring swapped for another. A common policy use is redacting sensitive values.

Redact a confidential header from a user-facing message

When a policy builds a user-facing error, it can accidentally include a sensitive header value. `replace` redacts that substring before the message goes back to the caller.

policy.rego

```
package play# Message that would leak a confidential header, then redacted for the caller.safe_message := replace(	sprintf("upstream rejected request with %s", [input.headers.authorization]),	input.headers.authorization,	"[REDACTED]",)
```

Output

{
  "safe\_message": "upstream rejected request with \[REDACTED\]"
}

input.json

```
{  "headers": {    "authorization": "Bearer abc123xyz"  }}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcImhlYWRlcnNcIjoge1xuICAgIFwiYXV0aG9yaXphdGlvblwiOiBcIkJlYXJlciBhYmMxMjN4eXpcIlxuICB9XG59IiwiZCI6Int9IiwicCI6InBhY2thZ2UgcGxheVxuXG4jIE1lc3NhZ2UgdGhhdCB3b3VsZCBsZWFrIGEgY29uZmlkZW50aWFsIGhlYWRlciwgdGhlbiByZWRhY3RlZCBmb3IgdGhlIGNhbGxlci5cbnNhZmVfbWVzc2FnZSA6PSByZXBsYWNlKFxuXHRzcHJpbnRmKFwidXBzdHJlYW0gcmVqZWN0ZWQgcmVxdWVzdCB3aXRoICVzXCIsIFtpbnB1dC5oZWFkZXJzLmF1dGhvcml6YXRpb25dKSxcblx0aW5wdXQuaGVhZGVycy5hdXRob3JpemF0aW9uLFxuXHRcIltSRURBQ1RFRF1cIixcbilcbiJ9)