diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index e0604b043361828f94b58f676a5ed4f15b116348..2bd8118852a97251fc1081d19ea9756428d190f4 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -428,16 +428,21 @@ $ git worktree list
 
 For these annotations, a reason might also be available and this can be
 seen using the verbose mode. The annotation is then moved to the next line
-indented followed by the additional information.
+indented followed by the additional information. In verbose mode, the worktree
+id is also shown on the next line if available.
 
 ------------
 $ git worktree list --verbose
 /path/to/linked-worktree              abcd1234 [master]
+	id: linked-worktree-12345678
 /path/to/locked-worktree-no-reason    abcd5678 (detached HEAD) locked
+	id: locked-worktree-no-reason-89765464
 /path/to/locked-worktree-with-reason  1234abcd (brancha)
 	locked: worktree path is mounted on a portable device
+	id: locked-worktree-with-reason-41564654
 /path/to/prunable-worktree            5678abc1 (detached HEAD)
 	prunable: gitdir file points to non-existent location
+	id: prunable-worktree-98454651
 ------------
 
 Note that the annotation is moved to the next line if the additional
@@ -461,24 +466,29 @@ worktree /path/to/bare-source
 bare
 
 worktree /path/to/linked-worktree
+id linked-worktree-12345678
 HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
 branch refs/heads/master
 
 worktree /path/to/other-linked-worktree
+id other-linked-worktree-879456466
 HEAD 1234abc1234abc1234abc1234abc1234abc1234a
 detached
 
 worktree /path/to/linked-worktree-locked-no-reason
+id locked-worktree-no-reason-89765464
 HEAD 5678abc5678abc5678abc5678abc5678abc5678c
 branch refs/heads/locked-no-reason
 locked
 
 worktree /path/to/linked-worktree-locked-with-reason
+id locked-worktree-with-reason-41564654
 HEAD 3456def3456def3456def3456def3456def3456b
 branch refs/heads/locked-with-reason
 locked reason why is locked
 
 worktree /path/to/linked-worktree-prunable
+id prunable-worktree-98454651
 HEAD 1233def1234def1234def1234def1234def1234b
 detached
 prunable gitdir file points to non-existent location
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 36235546b492803707707ff208b13fe777bff1b4..fca8a9cda51643e434b5f8905e32e537c6b4418b 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -944,6 +944,8 @@ static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
 	const char *reason;
 
 	printf("worktree %s%c", wt->path, line_terminator);
+	if (!is_main_worktree(wt))
+		printf("id %s%c", wt->id, line_terminator);
 	if (wt->is_bare)
 		printf("bare%c", line_terminator);
 	else {
@@ -1009,6 +1011,9 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
 	else if (reason)
 		strbuf_addstr(&sb, " prunable");
 
+	if (verbose && !is_main_worktree(wt))
+		strbuf_addf(&sb, "\n\tid: %s", wt->id);
+
 	printf("%s\n", sb.buf);
 	strbuf_release(&sb);
 }
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index 780daa6cd6351f8fa9434619cc212aade8f01420..502d35cabb837121d178937673b580cd767d17aa 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -56,8 +56,9 @@ test_expect_success '"list" all worktrees --porcelain' '
 	echo "branch $(git symbolic-ref HEAD)" >>expect &&
 	echo >>expect &&
 	test_when_finished "rm -rf here actual expect && git worktree prune" &&
-	git worktree add --detach here main &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add --detach here main &&
 	echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
+	echo "id here-123" >>expect &&
 	echo "HEAD $(git rev-parse HEAD)" >>expect &&
 	echo "detached" >>expect &&
 	echo >>expect &&
@@ -71,9 +72,10 @@ test_expect_success '"list" all worktrees --porcelain -z' '
 	printf "worktree %sQHEAD %sQbranch %sQQ" \
 		"$(git rev-parse --show-toplevel)" \
 		$(git rev-parse HEAD --symbolic-full-name HEAD) >expect &&
-	git worktree add --detach here main &&
-	printf "worktree %sQHEAD %sQdetachedQQ" \
+	GIT_TEST_WORKTREE_SUFFIX=456 git worktree add --detach here main &&
+	printf "worktree %sQid %sQHEAD %sQdetachedQQ" \
 		"$(git -C here rev-parse --show-toplevel)" \
+		"here-456" \
 		"$(git rev-parse HEAD)" >>expect &&
 	git worktree list --porcelain -z >_actual &&
 	nul_to_q <_actual >actual &&
@@ -166,16 +168,17 @@ test_expect_success '"list" --verbose and --porcelain mutually exclusive' '
 test_expect_success '"list" all worktrees --verbose with locked' '
 	test_when_finished "rm -rf locked1 locked2 out actual expect && git worktree prune" &&
 	git worktree add locked1 --detach &&
-	git worktree add locked2 --detach &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git worktree add locked2 --detach &&
 	git worktree lock locked1 &&
 	test_when_finished "git worktree unlock locked1" &&
 	git worktree lock locked2 --reason "with reason" &&
 	test_when_finished "git worktree unlock locked2" &&
 	echo "$(git -C locked2 rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&
 	printf "\tlocked: with reason\n" >>expect &&
+	printf "\tid: locked2-456\n" >>expect &&
 	git worktree list --verbose >out &&
 	grep "/locked1  *[0-9a-f].* locked$" out &&
-	sed -n "s/  */ /g;/\/locked2  *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&
+	sed -n "s/  */ /g;/\/locked2  *[0-9a-f].*$/,/id: .*$/p" <out >actual &&
 	test_cmp actual expect
 '
 
@@ -211,11 +214,12 @@ test_expect_success '"list" all worktrees from bare main' '
 
 test_expect_success '"list" all worktrees --porcelain from bare main' '
 	test_when_finished "rm -rf there actual expect && git -C bare1 worktree prune" &&
-	git -C bare1 worktree add --detach ../there main &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git -C bare1 worktree add --detach ../there main &&
 	echo "worktree $(pwd)/bare1" >expect &&
 	echo "bare" >>expect &&
 	echo >>expect &&
 	echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect &&
+	echo "id there-456" >>expect &&
 	echo "HEAD $(git -C there rev-parse HEAD)" >>expect &&
 	echo "detached" >>expect &&
 	echo >>expect &&
