diff --git a/django/utils/html.py b/django/utils/html.py
index 22d3ae42fa..a4be3cf8e6 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -11,6 +11,7 @@ from django.utils.deprecation import RemovedInDjango60Warning
 from django.utils.encoding import punycode
 from django.utils.functional import Promise, keep_lazy, keep_lazy_text
 from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
+from django.utils.markdown import find_closing_markdown_bracket, has_markdown_link
 from django.utils.regex_helper import _lazy_re_compile
 from django.utils.safestring import SafeData, SafeString, mark_safe
 from django.utils.text import normalize_newlines
@@ -278,6 +279,7 @@ class Urlizer:
 
     mailto_template = "mailto:{local}@{domain}"
     url_template = '{url}'
+    markdown_url_template = '[{text}]({trimmed_url})'
 
     def __call__(self, text, trim_url_limit=None, nofollow=False, autoescape=False):
         """
@@ -291,7 +293,17 @@ class Urlizer:
         """
         safe_input = isinstance(text, SafeData)
 
-        words = self.word_split_re.split(str(text))
+        text = str(text)
+        if has_markdown_link(text):
+            return self.handle_markdown_link(
+                text,
+                safe_input=safe_input,
+                trim_url_limit=trim_url_limit,
+                nofollow=nofollow,
+                autoescape=autoescape,
+            )
+
+        words = self.word_split_re.split(text)
         return "".join(
             [
                 self.handle_word(
@@ -305,6 +317,79 @@ class Urlizer:
             ]
         )
 
+    def handle_markdown_link(
+        self,
+        text,
+        *,
+        safe_input,
+        trim_url_limit=None,
+        nofollow=False,
+        autoescape=False,
+    ):
+        nofollow_attr = ' rel="nofollow"' if nofollow else ""
+
+        def find_and_replace_link(text):
+            i = 0
+            result = []
+            while i < len(text):
+                if text[i] == "\\":
+                    result.append(text[i : i + 2])
+                    i += 2
+                    continue
+                if text[i] == "[":
+                    start = i
+                    close_bracket = find_closing_markdown_bracket(text, i + 1)
+                    if (
+                        close_bracket != -1
+                        and close_bracket + 1 < len(text)
+                        and text[close_bracket + 1] == "("
+                    ):
+                        j = close_bracket + 2
+                        paren_depth = 1
+                        while j < len(text):
+                            if text[j] == "\\":
+                                j += 2
+                                continue
+                            if text[j] == "(":
+                                paren_depth += 1
+                            elif text[j] == ")":
+                                paren_depth -= 1
+                                if paren_depth == 0:
+                                    link_text = text[start + 1 : close_bracket]
+                                    link_url = text[close_bracket + 2 : j]
+                                    trimmed_url = self.trim_url(
+                                        link_url, limit=trim_url_limit
+                                    )
+
+                                    if autoescape and not safe_input:
+                                        link_text = escape(link_text)
+                                        link_url = escape(link_url)
+                                        trimmed_url = escape(trimmed_url)
+
+                                    result.append(
+                                        self.markdown_url_template.format(
+                                            text=link_text,
+                                            url=link_url,
+                                            attrs=nofollow_attr,
+                                            trimmed_url=trimmed_url,
+                                        )
+                                    )
+                                    i = j + 1
+                                    break
+                            j += 1
+                        else:
+                            result.append(text[i])
+                            i += 1
+                    else:
+                        result.append(text[i])
+                        i = close_bracket + 1 if close_bracket != -1 else i + 1
+                else:
+                    result.append(text[i])
+                    i += 1
+            return "".join(result)
+
+        return find_and_replace_link(text)
+
     def handle_word(
         self,
         word,
diff --git a/django/utils/markdown.py b/django/utils/markdown.py
new file mode 100644
index 0000000000..c0213ed47a
--- /dev/null
+++ b/django/utils/markdown.py
@@ -0,0 +1,67 @@
+def find_closing_markdown_bracket(text, start):
+    """
+    Find the closing bracket corresponding to the opening bracket.
+    """
+    depth = 0
+    i = start
+    while i < len(text):
+        if text[i] == "\\":
+            i += 2
+            continue
+        if text[i] == "[":
+            depth += 1
+        elif text[i] == "]":
+            if depth == 0:
+                return i
+            depth -= 1
+        i += 1
+    return -1
+
+
+def has_markdown_link(text):
+    """
+    Check if the given text contains any Markdown links.
+    """
+
+    def is_valid_url(start, end):
+        """
+        Check if the URL is valid.
+        """
+        url = text[start:end].strip()
+        return (
+            url.startswith("http://")
+            or url.startswith("https://")
+            or any(c.isalnum() for c in url)
+        )
+
+    i = 0
+    while i < len(text):
+        if text[i] == "\\":
+            i += 2
+            continue
+        if text[i] == "[":
+            close_bracket = find_closing_markdown_bracket(text, i + 1)
+            if (
+                close_bracket != -1
+                and close_bracket + 1 < len(text)
+                and text[close_bracket + 1] == "("
+            ):
+                j = close_bracket + 2
+                paren_depth = 1
+                while j < len(text):
+                    if text[j] == "\\":
+                        j += 2
+                        continue
+                    if text[j] == "(":
+                        paren_depth += 1
+                    elif text[j] == ")":
+                        paren_depth -= 1
+                        if paren_depth == 0:
+                            if is_valid_url(close_bracket + 2, j):
+                                return True
+                            break
+                    j += 1
+            i = close_bracket + 1 if close_bracket != -1 else i + 1
+        else:
+            i += 1
+    return False
diff --git a/tests/template_tests/filter_tests/test_urlize.py b/tests/template_tests/filter_tests/test_urlize.py
index 8f84e62c92..1b377883f9 100644
--- a/tests/template_tests/filter_tests/test_urlize.py
+++ b/tests/template_tests/filter_tests/test_urlize.py
@@ -320,8 +320,8 @@ class FunctionTests(SimpleTestCase):
         )
         self.assertEqual(
             urlize("[http://168.192.0.1](http://168.192.0.1)"),
-            '['
-            "http://168.192.0.1](http://168.192.0.1)",
+            '[http://168.192.0.1]('
+            "http://168.192.0.1)",
         )
 
     def test_wrapping_characters(self):
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index ad31b8cc5b..ecb9bc2ea2 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -17,6 +17,7 @@ from django.utils.html import (
     strip_spaces_between_tags,
     strip_tags,
     urlize,
+    urlizer,
 )
 from django.utils.safestring import mark_safe
 
@@ -356,3 +357,78 @@ class TestUtilsHtml(SimpleTestCase):
         for value in tests:
             with self.subTest(value=value):
                 self.assertEqual(urlize(value), value)
+
+    def test_handle_markdown_link(self):
+        tests = [
+            {
+                "input": "Here's a [link with [nested] brackets](https://example.com)",
+                "expected": "Here's a [link with [nested] brackets](https://example.com)',
+                "params": {
+                    "trim_url_limit": None,
+                    "nofollow": False,
+                    "autoescape": False,
+                },
+            },
+            {
+                "input": "Check out [this link](https://example.com/page(1))",
+                "expected": 'Check out [this link](https://example.com/page(1))',
+                "params": {
+                    "trim_url_limit": None,
+                    "nofollow": False,
+                    "autoescape": False,
+                },
+            },
+            {
+                "input": "Here's a [complex URL](https://example.com/"
+                "path?param1=value1¶m2=value2#fragment)",
+                "expected": "Here's a [complex URL]('
+                "https://example.com/path?param1=value1&"
+                "param2=value2#fragment)",
+                "params": {
+                    "trim_url_limit": None,
+                    "nofollow": False,
+                    "autoescape": True,
+                },
+            },
+            {
+                "input": "Multiple [link1](https://example1.com) and "
+                "[link2](https://example2.com)",
+                "expected": 'Multiple [link1]('
+                "https://example1.com) and [link2]"
+                '(https://example2.com)',
+                "params": {
+                    "trim_url_limit": None,
+                    "nofollow": False,
+                    "autoescape": False,
+                },
+            },
+            {
+                "input": "This is a [broken link(https://example.com)",
+                "expected": "This is a [broken link(https://example.com)",
+                "params": {
+                    "trim_url_limit": None,
+                    "nofollow": False,
+                    "autoescape": False,
+                },
+            },
+            {
+                "input": "Here's a [very long URL](https://example.com/"
+                + "x" * 100
+                + ")",
+                "expected": "Here's a [very long URL](https://example.com/xxxxxxxxx…)',
+                "params": {
+                    "trim_url_limit": 30,
+                    "nofollow": False,
+                    "autoescape": False,
+                },
+            },
+        ]
+        for test in tests:
+            with self.subTest(test=test):
+                output = urlizer(test["input"], **test["params"])
+                self.assertEqual(output, test["expected"])