String functions — Marketing Cloud AMPscript reference
The string operations available in AMPscript — every function with the inverted indexing (1-based, not 0-based), the replace-all-by-default behavior, and the small set of formatting helpers. Where it diverges from SSJS and SQL, plus the patterns Cleon ships.
AMPscript string handling is all functions, no operators — there is no + for concatenation, no .length property access, no method dot-syntax. Every string operation is a function call: Concat(@a, @b), Length(@s), Substring(@s, 1, 5). The function surface is small (around 20 string operations), but a few default behaviors are inverted from what JavaScript or SQL programmers expect: Substring and IndexOf are 1-based (not 0-based), and Replace replaces all occurrences by default (not just the first). The list below is the inventory plus the patterns that survive at scale.
Official syntax
%%[
VAR @email, @firstName, @cleaned, @greeting
SET @email = AttributeValue("EmailAddress")
SET @firstName = AttributeValue("FirstName")
/* Length, character access */
SET @len = Length(@email) /* character count, not bytes */
/* Search — 1-based, returns 0 when not found */
SET @atPos = IndexOf(@email, "@") /* e.g. 14 if email[14] is "@" */
/* Slice — 1-based start, length-based (not end-exclusive) */
SET @user = Substring(@email, 1, IndexOf(@email, "@") - 1)
/* For "ana@example.com": Substring(s, 1, 3) → "ana" */
/* Case */
SET @lowered = Lowercase(@email)
SET @upped = Uppercase(@firstName)
SET @proper = ProperCase(@firstName) /* "JOHN" → "John" */
/* Trim whitespace (both ends) */
SET @cleaned = Trim(@firstName)
/* Replace — replaces ALL occurrences by default */
SET @noDots = Replace(@email, ".", "_") /* every dot becomes underscore */
SET @oneShot = Replace(@email, ".", "_", 1) /* optional 4th arg: max replacements */
/* Concat — variadic, takes 2+ args */
SET @greeting = Concat("Hello, ", @firstName, "!")
/* Multi-value replace */
SET @safe = ReplaceList(@firstName, "_", "@", "%")
/* replaces any of {@, %} with _ */
]%%
<p>%%=v(@greeting)=%%</p>Formatting helpers:
%%[
/* Format — printf-style with positional args. {0}, {1}, ... */
SET @msg = Format("Subscriber {0} moved to tier {1}", @subKey, @tier)
/* AMPscript Format takes variadic args, NOT an array (unlike SSJS) */
/* FormatNumber — locale-aware number formatting */
SET @priceStr = FormatNumber(@price, "C", "es-AR")
/* 1234.5 with "C" / "es-AR" → "$ 1.234,50" */
/* FormatDate — locale-aware date formatting */
SET @today = FormatDate(Now(), "yyyy-MM-dd")
/* → "2026-05-13" */
/* AsciiToChar / Char — character from code point */
SET @nl = Char(10) /* newline */
SET @amp = AsciiToChar(38) /* "&" */
]%%The supported set:
| Function | What it does | Notes |
|---|---|---|
| Length(s) | Character count | Not byte count — Unicode-safe |
| IndexOf(s, needle) | Position of first match | 1-based, returns 0 when not found |
| Substring(s, start, length) | Substring starting at start | 1-based; third arg is length, not end index |
| Lowercase(s) / Uppercase(s) | Case fold | |
| ProperCase(s) | Title case the string | "JOHN SMITH" → "John Smith" |
| Trim(s) | Strip leading + trailing whitespace | No LTrim / RTrim separately |
| Replace(s, find, replace [, count]) | Replace occurrences | Replaces ALL by default; optional 4th arg caps the count |
| ReplaceList(s, replacement, find1, find2, ...) | Replace multiple needles with one replacement | Variadic |
| Concat(s1, s2, ...) | Concatenate strings | Variadic; no + operator |
| Char(code) | Character from int code | Useful for newlines, special chars |
| AsciiToChar(code) | Character from ASCII code | Same as Char for ASCII range |
| Format(template, arg1, arg2, ...) | Printf-style with {0}, {1} placeholders | Variadic args (unlike SSJS which takes an array) |
| FormatNumber(n, fmt, locale) | Locale-aware number format | "C" (currency), "N2", "P", etc. |
| FormatDate(d, fmt [, locale]) | Locale-aware date format | Returns string |
| RegExMatch(s, pattern, group) | Regex match — limited surface | Not available in all tenants |
| StringToDate(s) | Parse string to date | Loose parsing rules |
| StringToHex(s) | String to hex representation | Useful for tracking pixel encodings |
Reference:
What survives in production
Substring and IndexOf are 1-based — the off-by-one bug that ships
JavaScript, Python, and Go developers reach for Substring(@s, 0, 3) and get a confusing result. In AMPscript:
Substring(@s, 1, 3)returns the first three characters.Substring(@s, 0, 3)returns an empty string or behaves undefined depending on the tenant — never the first three.
%%[
SET @s = "Mariana"
/* WRONG — silently returns "" or "Mar" depending on tenant version */
SET @firstThree = Substring(@s, 0, 3)
/* CORRECT — 1-based start, length-based count */
SET @firstThree = Substring(@s, 1, 3) /* → "Mar" */
/* "Get everything from position 5 onward" */
SET @tail = Substring(@s, 5, Length(@s))
/* → "ana" (extra length is fine, MC truncates to end of string) */
]%%IndexOf follows the same convention — 1-based when found, 0 when not found:
%%[
SET @email = "ana@example.com"
SET @atPos = IndexOf(@email, "@") /* → 4 (1-based) */
SET @nope = IndexOf(@email, "?") /* → 0 (not found) */
/* Conditional logic — note the comparison */
IF IndexOf(@email, "@") > 0 THEN
/* found — same as "> 0" check in SQL's CHARINDEX */
ENDIF
]%%This is the inverse of the SSJS / JavaScript indexOf, which returns -1 when not found and 0 when found at position 0. The hand-off failure: a dev copies a JS string-parsing pattern, swaps the function names to AMPscript equivalents, and silently breaks the conditional. See gotchas — #7.
Replace replaces all occurrences by default
The opposite of JavaScript and SSJS, where .replace("x", "y") replaces only the first match unless you pass a regex with /g. AMPscript replaces everything by default — pass a 4th argument to cap the count.
%%[
SET @s = "a,b,c,d"
/* Replaces ALL commas — usually what you want */
SET @piped = Replace(@s, ",", "|") /* → "a|b|c|d" */
/* Replace only the first comma */
SET @oneShot = Replace(@s, ",", "|", 1) /* → "a|b,c,d" */
]%%The hand-off failure runs the other way: a dev coming from JS adds Replace(@s, ",", "|") thinking it only handles the first match, then is surprised when output is correct. Less harmful than the reverse direction, but worth knowing for code review.
Concat is variadic — no + operator
There is no + for strings. Use Concat for everything.
%%[
SET @firstName = "Mariana"
/* WRONG — AMPscript doesn't parse + as string concat */
SET @greeting = "Hello, " + @firstName + "!"
/* CORRECT — Concat handles 2+ args */
SET @greeting = Concat("Hello, ", @firstName, "!")
]%%For 4+ pieces, Concat reads better than nested calls. The Cleon pattern: build the string in one Concat call when readable, fall back to a sequence of SET @x = Concat(@x, ...) when conditions need to branch the build:
%%[
SET @line = Concat("Order #", @orderId, " — ", @itemName)
IF NOT Empty(@discount) THEN
SET @line = Concat(@line, " (", @discount, "% off)")
ENDIF
]%%Trim exists — but no LTrim / RTrim separately
AMPscript ships Trim which strips both ends. There's no built-in for trimming only one side. Use Substring with IndexOf if you need one-side-only behavior — it's rare enough that the verbose form is fine.
%%[
/* Trim both ends — common case */
SET @clean = Trim(@firstName)
/* Trim only the right side (rare) — no built-in */
/* If your input has trailing whitespace from a data import,
fix it in the SQL Activity upstream where TRIM functions exist. */
]%%Empty(Concat(...)) is true if any arg is NULL
Concat with a NULL arg produces a string with the NULL coerced to empty — the result is still a string, but visually the concatenation has a hole. Worse, if all args are NULL, the result is the empty string and Empty() returns true.
%%[
SET @firstName = AttributeValue("FirstName") /* may be NULL */
SET @greeting = Concat("Hello, ", @firstName, "!")
/* If @firstName is NULL → "Hello, !" — visually broken */
/* DURABLE — default the var before concat */
IF Empty(@firstName) THEN
SET @firstName = "Friend"
ENDIF
SET @greeting = Concat("Hello, ", @firstName, "!")
]%%This is the AMPscript flavor of the SSJS "+ coerces NULL to literal 'null'" failure — same shape, slightly different output. The defense is the same: default every variable that feeds a concatenation, every time.
Format takes variadic args, not an array
A small but important difference from SSJS where Platform.Function.Format takes an array. In AMPscript, the args are positional after the template.
%%[
/* AMPscript form — variadic */
SET @msg = Format("User {0} ({1}) in tier {2}", @user, @email, @tier)
/* SSJS form for comparison — args as array */
/* SSJS: Platform.Function.Format("User {0} ({1})", [user, email]) */
]%%Up to ~10 positional args is fine; beyond that you're better off building the string with Concat and named variables for readability.
FormatNumber and FormatDate are locale-aware — preview lies about locale
The third arg to FormatNumber and FormatDate is a locale string ("en-US", "es-AR", etc.). The locale affects decimal separators, currency symbols, date formats.
%%[
SET @price = 1234.5
SET @us = FormatNumber(@price, "C", "en-US") /* → "$1,234.50" */
SET @ar = FormatNumber(@price, "C", "es-AR") /* → "$ 1.234,50" */
SET @today = Now()
SET @usDate = FormatDate(@today, "MM/dd/yyyy") /* → "05/13/2026" */
SET @arDate = FormatDate(@today, "dd/MM/yyyy") /* → "13/05/2026" */
]%%The trap: Email Studio's personalization preview can use a different locale than the production send. A preview that renders $1,234.50 may send $ 1.234,50 for a recipient in Argentina, or vice versa. Test sends to a real subscriber in the target locale before assuming the format is right. See gotchas — #9.
Quick decision
Use Substring when:
- Extracting a fixed-position portion of a string (first N chars, last N chars, slice between two known indices).
Use IndexOf then Substring when:
- The slice depends on finding a delimiter — split-at-
@for emails, etc. No nativeSplit()in AMPscript.
Use Replace when:
- Substituting a single value across the string. Default behavior (replace-all) is usually what you want; cap with the 4th arg only when needed.
Use Concat when:
- Building any string from 2+ parts. There is no
+alternative.
Use Format when:
- Building a string with 2+ interpolated values that share a single template.
Use FormatNumber / FormatDate when:
- Output goes to a subscriber and the locale matters. Always test in the target locale.
Do the work in SQL instead when:
- You need regex beyond simple matching. Pre-shape upstream.
- You need to split on a delimiter and access individual parts. Pre-compute in SQL.
- The same transformation runs per-recipient on a large send. One SQL run beats N AMPscript runs.
Related
- Basics — supported language surface
- MC AMPscript gotchas — see #7 (1-based indexing) and #8 (
EmptyvsIsNullvs== "") - MC SSJS — String functions — sibling surface; same problem space, different conventions (0-based, dot-syntax,
+operator) - MC SQL — String functions — the upstream regex / split / aggregate string work that should leave AMPscript free to just interpolate
More AMPscript reference pages incoming: Date · Math · Validation · Data Extension · Subscriber/Profile · Cloud-write · Encoding/Hashing function categories · Style Guide.