Power Automate substring() Function: Syntax, Examples and Errors

Say you’re automating web orders for a Chicago outdoor store, and every order ID looks like ORD-2026-10-4471-CHI. You need the year, the month, the order number and the store code in separate columns, and the substring() expression pulls each piece out by position.

In this tutorial, I’ll show you how to use the power automate substring function with fixed positions, with indexOf() and lastIndexOf() when the positions move, and how to stop the out-of-range error that breaks so many flows.

I ran every expression below in a real cloud flow, so the results you see are the actual outputs.

The sample order ID we’ll use

My test flow, ESP Substring Demo, starts with a manual trigger and one Initialize variable action named OrderRef (type String). Every example reads that same value.

  • ORD at positions 0-2 (the prefix)
  • 2026 at positions 4-7 (the year)
  • 10 at positions 9-10 (the month)
  • 4471 at positions 12-15 (the order number)
  • CHI at positions 17-19 (the store code)

The whole string is 20 characters long, with dashes at positions 3, 8, 11 and 16. That length matters, because substring() fails the moment you ask for a character past position 19.

Initialize variable action holding the sample order ID in Power Automate

How substring() works

Here’s the syntax:

substring(text, startIndex, length)
  • text (required): the string you want a piece of.
  • startIndex (required): where to start. Counting starts at 0, so the first character is position 0.
  • length (optional): how many characters to take. Leave it out to take everything to the end.

The result is always a string, even when it looks like a number. Wrap it in int() if you need to do math with it.

In the new designer, select the input box, choose fx, type the expression without the leading @, and select Add (Update when editing). If expressions are new to you, this list of Power Automate string functions is a good warm-up.

substring expression typed in the Power Automate fx expression editor of a Compose action

Method 1: Extract text at a fixed position (Recommended)

Use this when the format never changes, like our order IDs, a ZIP+4 code or a fixed-width date. Same OrderRef variable as before: the year starts at position 4 and runs 4 characters.

Here’s the expression:

substring(variables('OrderRef'), 4, 4)

That returns 2026. Change it to substring(variables('OrderRef'), 9, 2) and you get the month, 10. The run history shows both in quotes, "2026" and "10", because the value is still text.

Compose output showing 2026 extracted with the Power Automate substring function

How does this work?

Position 4 is the “2” right after the first dash. From there, substring() counts 4 characters: 2, 0, 2, 6. The month works the same way, starting at position 9 and taking 2 characters.

Here’s the Code view of that Compose action, if you’d rather paste it:

{
  "type": "Compose",
  "inputs": "@substring(variables('OrderRef'), 4, 4)",
  "runAfter": {
    "Initialize_OrderRef": ["Succeeded"]
  }
}

Method 2: Leave out the length to take everything to the end

When you want the rest of the string, skip the third argument. Our store code starts at position 17 and is the last thing in the order ID.

substring(variables('OrderRef'), 17)

That returns CHI. Since there’s no length, there’s nothing that can run past the end, so this form is safer than guessing a length.

substring without the length argument returning the store code CHI in a Power Automate run

Need only the first or last character? There’s a shorter route in this guide to get the first character from a string.

Method 3: Find the start position with indexOf()

Real data isn’t always fixed width. Order prefixes might be ORD today and WEB or RETURN tomorrow. The indexOf() and lastIndexOf() functions find a character for you and return its position, so the expression adjusts on its own.

To get the text before the first dash, use the dash position as the length:

substring(variables('OrderRef'), 0, indexOf(variables('OrderRef'), '-'))

That returns ORD. To get everything after the first dash, start one character past it and leave out the length:

substring(variables('OrderRef'), add(indexOf(variables('OrderRef'), '-'), 1))

That returns 2026-10-4471-CHI.

Power Automate run outputs for substring with indexOf before and after the first dash

How does this work?

indexOf(variables('OrderRef'), '-') returns 3, the position of the first dash. In the first expression, 3 becomes the length, so you get positions 0, 1 and 2.

In the second, add(3, 1) moves the start to position 4, just past the dash. This is the same trick behind extracting a string after a character.

Important: indexOf() isn’t case-sensitive, and it returns -1 when the character isn’t there. I’ll show you what -1 does to substring() in a minute.

Method 4: Get the last N characters with length()

When you know how many characters you want from the end, subtract them from the total. The length() function counts the characters, which is 20 for our OrderRef.

substring(variables('OrderRef'), sub(length(variables('OrderRef')), 3))

That returns CHI. If the last part can vary in size, use lastIndexOf() instead and take everything after the last dash:

substring(variables('OrderRef'), add(lastIndexOf(variables('OrderRef'), '-'), 1))

That also returns CHI, and it still works if a store code like NYC2 ever shows up.

substring with length and lastIndexOf returning the last characters of the order ID in Power Automate

How does this work?

sub(20, 3) gives 17, the position where CHI starts. lastIndexOf(..., '-') returns 16, the last dash, and adding 1 lands on 17 again.

Method 5: Grab a middle piece with nthIndexOf()

The order number sits after the third dash. nthIndexOf() finds the nth occurrence of a character, so you don’t have to chain several indexOf() calls.

substring(variables('OrderRef'), add(nthIndexOf(variables('OrderRef'), '-', 3), 1), 4)

That returns 4471 (shown as "4471" in the run, because it’s text).

substring combined with nthIndexOf returning the order number 4471 in Power Automate

How does this work?

nthIndexOf(variables('OrderRef'), '-', 3) returns 11, the third dash. Adding 1 starts at position 12, and a length of 4 takes 4, 4, 7, 1.

