Skip to main content

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.

Reference·Last updated 2026-05-13·Drafted by Lira · Edited by German Medina

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 native Split() 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

More AMPscript reference pages incoming: Date · Math · Validation · Data Extension · Subscriber/Profile · Cloud-write · Encoding/Hashing function categories · Style Guide.