Markdown Link Regex
Matches Markdown link syntax [text](url) and captures the link text and destination, with optional support for a quoted title.
Regex Pattern
\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)Pattern Breakdown
Hover over a token to see what it does.
| Token | Meaning |
|---|---|
| \[ | Literal opening square bracket that starts the link text |
| ([^\]]+) | Capture group 1: the link text, one or more characters that are not ] |
| \] | Literal closing square bracket ending the link text |
| \( | Literal opening parenthesis that starts the destination |
| ([^)\s]+) | Capture group 2: the URL, one or more characters that are not ) or whitespace |
| (?:\s+"[^"]*")? | Optional non-capturing group for a space-separated quoted title, e.g. "Example" |
| \) | Literal closing parenthesis ending the link |
Detailed Explanation
What it does
This pattern locates inline Markdown links of the form [link text](https://example.com) anywhere in a string and captures the visible text and the target URL as separate groups. It also tolerates an optional title attribute in quotes after the URL, as in [text](url "Title").
Why it works
The two bracket-delimited groups use negated character classes ([^\]]+ and [^)\s]+) instead of greedy wildcards so each group stops precisely at its own closing delimiter without overrunning into the rest of the link or accidentally spanning multiple links. The optional non-capturing group for the title is anchored to whitespace followed by a quoted string, so it only engages when a title is actually present.
Common use cases
- Extracting all links and their labels from a Markdown document for a table of contents
- Rewriting relative links to absolute links during a static site build
- Validating that documentation contains no malformed or empty link syntax
- Converting Markdown links to HTML anchor tags in a lightweight renderer
Edge cases
- Image syntax like  still matches the trailing [alt](image.png) portion since the pattern is unanchored and doesn't check for a preceding !
- Links with a title attribute, e.g. [text](url "Title"), are matched including the title in the full match but the title itself isn't captured separately
- Empty link text such as [] () fails to match because the text group requires at least one non-] character
- URLs containing spaces are not matched because the destination group stops at the first whitespace character
- Nested brackets inside the link text, like [a [b] c](url), will stop capturing at the first ]
Limitations
- Does not validate that the captured URL is itself well-formed; use a dedicated URL pattern for that
- Reference-style links like [text][ref] are not matched, only inline links
- Does not distinguish between a real link and an image link prefixed with !
- Titles using single quotes instead of double quotes are not recognized
Interactive Tester
Edit the pattern or text below — matching runs live in your browser.
Test Cases
Editable — add your own inputs to see if they pass.
| Input | Expected | Result | |
|---|---|---|---|
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass |
Language Variants
Production-ready examples in 12 languages.
const mdLinkRegex = /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
const matches = [..."[Docs](https://example.com)".matchAll(mdLinkRegex)];
console.log(matches[0][1], matches[0][2]); // 'Docs' 'https://example.com'Common Mistakes
Using .* instead of [^\]]+ for the link text, causing the match to greedily span across multiple links on the same line
Fix: Use a negated character class scoped to the delimiter (e.g. [^\]]+) so each group stops at its own closing bracket
Forgetting the g flag when extracting all links from a document, so only the first link is found
Fix: Add the g flag (or use matchAll/finditer/scan depending on language) to retrieve every match
Assuming this pattern also matches image syntax  as a distinct type
Fix: Check for a preceding ! character separately if images need to be excluded or handled differently
Performance Notes
- The negated character classes [^\]]+ and [^)\s]+ are efficient because they can only match up to their terminating delimiter, avoiding backtracking blowups
- Running this pattern with the global flag over a large document is linear in the length of the text
- The optional title group only adds overhead when whitespace immediately follows the URL, so plain links are matched with minimal extra work
Browser Compatibility
| Engine | Supported | Notes |
|---|---|---|
| Chrome | Yes | — |
| Firefox | Yes | — |
| Safari | Yes | — |
| Edge | Yes | — |
| Node.js | Yes | — |