If you need every piece at once, the split() function is simpler: split(variables('OrderRef'), '-') gives an array, and split(variables('OrderRef'), '-')[3] returns the order number.

Pro Tip: If the text comes from a SharePoint list column instead of a variable, swap variables('OrderRef') for the dynamic value, such as triggerOutputs()?['body/Title']. The rest of the expression stays the same.

Fix the substring() out-of-range error

If startIndex plus length goes past the end of the string, substring() doesn’t trim the result like Excel’s MID does. The action fails.

substring(variables('OrderRef'), 17, 5)

Our string has only 3 characters after position 17, so the Compose action fails with this message:

InvalidTemplate. The template language function 'substring' parameters are out of range: 'start index' and 'length' must be non-negative integers and their sum must be no larger than the length of the string.
Power Automate substring error when the start index plus length is longer than the string

Asking for exactly the rest is fine: substring(variables('OrderRef'), 0, 20) returns the full 20-character ID. So the real rule is that start plus length can’t be larger than the length, which matches the error text above.

To cap the length so it never runs past the end, use min():

substring(variables('OrderRef'), 17, min(5, sub(length(variables('OrderRef')), 17)))

That returns CHI instead of an error.

How does this work?

sub(20, 17) is 3, the characters actually left. min(5, 3) picks 3, so the length can never be bigger than what’s there.

Handle a missing delimiter, empty text and null

Same OrderRef variable, but now we look for a # that isn’t in it. indexOf() returns -1, and a length of -1 fails the action with the same out-of-range error:

substring(variables('OrderRef'), 0, indexOf(variables('OrderRef'), '#'))

Wrap it in an if() expression so the flow checks first and falls back to the whole value:

if(greater(indexOf(variables('OrderRef'), '#'), -1), substring(variables('OrderRef'), 0, indexOf(variables('OrderRef'), '#')), variables('OrderRef'))

That returns ORD-2026-10-4471-CHI, and the run keeps going.

Power Automate run showing the failed substring and the guarded if expression for a missing character

Here’s what the other edge cases returned in my run:

ExpressionResult
substring(variables('OrderRef'), 25)Fails: start index out of range
substring(variables('OrderRef'), -3)Fails: start index out of range
substring('', 0)Fails: start index out of range
substring(null, 0, 3)Fails: expects a string, got Null
substring(coalesce(null, ''), 0)Still fails: ” has no position 0

The start-index errors read: The template language function 'substring' parameter is out of range: 'start index' must be non-negative integer and should be less than the length of the string.

So here’s the catch: coalesce() alone doesn’t save you. It turns null into an empty string, and an empty string has no position 0, so substring() still fails. Check for empty text first:

if(empty(triggerOutputs()?['body/Title']), '', substring(triggerOutputs()?['body/Title'], 0, 3))

I tested the pattern in a second flow: if(empty(null), 'no value', substring(null, 0, 3)) returned no value, and if(empty(''), '', substring('', 0)) returned an empty string. if() only runs the branch it picks, so the failing substring() never fires.

substring() vs slice()

The slice() function also returns part of a string, but it takes an end position instead of a length. It’s also more forgiving.

Same OrderRefsubstring()slice()
Yearsubstring(variables('OrderRef'), 4, 4)slice(variables('OrderRef'), 4, 8) returns 2026
Last 3 characterssubstring(..., sub(length(...), 3))slice(variables('OrderRef'), -3) returns CHI
Ask past the endErrorslice(variables('OrderRef'), 17, 50) returns CHI
slice expressions returning the year and store code from the same order ID in Power Automate

For the first few characters, take(variables('OrderRef'), 3) returns ORD with no index math at all.

Pro Tip: I still use substring() when the length is fixed, because the expression reads clearly. When the end might move, I reach for slice().

Which method should you use?

You want toUse
Pull a piece from a fixed-width codeMethod 1: substring(text, start, length)
Take everything from a position onMethod 2: leave out the length
Get text before or after a characterMethod 3: indexOf()
Get the last N charactersMethod 4: length() or lastIndexOf()
Get a piece between two delimitersMethod 5: nthIndexOf() or split()
Avoid errors on short or messy textslice(), or min() and if() guards

For cleanup jobs rather than extraction, like stripping dashes or spaces, see how to remove characters from a string in Power Automate.

Things to Keep in Mind

  • Positions start at 0: the first character is position 0, not 1. Count before you type.
  • Too long fails: start plus length past the end stops the flow. Cap it with min() or use slice().
  • -1 from indexOf(): a missing character turns into -1 and breaks substring(). Check with if() first.
  • Null or empty input: both fail, even after coalesce(). Wrap the call in if(empty(…), ”, substring(…)).
  • No @ in the fx editor: type the expression without it; the designer adds the @ you see in Code view.

If you work with text in flows a lot, these will help too:

Frequently Asked Questions

Is the substring() index in Power Automate zero-based?

Yes. The first character is at position 0, so substring(‘ORD-2026’, 0, 3) returns ORD.

Is the length argument required in substring()?

No. If you leave it out, substring() returns every character from the start position to the end of the string.

Why does my substring() expression fail?

Usually the start position plus the length is longer than the text, or indexOf() returned -1. Microsoft lists the rules in the substring() function reference.

How do I get the last 4 characters of a string in Power Automate?

Use substring(text, sub(length(text), 4)), or the shorter slice(text, -4).

Does Power Automate Desktop use the same substring() function?

No. Desktop flows use the Get subtext and Crop text actions instead of the cloud flow expression.