---
title: "String operations"
description: "Reference for the Node.js client's Aerospike.strings builders and Aerospike.exp.string builders for server-side String operations and expressions."
---

# String operations

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

Jump to the [Code block](#code-block) for a combined complete example.

[String](https://aerospike.com/docs/develop/data-types/string) operations let the server search, transform, extract, and normalize text in a String bin. That avoids fetching a bin, editing it in your application, and writing it back.

This reference covers the Aerospike Node.js client surface for developers already using [`client.operate()`](https://aerospike.com/docs/develop/client/node/usage/atomic/multi) and [expressions](https://aerospike.com/docs/develop/client/node/usage/atomic/expressions): the `Aerospike.strings` builders for `operate()` calls, and the `Aerospike.exp.string` builders for expressions. After reading this page, you can choose the right builder for a task, configure String write flags, and interpret `operate()` results.

It requires Aerospike Database 8.2.0 or later and Node.js client 7.0.0 or later. See [Version requirements](#version-requirements) before upgrading a production cluster. For operation semantics, argument details, and the full 37-operation catalog, see the [String operations reference](https://aerospike.com/docs/develop/data-types/string/operations) and [String expressions reference](https://aerospike.com/docs/develop/expressions/string). For installing or upgrading the `aerospike` package, see [Installation](https://aerospike.com/docs/develop/client/node/install).

## Setup

The examples on this page use the following connection and key:

```js
const Aerospike = await import("aerospike");

const strings = Aerospike.strings;

const stringExp = Aerospike.exp.string;

const exp = Aerospike.exp;

const map = Aerospike.maps;

// Set hosts to your server's address and port

const config = { hosts: "YOUR_HOST_ADDRESS:YOUR_PORT" };

// Creates a key with the namespace "sandbox", set "users", and user key "jdoe123"

const key = new Aerospike.Key("sandbox", "users", "jdoe123");

// Establishes a connection to the server

const client = await Aerospike.connect(config);
```

## Round-trip elimination

Without String operations, normalizing a bin takes a read, an application-side edit, and a write:

```js
await client.put(key, { email: "  Jane.Doe@Example.com  " });

// Before: fetch, modify, write

const before = await client.get(key);

const email = before.bins.email.trim().toLowerCase();

await client.put(key, { email });
```

`Aerospike.strings` runs the same edit inside a single `operate()` call, on the server:

```js
// After: one round trip

const ops = [strings.trim("email"), strings.lower("email")];

await client.operate(key, ops);

// Verify: "  Jane.Doe@Example.com  " becomes "jane.doe@example.com"

const after = await client.get(key);

console.info(after.bins.email);
```

## Two surfaces

Every String operation is available in two forms:

| Surface | Naming | Used with | Argument order |
| --- | --- | --- | --- |
| Operation | `Aerospike.strings.*` | `client.operate()` | Bin name first: `strings.strlen(binName)` |
| Expression | `Aerospike.exp.string.*` | `filterExpression` or [operation expressions](https://aerospike.com/docs/develop/client/node/usage/atomic/expressions#operation-expressions) | Source expression last: `stringExp.strlen(bin)` |

Use `filterExpression` in `ReadPolicy`/`WritePolicy`, or pass an `Aerospike.exp.string` builder to `exp.operations.read`/[`exp.operations.write`](https://aerospike.com/docs/develop/client/node/usage/atomic/expressions#write-1).

`Aerospike.strings` builders return a `StringOperation` for `client.operate()`. `Aerospike.exp.string` builders return a plain array describing an expression node, which composes inside a larger expression tree with no separate build or compile step. Assign the finished expression directly to `filterExpression`, or pass it to `exp.operations.read`/`exp.operations.write`.

A modify-style `Aerospike.exp.string` builder (`stringExp.upper`, `stringExp.replace`, `stringExp.trim`, and similar) returns the transformed string as a value. It does not write the result back to the bin on its own. To persist a modify expression’s result, write it back with [`exp.operations.write`](https://aerospike.com/docs/develop/client/node/usage/atomic/expressions#write-1), or use the `Aerospike.strings` equivalent instead. See [Nested strings](#nested-strings) for a worked filter and projection example.

Modify operations also take a policy or flags argument first, ahead of the bin name or source expression. On the operation surface, this means calling `.withPolicy({ writeFlags })` on the returned `StringOperation` rather than passing it as an argument:

```js
// Operation: chain .withPolicy() after the call

strings.upper("text").withPolicy({ writeFlags: strings.writeFlags.NO_FAIL });

// Expression: policy object first, source expression last

stringExp.upper({ flags: strings.writeFlags.NO_FAIL }, exp.binStr("text"));
```

## String write policy

`Aerospike.strings` exposes its write flags, regex flags, and numeric-type filter as constants merged in from the native binding:

```js
strings.writeFlags.DEFAULT; // 0

strings.writeFlags.CREATE_ONLY; // 1

strings.writeFlags.UPDATE_ONLY; // 2

strings.writeFlags.NO_FAIL; // 4
```

| Flag | Value | Effect |
| --- | --- | --- |
| `DEFAULT` | 0 | Allow create or update, subject to the operation’s own create capability (see [Missing-bin behavior](#missing-bin-behavior)). |
| `CREATE_ONLY` | 1 | Create the bin only when it’s missing. Valid only on the eight create-capable operations (see [Missing-bin behavior](#missing-bin-behavior)). Fails with `Aerospike.status.ERR_BIN_EXISTS` if the bin already exists. Mutually exclusive with `UPDATE_ONLY`, and invalid together with a CDT context path (`.withContext()`). |
| `UPDATE_ONLY` | 2 | Apply the operation only to an existing bin. Against a missing bin, this succeeds as a silent no-op rather than creating one or raising an error, even without `NO_FAIL` (see following caution). Mutually exclusive with `CREATE_ONLY`. |
| `NO_FAIL` | 4 | Return success and leave the bin unchanged if the operation can’t be applied, instead of failing. Doesn’t suppress a wrong bin type or invalid UTF-8. |

`strings.writeFlags` exposes `CREATE_ONLY` the same way the Java, Python, and C# clients do.

::: no_fail doesn’t cover every failure
`NO_FAIL` doesn’t suppress a wrong bin type or invalid UTF-8, in the stored value or in an argument. Both still raise an `AerospikeError` even with `NO_FAIL` set.
:::

### Missing-bin behavior

Against a missing bin, `DEFAULT` never fails, but it also doesn’t make every operation create one. Only 8 of the 19 modify operations can create a bin from nothing:

-   [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert)
-   [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite)
-   [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat)
-   [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append)
-   [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend)
-   [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start)
-   [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end)
-   [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat)

Calling one of those against a missing bin creates it, seeded from an empty string.

The other 11 modify operations (`trim`, `upper`, `replace`, and similar) can’t create a bin at all. Against a missing bin, they leave the record unchanged and still return success, so a subsequent read of that bin returns `undefined`. Read the bin back to confirm a write happened.

::: update_only against a missing bin fails silently, with or without no_fail
`UPDATE_ONLY` against a missing bin already succeeds as a no-op on its own, without needing `NO_FAIL`: the write applies nothing, and the client receives no error. A typo’d bin name or a bin another process already deleted won’t surface as an `AerospikeError`. `NO_FAIL` on its own separately suppresses a result that would exceed the [per-operation size cap](https://aerospike.com/docs/develop/data-types/string/operations#result-size-limits), leaving the bin unchanged. Both look identical to a normal success on the client, so either case can mask a production bug where writes silently stop applying. In a multi-operation `operate()` call, a suppressed write like this only affects that one operation. Sibling operations in the same call still commit, so the record can end up partially updated with no error to signal it. On any production write path that uses `UPDATE_ONLY` or `NO_FAIL` against a bin whose existence isn’t guaranteed, verify the outcome with a read-after-write rather than trusting the absence of an error.
:::

`.withPolicy({ writeFlags })` is a per-operation call, not client configuration: there’s no string-policy field on `ClientPolicy` or `OperatePolicy`. Chain it onto each `StringOperation` that needs non-default flags.

`regexReplace` takes `regexFlags` as a plain argument and write flags from `.withPolicy()`, like the other modify builders. It accepts `UPDATE_ONLY` and `NO_FAIL`. `CREATE_ONLY` fails with `Aerospike.status.ERR_REQUEST_INVALID`. See [Regex and numeric-type flags](#regex-and-numeric-type-flags).

## Read operations

All read operations take the bin name as the first `Aerospike.strings` argument, or the source expression as the last `Aerospike.exp.string` argument. Both also take an optional trailing collection data type (CDT) context (`CDTContext`) path to a value nested in a [List](https://aerospike.com/docs/develop/data-types/collections/list) or [Map](https://aerospike.com/docs/develop/data-types/collections/map), covered in [Nested strings](#nested-strings). Every operation requires the target to already be a String. Calling one against another bin type fails with `Aerospike.status.ERR_BIN_INCOMPATIBLE_TYPE` (server status `AS_ERR_INCOMPATIBLE_TYPE`, code 12). See [Error codes](https://aerospike.com/docs/database/reference/error-codes) and [error handling](https://aerospike.com/docs/develop/client/node/error-handling).

Index and length values count Unicode codepoints (the standard unit for string indexes in these operations, not UTF-8 bytes). Most characters are one codepoint, but some emoji span multiple codepoints for one visible character (a grapheme cluster, the character a reader perceives as a single unit). Characters beyond U+FFFF, including many emoji and historic scripts, can also affect index and length counts. Negative indexes count from the end of the string, and out-of-bounds indexes are clamped to the string’s length rather than raising an error. `regexCompare` uses [ICU regex](https://aerospike.com/docs/develop/data-types/string#unicode-semantics) syntax, the International Components for Unicode regex engine, which differs from JavaScript’s built-in `RegExp` in some constructs. See [Regex dialect](https://aerospike.com/docs/develop/data-types/string/regex-syntax#non-icu-constructs-rejected-at-parse-time) for spellings that ICU rejects.

Substring matching in `find`, `contains`, `startsWith`, and `endsWith` (and in `replace`/`replaceAll` under [Modify operations](#modify-operations)) treats canonically equivalent text as equal: different Unicode encodings of the same visual character compare as identical. For example, a precomposed `é` (U+00E9) matches `e` followed by a combining acute accent (U+0301).

| Operation | Node.js builders | Returns | Description |
| --- | --- | --- | --- |
| [`strlen`](https://aerospike.com/docs/develop/data-types/string/operations#strlen) | `strings.strlen` / `stringExp.strlen` | Integer | Codepoint count. |
| [`byte_length`](https://aerospike.com/docs/develop/data-types/string/operations#byte_length) | `strings.byteLength` / `stringExp.byteLength` | Integer | UTF-8 byte count. Differs from `strlen` for non-ASCII text. |
| [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) | `strings.substr` / `strings.substrRange`, and `stringExp.substr` (two- or three-arg) | String | Substring from `start` to the end, or the half-open range `[start, end)`. |
| [`char_at`](https://aerospike.com/docs/develop/data-types/string/operations#char_at) | `strings.charAt` / `stringExp.charAt` | String | The one-codepoint string at `index`. |
| [`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) | `strings.find` / `strings.findOccurrence`, and `stringExp.find` / `stringExp.findOccurrence` | Integer | Codepoint index of `needle`, or of a specific `occurrence` (1 = first, -1 = last). `-1` if not found. |
| [`contains`](https://aerospike.com/docs/develop/data-types/string/operations#contains) | `strings.contains` / `stringExp.contains` | Boolean | Whether the bin contains `needle`. |
| [`starts_with`](https://aerospike.com/docs/develop/data-types/string/operations#starts_with) | `strings.startsWith` / `stringExp.startsWith` | Boolean | Whether the bin begins with `prefix`. |
| [`ends_with`](https://aerospike.com/docs/develop/data-types/string/operations#ends_with) | `strings.endsWith` / `stringExp.endsWith` | Boolean | Whether the bin ends with `suffix`. |
| [`to_integer`](https://aerospike.com/docs/develop/data-types/string/operations#to_integer) | `strings.toInteger` / `stringExp.toInteger` | Integer | Parses the string as a 64-bit integer. Fails if it doesn’t parse. See [Type conversion](#type-conversion). |
| [`to_double`](https://aerospike.com/docs/develop/data-types/string/operations#to_double) | `strings.toDouble` / `stringExp.toDouble` | Float | Parses the string as a 64-bit float. Fails if it doesn’t parse. |
| [`is_numeric`](https://aerospike.com/docs/develop/data-types/string/operations#is_numeric) | `strings.isNumeric` / `strings.isNumericType`, and `stringExp.isNumeric` / `stringExp.isNumericType` | Boolean | Whether the bin’s spelling matches an optional `strings.numericType` (`ANY`, `INT`, or `FLOAT`). |
| [`is_upper`](https://aerospike.com/docs/develop/data-types/string/operations#is_upper) / [`is_lower`](https://aerospike.com/docs/develop/data-types/string/operations#is_lower) | `strings.isUpper`, `strings.isLower` / `stringExp.isUpper`, `stringExp.isLower` | Boolean | Whether every codepoint is a cased letter. Returns `false` if any digit, space, or punctuation is present, and `true` for an empty string. |
| [`to_blob`](https://aerospike.com/docs/develop/data-types/string/operations#to_blob) | `strings.toBlob` / `stringExp.toBlob` | Blob | The UTF-8 bytes of the string, as a [Blob](https://aerospike.com/docs/develop/data-types/blob). |
| [`split`](https://aerospike.com/docs/develop/data-types/string/operations#split) | `strings.split` / `strings.splitSeparator`, and `stringExp.split` / `stringExp.splitSeparator` | List | Splits by Unicode codepoint, or by `separator` (a singleton list if `separator` isn’t found). |
| [`b64_decode`](https://aerospike.com/docs/develop/data-types/string/operations#b64_decode) | `strings.b64Decode` / `stringExp.b64Decode` | Blob | Decodes the bin as base64 text into a [Blob](https://aerospike.com/docs/develop/data-types/blob). Fails if it isn’t valid base64. |
| [`regex_compare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare) | `strings.regexCompare` / `strings.regexCompareFlags`, and `stringExp.regexCompare` / `stringExp.regexCompareFlags` | Boolean | Matches an ICU regex `pattern` against the bin, optionally with `strings.regexFlags`. |

Seven read operations (`contains`, `startsWith`, `endsWith`, `isNumeric`, `isUpper`, `isLower`, `regexCompare`) return a native boolean, not an integer `0`/`1`. Their flag-carrying variants (`isNumericType`, `regexCompareFlags`) return the same boolean type. See [Reading operate() results](#reading-operate-results).

## Modify operations

Modify operations write a transformed value back to the bin (`Aerospike.strings`) or return it as an expression value (`Aerospike.exp.string`, which does not mutate the underlying bin). Every modify operation accepts `DEFAULT`, `UPDATE_ONLY`, or `NO_FAIL` using `.withPolicy()`. `CREATE_ONLY` is valid only on the eight create-capable operations in this table’s first eight rows, which are also the only ones that can create a missing bin (see [Missing-bin behavior](#missing-bin-behavior)).

::: destructive, irreversible writes
`Aerospike.strings` modify calls overwrite the stored bin value on the server, with no built-in undo. `snip`, `replace`, `replaceAll`, `regexReplace`, and `overwrite` can truncate or permanently discard part of the value if the index, pattern, or range is wrong. `strings.regexFlags.GLOBAL` raises the risk further on `regexReplace`, since it applies the replacement to every match in the bin instead of only the first. `repeat` is also destructive in one specific case: a `count` of `0` succeeds and replaces the bin with an empty string rather than leaving it unchanged, so an unvalidated `count` from application input can silently erase existing data. Because modify operations return no value on success, a bad index, pattern, range, or `count` looks identical to a correct write in the response. Back up the affected namespace or set before running a destructive operation at scale against production records, test against sample data first, and verify the result with a read-after-write.
:::
::: legacy data can fail with invalid utf-8
String operations validate UTF-8 before they run. A bin written by legacy, non-UTF-8-aware code can fail with `Aerospike.status.ERR_OP_NOT_APPLICABLE` (26) and `Aerospike.subcode.OPNOT_STRING_UTF8_INVALID` (11). See [error handling](https://aerospike.com/docs/develop/client/node/error-handling) for the general `AerospikeError` pattern, and [String operations and UTF-8 validation](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#string-operations-and-utf-8-validation) before running these operations against existing bins.
:::

| Operation | Node.js builders | Description |
| --- | --- | --- |
| [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert) | `strings.insert` / `stringExp.insert` | Splices `value` in at codepoint `index`. |
| [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) | `strings.overwrite` / `stringExp.overwrite` | Overwrites codepoints starting at `index` with `value`. |
| [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat) | `strings.concat` / `strings.concatList`, and `stringExp.concat` / `stringExp.concatList` | Appends one string, or each element of a list of strings, in order. |
| [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append) | `strings.append` / `stringExp.append` | Appends `value`. Unicode-aware, unlike the legacy `Aerospike.operations.append`. |
| [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend) | `strings.prepend` / `stringExp.prepend` | Prepends `value`. Unicode-aware, unlike the legacy `Aerospike.operations.prepend`. |
| [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start) | `strings.padStart` / `stringExp.padStart` | Left-pads with `padString` up to `targetLength` codepoints. No-op if already at or above the target. |
| [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end) | `strings.padEnd` / `stringExp.padEnd` | Right-pads with `padString` up to `targetLength` codepoints. |
| [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat) | `strings.repeat` / `stringExp.repeat` | Repeats the bin `count` times. A `count` of `0` empties the bin instead of leaving it unchanged. See [Destructive, irreversible writes](#modify-operations). |
| [`snip`](https://aerospike.com/docs/develop/data-types/string/operations#snip) | `strings.snip` (deprecated alias `strings.snipRange`) / `stringExp.snip` (deprecated alias `stringExp.snipRange`); or `strings.snipStart` / `stringExp.snipStart` for the one-argument form | Removes the half-open range `[start, end)`. With `snipStart`, or `snip` called with `end` omitted, removes from `start` through the end instead. |
| [`replace`](https://aerospike.com/docs/develop/data-types/string/operations#replace) | `strings.replace` / `stringExp.replace` | Replaces the first occurrence of `needle` with `replacement`. |
| [`replace_all`](https://aerospike.com/docs/develop/data-types/string/operations#replace_all) | `strings.replaceAll` / `stringExp.replaceAll` | Replaces every occurrence of `needle` with `replacement`. |
| [`upper`](https://aerospike.com/docs/develop/data-types/string/operations#upper) / [`lower`](https://aerospike.com/docs/develop/data-types/string/operations#lower) | `strings.upper`, `strings.lower` / `stringExp.upper`, `stringExp.lower` | Uppercases or lowercases the bin. |
| [`case_fold`](https://aerospike.com/docs/develop/data-types/string/operations#case_fold) | `strings.caseFold` / `stringExp.caseFold` | Maps characters to a common case for locale-independent, case-insensitive comparison keys. |
| [`normalize_nfc`](https://aerospike.com/docs/develop/data-types/string/operations#normalize_nfc) | `strings.normalizeNfc` / `stringExp.normalizeNfc` | Normalizes the bin to Unicode Normalization Form C (NFC), the canonical composed form. Already-normalized strings are unchanged. |
| [`trim`](https://aerospike.com/docs/develop/data-types/string/operations#trim) / [`trim_start`](https://aerospike.com/docs/develop/data-types/string/operations#trim_start) / [`trim_end`](https://aerospike.com/docs/develop/data-types/string/operations#trim_end) | `strings.trim`, `strings.trimStart`, `strings.trimEnd` / `stringExp.trim`, `stringExp.trimStart`, `stringExp.trimEnd` | Removes Unicode whitespace from both ends, the start, or the end. |
| [`regex_replace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) | `strings.regexReplace` / `stringExp.regexReplace` | Replaces the first regex match, or every match when `strings.regexFlags.GLOBAL` is set. See [String write policy](#string-write-policy). |

## Type conversion

`strings.toString`/`stringExp.toString` converts an Integer, Float, Boolean, String, or [Blob](https://aerospike.com/docs/develop/data-types/blob) bin to its string representation. It fails with `Aerospike.status.ERR_BIN_INCOMPATIBLE_TYPE` for any other bin type, and with `Aerospike.status.ERR_OP_NOT_APPLICABLE` (server status `AS_ERR_OP_NOT_APPLICABLE`, code 26) if a Blob bin’s bytes aren’t valid UTF-8.

```js
const record = await client.operate(key, [strings.toString("age")]);

console.info(record.bins.age);
```

`strings.toString` is the only operation that does not accept a `CDTContext`. It’s a separate server operation that always reads the whole bin and can’t carry a context path in its payload.

To convert a value nested inside a List or [Map](https://aerospike.com/docs/develop/data-types/collections/map), extract the nested string first with a list or map get operation (using the same `CDTContext`), then convert it client-side. Or compose `stringExp.toString` with `exp.lists.getByIndex`/`exp.maps.getByKey` inside an expression.

`toInteger`/`toDouble` parse failures and `toString`’s invalid-UTF-8 case both surface as `Aerospike.status.ERR_OP_NOT_APPLICABLE`, per the [String operations error codes](https://aerospike.com/docs/develop/data-types/string/operations#error-codes).

## Regex and numeric-type flags

`strings.regexFlags` (combine with bitwise OR) controls `regexCompareFlags` and `regexReplace`:

| Flag | Value | Applies to |
| --- | --- | --- |
| `NONE` | 0 | Both |
| `CASE_INSENSITIVE` | 1 | Both |
| `MULTILINE` | 2 | Both |
| `DOTALL` | 4 | Both |
| `UNIX_LINES` | 8 | Both |
| `GLOBAL` | 16 | `regexReplace` only. Replaces every match instead of only the first. |

`strings.numericType` narrows `isNumericType`: `ANY` (0, default from plain `isNumeric`), `INT` (1), or `FLOAT` (2). `FLOAT` requires a literal `.` followed by a digit, so `strings.isNumericType("bin", strings.numericType.FLOAT)` against `"5"` returns `false` even though `"5"` parses as a double.

## Reading operate() results

### Booleans decode as booleans

`contains`, `startsWith`, `endsWith`, `isNumeric`, `isNumericType`, `isUpper`, `isLower`, `regexCompare`, and `regexCompareFlags` decode as a native JavaScript `boolean`, not an integer `0`/`1`:

```js
const record = await client.operate(key, [strings.contains("email", "@")]);

console.info(record.bins.email); // true or false
```

### Multiple operations on one bin return a list

When more than one operation in a single `operate()` call targets the same bin, the client groups that bin’s results into an array, with one entry per operation, in call order. See [Returning from operate()](https://aerospike.com/docs/develop/client/node/usage/atomic/multi#returning-from-operate) in Bin operations for the general grouping rule. String operations follow it the same way CDT operations already do, with no extra policy needed.

Modify operations return no value of their own and don’t occupy an entry in the grouped array: the client’s own test suite confirms that chaining modify operations with a single read on the same bin returns that read’s value directly, with no array wrapper. Add a read operation for the same bin to retrieve the mutated value.

::: only value-returning operations appear in the grouped array
Confirmed against the client’s test suite: two `append` calls followed by one `read` on the same bin return the read’s string directly, not an array. When multiple reads target the same bin alongside a modify operation, expect the array to hold only the read results, in call order, with no placeholder entry for the modify.
:::

```js
const ops = [

    strings.trim("email"), // modify

    strings.strlen("email"), // read

    strings.substrRange("email", 0, 5), // read

];

const record = await client.operate(key, ops);

const results = record.bins.email; // array of the 2 read results, in order; trim's modify doesn't add an entry
```

A single string operation on a bin, with nothing else targeting that bin, returns its value directly, with no array wrapper.

## Nested strings

`Aerospike.strings` builders take an optional trailing [CDT context](https://aerospike.com/docs/develop/data-types/collections/context) (`CDTContext`), set with `.withContext()`, to reach a string nested inside a [List](https://aerospike.com/docs/develop/data-types/collections/list) or [Map](https://aerospike.com/docs/develop/data-types/collections/map). The path must already resolve to a string: a non-string nested value fails with `Aerospike.status.ERR_BIN_INCOMPATIBLE_TYPE`. A path that doesn’t resolve — an out-of-bounds list index or a missing map key — fails with `Aerospike.status.ERR_OP_NOT_APPLICABLE`; set `NO_FAIL` to turn it into a success that writes nothing. A malformed context path fails with `Aerospike.status.ERR_REQUEST_INVALID` (server status `AS_ERR_PARAMETER`, code 4) and isn’t suppressible by `NO_FAIL`. See [String operations context](https://aerospike.com/docs/develop/data-types/string/operations#context) for the full model, which the server enforces identically for every client.

```js
// Uppercase a string nested in a list bin "items" at index 0.

await client.operate(key, [strings.upper("items").withContext((ctx) => ctx.addListIndex(0))]);

// Read strlen of a string nested under a map key.

const record = await client.operate(key, [

    strings.strlen("profile").withContext((ctx) => ctx.addMapKey("bio")),

]);
```

`Aerospike.exp.string` builders don’t take a `CDTContext` at all. To apply a string expression to a nested value, project the value first with [`exp.lists.getByIndex`/`exp.maps.getByKey`](https://aerospike.com/docs/develop/expressions/nesting) (which do take a context), then pass the result as the source expression. The following example builds a `stringExp.strlen` condition, then uses it two ways: as a read filter, and as a projected read value.

```js
const bioLen = stringExp.strlen(

    exp.maps.getByKey(exp.binMap("profile"), exp.str("bio"), exp.type.STR, map.returnType.VALUE),

);

const isLong = exp.gt(bioLen, exp.int(280));

// As a filter: fetch the record only if its bio is over 280 codepoints

const readPolicy = new Aerospike.ReadPolicy({ filterExpression: isLong });

const record = await client.get(key, readPolicy);

// As a projection: always fetch the record, with the condition's result in a computed bin

const projected = await client.operate(key, [

    exp.operations.read("bioIsLong", isLong, exp.expReadFlags.DEFAULT),

]);

console.info(projected.bins.bioIsLong); // true or false
```

`strings.toString`/`stringExp.toString` never accepts a `CDTContext`, on either surface. See [Type conversion](#type-conversion).

## Version requirements

String operations require Aerospike Database 8.2.0 or later on every node, and Node.js client 7.0.0 or later. A server running a version prior to Database 8.2.0 doesn’t recognize the string opcodes and returns a generic parameter error, not a distinct “unsupported feature” code. Check the cluster version rather than branching on the error. Run `asinfo -v build` against each node to confirm it reports 8.2.0 or later. Check the installed `aerospike` package version with `npm list aerospike` to confirm the client version, and see [Installation](https://aerospike.com/docs/develop/client/node/install) to install or upgrade it.

::: rolling upgrades can cause intermittent failures
During a rolling upgrade of a multi-node cluster, application code that calls String operations can succeed or fail on the same request depending on which node serves it. Nodes already on 8.2.0 or later accept String operations, but nodes not yet upgraded reject them with the same generic parameter error. Confirm every node reports 8.2.0 or later with `asinfo -v build`. Enable String operations in application code only after every node passes that check, rather than waiting a fixed amount of time.
:::

## Deprecations

-   The legacy `Aerospike.operations.append(bin)` and `Aerospike.operations.prepend(bin)` are deprecated for String bins only, in favor of `strings.append`/`strings.prepend`, which are Unicode-aware. The legacy pair does a raw byte concatenation and doesn’t support string write flags or `CDTContext`.
-   Both legacy operations also accept Blob bins, which the string module can’t target. For a Blob bin, keep using `Aerospike.operations.append`/`Aerospike.operations.prepend`. There’s no string-module replacement, and the legacy behavior is unchanged and fully supported for that case.
-   The legacy `exp.cmpRegex(options, regex, cmpStr)` (POSIX regex, per `regex.h`) is deprecated for string matching in favor of `stringExp.regexCompare`/`stringExp.regexCompareFlags`, which are Unicode-aware (ICU regex). See [`string_regex_compare`](https://aerospike.com/docs/develop/expressions/string#string_regex_compare).
-   `strings.snipRange`/`stringExp.snipRange` are deprecated aliases for `snip` with the same behavior: both dispatch to the same underlying native call. On the expression surface, `stringExp.substrRange` is a deprecated alias for the three-argument `stringExp.substr(start, end, bin)`. On the operation surface, `strings.substrRange` is a distinct, non-deprecated function.

### Migrating from the legacy byte-concatenation APIs

Switching a bin’s write path from `Aerospike.operations.append`/`Aerospike.operations.prepend` to `strings.append`/`strings.prepend` starts UTF-8 validation on that bin; the legacy byte-concatenation path never validated UTF-8 at all.

::: migration can cause ongoing write failures
If existing data written through the legacy byte-concatenation path contains invalid UTF-8, every future `strings.append`/`strings.prepend` call against that bin fails with `Aerospike.status.ERR_OP_NOT_APPLICABLE` (26) and `Aerospike.subcode.OPNOT_STRING_UTF8_INVALID` (11) instead of succeeding as before. This isn’t a one-time failure: it repeats on every call until the bin’s content is repaired, so a hot code path can break continuously as soon as the change deploys.
:::

Before switching write paths in production:

1.  Audit affected bins for valid UTF-8 and repair any that fail (see [Repair legacy String bins](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#repair-legacy-string-bins)).
2.  Roll out the switch per namespace or set, not all at once.
3.  Monitor for the `ERR_OP_NOT_APPLICABLE`/`OPNOT_STRING_UTF8_INVALID` pair after each rollout step.
4.  If it appears in production, revert that bin’s write path to the legacy `Aerospike.operations.append`/`Aerospike.operations.prepend` call while you complete the repair.

## Code block

Expand this section for a single code block combining round-trip elimination, a modify operation with flags, and reading a boolean result.

```js
const Aerospike = await import("aerospike");

const strings = Aerospike.strings;

// Set hosts to your server's address and port

const config = { hosts: "YOUR_HOST_ADDRESS:YOUR_PORT" };

// Creates a key with the namespace "sandbox", set "users", and user key "jdoe123"

const key = new Aerospike.Key("sandbox", "users", "jdoe123");

// Establishes a connection to the server

const client = await Aerospike.connect(config);

await client.put(key, { email: "  Jane.Doe@Example.com  " });

// Normalize the email in one round trip instead of get/edit/put

const ops = [

    strings.trim("email"),

    strings.lower("email").withPolicy({ writeFlags: strings.writeFlags.NO_FAIL }),

];

await client.operate(key, ops);

// A single operation on a bin returns its value directly (no array wrapper)

const record = await client.operate(key, [strings.contains("email", "@")]);

console.info("Contains '@':", record.bins.email); // true

await client.close();
```

## Next steps

-   [Bin operations](https://aerospike.com/docs/develop/client/node/usage/atomic/multi) for the general `operate()` and result-grouping model String operations build on.
-   [Expressions](https://aerospike.com/docs/develop/client/node/usage/atomic/expressions) for filter expressions and operation expressions beyond `Aerospike.exp.string`.
-   [String operations reference](https://aerospike.com/docs/develop/data-types/string/operations) and [String expressions reference](https://aerospike.com/docs/develop/expressions/string) for full operation semantics, argument details, and error subcodes.
-   [String](https://aerospike.com/docs/develop/data-types/string) for the underlying data type and Unicode model.
-   [Error handling](https://aerospike.com/docs/develop/client/node/error-handling) for the `AerospikeError` shape and status codes used throughout this page.