# Extracting Hyperlinks from PDFs as Markdown Links Using pdf-inspector

> Learn how to extract PDF hyperlinks as Markdown links using pdf-inspector. This tool efficiently parses link annotations and converts them into easy-to-use Markdown syntax.

- Repository: [Firecrawl/pdf-inspector](https://github.com/firecrawl/pdf-inspector)
- Tags: how-to-guide
- Published: 2026-08-10

---

**Use `pdf-inspector` to parse PDF link annotations and render them as `[text](url)` Markdown syntax through a three-stage pipeline: annotation detection, `TextItem` creation with `ItemType::Link(url)`, and final Markdown conversion.**

The `pdf-inspector` library by Firecrawl provides a robust Rust-based solution for extracting hyperlinks from PDF documents and converting them to standard Markdown link format. Unlike simple text extraction tools, it preserves the semantic structure of links by parsing PDF annotation dictionaries directly.

## How pdf-inspector Extracts PDF Hyperlinks

The extraction pipeline operates in three distinct stages, each isolated to handle any document type—text-based, scanned, or mixed.

### Stage 1: PDF Parsing and Link Annotation Detection

The extractor walks each page's `/Annots` array in [`src/extractor/links.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/extractor/links.rs), filtering for annotations where `Subtype` equals **Link**. The `extract_page_links` function reads the rectangle (`Rect`) for positioning and calls `extract_link_uri` to retrieve the destination URI from the annotation's `A` dictionary.

```rust
let annots = page_dict.get(b"Annots") …;   // line 56-64

```

Only annotations matching the Link subtype are processed:

```rust
if let Ok(subtype) = annot_dict.get(b"Subtype") {
    if let Ok(name) = subtype.as_name() {
        if name != b"Link" { continue; }
    }
}

```

### Stage 2: TextItem Creation with Link Metadata

For every valid link, `pdf-inspector` builds a `TextItem` with position fields (`x`, `y`, `width`, `height`) and `item_type` set to `ItemType::Link(url)`. This allows the rest of the pipeline to treat links like normal text while preserving the URL.

```rust
links.push(TextItem {
    text: url.clone(),
    // geometry fields...
    item_type: ItemType::Link(url),
    // ...
});

```

### Stage 3: Markdown Conversion

During final rendering in [`src/markdown/convert.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/markdown/convert.rs), the renderer inspects each `TextItem`. When it encounters `ItemType::Link(_)`, it emits standard Markdown link syntax:

```rust
ItemType::Link(url) => format!("[{}]({})", escaped_text, url);

```

## Extracting Links via Command Line

The `pdf2md` CLI tool provides the fastest way to extract PDF hyperlinks as Markdown:

```bash
pdf2md --json my-document.pdf > output.md

```

The `--json` flag returns structured output where each link appears as:

```json
{
  "text": "https://example.com",
  "page": 2,
  "item_type": { "Link": "https://example.com" },
  ...
}

```

## Extracting Links Programmatically in Rust

### Basic Link Extraction

Use `extract_text_with_positions` to pull links and print them as Markdown:

```rust
use pdf_inspector::extractor::extract_text_with_positions;
use pdf_inspector::types::ItemType;

fn main() -> Result<(), pdf_inspector::PdfError> {
    let items = extract_text_with_positions("my-document.pdf")?;
    for item in items {
        if let ItemType::Link(url) = &item.item_type {
            // Use surrounding text as label; here we reuse the URL
            println!("[{}]({})", url, url);
        }
    }
    Ok(())
}

```

### Full PDF-to-Markdown Conversion

Let the library handle all link formatting automatically:

```rust
use pdf_inspector::markdown::convert::to_markdown;
use pdf_inspector::extractor::extract_text_with_positions;

let items = extract_text_with_positions("my-document.pdf")?;
let markdown = to_markdown(items, pdf_inspector::markdown::MarkdownOptions::default());
// `markdown` now contains all text with `[label](url)` links

```

## Key Source Files and Their Roles

| File | Purpose |
|------|---------|
| [`src/extractor/links.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/extractor/links.rs) | Detects link annotations, extracts URIs via `extract_link_uri`, creates `TextItem` with `ItemType::Link` |
| [`src/markdown/convert.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/markdown/convert.rs) | Renders `ItemType::Link` as `[text](url)` Markdown syntax |
| [`src/bin/pdf2md.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/pdf2md.rs) | CLI front-end calling the extractor and outputting Markdown/JSON |
| [`src/types.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/types.rs) | Defines `TextItem` struct and `ItemType` enum including the `Link` variant |
| [`src/extractor/mod.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/extractor/mod.rs) | Public API wiring link extraction into the overall text-extraction flow |

## Safety and Performance Considerations

The link extraction is deliberately isolated from text-layout logic, ensuring compatibility across document types. Budget constants `MAX_FORM_FIELD_NODES` and `MAX_FORM_FIELD_DEPTH` protect against pathological PDFs that could cause stack overflows during AcroForm field traversal.

## Summary

- **Link detection** happens in [`src/extractor/links.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/extractor/links.rs) via `extract_page_links` and `extract_link_uri`
- **URI extraction** reads the `A` dictionary's `URI` entry from PDF annotation objects
- **Markdown output** uses `ItemType::Link(url)` pattern matching in [`src/markdown/convert.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/markdown/convert.rs)
- **CLI usage**: `pdf2md --json document.pdf` outputs structured links
- **Library usage**: `extract_text_with_positions` followed by `to_markdown` for full conversion

## Frequently Asked Questions

### Does pdf-inspector handle both text-based and scanned PDFs?

Yes. The link extraction operates on PDF annotation dictionaries, which exist independently of whether the page content is text or images. The position-based `TextItem` system treats links uniformly regardless of underlying content type.

### What URL formats does pdf-inspector support?

The library extracts any URI string present in the annotation's `A` dictionary `URI` entry. This includes `http://`, `https://`, `mailto:`, `file://`, and custom scheme URLs as encoded in the PDF.

### How are link labels determined in the Markdown output?

The renderer uses the surrounding or associated text from the `TextItem` as the link label. If the link annotation has no explicit text content, the raw URL becomes the label. You can customize this by manipulating `TextItem` fields before calling `to_markdown`.

### Can I extract links without converting the full document to Markdown?

Yes. Call `extract_text_with_positions` directly and filter for `ItemType::Link(url)` variants. This bypasses the Markdown renderer entirely while still giving you access to all hyperlink data including positions and page numbers.