https://txtfetch.com/docs/links/
Do links come back?
The link text does. The address behind it mostly does not. Here is what each format returns.
The short answer
Not as links. The words of a link always come back. The address behind them does not come back for HTML, Word, PowerPoint or email.
PDF and Excel are different. Their link addresses come back as extra lines after the content. Nothing ties an address to its word.
A URL that is typed in the text is ordinary text. It comes back in every format.
The Markdown output has no links either. It never takes the form [text](url).
The engine is tika-server-standard 3.3.1.
Source: terraform/lambda-layers/build/versions.env (versions.env: TIKA_VERSION), accessed 2026-10-17.
What comes back, case by case
We sent each file to the production API. Every file holds the word spec as a link to https://example.com/spec. Every file also holds the URL https://example.com/plain as typed text. Every row shows its source. The inputs and the raw responses are in the repo.
| Input | What comes back | Source |
|---|---|---|
| An HTML page with a link, a visible URL and a mail link | The link text comes back. The address in href does not. The visible URL comes back as typed. The mail link keeps only its text. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| The same HTML page as Markdown | The same text, with no links. The output never takes the form [text](url). Markdown here means headings, lists and tables. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| The same HTML page as element JSON | Three paragraph elements. No element holds an href. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| A Word file with an external link | The link text comes back. The target address stored in the file does not. The visible URL comes back. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| A PowerPoint slide with a linked word | The same result as Word. The word comes back. The address behind it does not. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| An email with an HTML body and a link | The same result again. The link text comes back and the address does not. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| A PDF with a link over the word spec | The word comes back in place. The address comes back too, on its own line after the page text. Nothing ties it to the word. | Production API probe: POST /v1/extract accessed 2026-10-17 |
| An Excel sheet with a linked cell | The cell text comes back in the sheet. The address comes back after the sheet, on its own line. Nothing ties it to the cell. | Production API probe: POST /v1/extract accessed 2026-10-17 |
Real output
Each block is the full response body, as the API sent it. In the JSON, \n is a line break inside the text.
An HTML page with a link, a visible URL and a mail link
In
links.html
Out
{"status":"success","extracted_text":"\nRead the spec before you start.\n\nThe plain address is https://example.com/plain\n\nAsk the team for help.\n\n","metadata":{"content_type":"text/html","bytes":300,"chars":106,"ocr":false}}The same HTML page as Markdown
In
links.html (format=markdown)
Out
{"status":"success","markdown":"Read the spec before you start.\n\nThe plain address is https://example.com/plain\n\nAsk the team for help.\n","metadata":{"content_type":"text/html","bytes":300,"ocr":false,"chars":104,"format":"markdown","tier":"standard","pages":null,"vlm":false}}The same HTML page as element JSON
In
links.html (format=json)
Out
{"status":"success","elements":[{"type":"paragraph","text":"Read the spec before you start.","page":null,"offset":0,"bbox":null},{"type":"paragraph","text":"The plain address is https://example.com/plain","page":null,"offset":33,"bbox":null},{"type":"paragraph","text":"Ask the team for help.","page":null,"offset":81,"bbox":null}],"metadata":{"content_type":"text/html","bytes":300,"ocr":false,"chars":103,"format":"json","tier":"standard","pages":null,"vlm":false}}A Word file with an external link
In
links.docx
Out
{"status":"success","extracted_text":"Read the spec before you start.\nThe plain address is https://example.com/plain\n","metadata":{"content_type":"application/vnd.openxmlformats-officedocument.wordprocessingml.document","bytes":1320,"chars":79,"ocr":false}}A PowerPoint slide with a linked word
In
links.pptx
Out
{"status":"success","extracted_text":"Read the spec before you start.\nThe plain address is https://example.com/plain\n\n\n\n\n\n\n","metadata":{"content_type":"application/vnd.openxmlformats-officedocument.presentationml.presentation","bytes":28395,"chars":85,"ocr":false}}An email with an HTML body and a link
In
links.eml
Out
{"status":"success","extracted_text":"Read the spec before you start.\n\n\nThe plain address is https://example.com/plain\n\n","metadata":{"content_type":"message/rfc822","bytes":271,"chars":82,"ocr":false}}A PDF with a link over the word spec
In
links.pdf
Out
{"status":"success","extracted_text":"\nRead the spec before you start.\n\nThe plain address is https://example.com/plain\n\nhttps://example.com/spec\n\n","metadata":{"content_type":"application/pdf","bytes":1663,"chars":108,"ocr":false}}An Excel sheet with a linked cell
In
links.xlsx
Out
{"status":"success","extracted_text":"Sheet\n\tspec\n\tThe plain address is https://example.com/plain\n\nhttps://example.com/spec\n","metadata":{"content_type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","bytes":5217,"chars":86,"ocr":false}}What this means for your pipeline
A citation needs a page or a section. It rarely needs a link target. Read Page numbers and citations for what each format returns.
A crawler or a "see also" index needs the targets. Read them from the source file, as shown next. Keep them beside the chunk that holds the link text.
A detached PDF address is easy to mistake for content. It lands in your chunks as a bare URL line. Run the checks in Quality checks if you index it.
How to get the URLs yourself
Read the link targets from the source file. Run the snippet next to your txtfetch call. Each snippet ran on the sample file named under it.
HTML
You already hold the source. Read the href values with the standard library, next to the txtfetch call.
Python, run on links.html
from html.parser import HTMLParser
class Hrefs(HTMLParser):
def __init__(self):
super().__init__()
self.found = [] # (anchor text, URL)
self._href = None
self._text = ""
def handle_starttag(self, tag, attrs):
if tag == "a":
self._href = dict(attrs).get("href")
self._text = ""
def handle_data(self, data):
if self._href:
self._text += data
def handle_endtag(self, tag):
if tag == "a" and self._href:
if self._href.startswith("http"):
self.found.append((self._text, self._href))
self._href = None
parser = Hrefs()
with open("links.html", encoding="utf-8") as f:
parser.feed(f.read())
print(parser.found)It printed
[('spec', 'https://example.com/spec')]Word
A .docx file is a zip. The link targets sit in word/_rels/document.xml.rels. Keep the rows whose Type ends in /hyperlink.
Python, run on links.docx
import zipfile
import xml.etree.ElementTree as ET
REL = "{http://schemas.openxmlformats.org/package/2006/relationships}Relationship"
with zipfile.ZipFile("links.docx") as z:
root = ET.fromstring(z.read("word/_rels/document.xml.rels"))
urls = [
r.get("Target")
for r in root.iter(REL)
if r.get("Type", "").endswith("/hyperlink") and r.get("TargetMode") == "External"
]
print(urls)It printed
['https://example.com/spec']
PowerPoint
python-pptx exposes the address of each text run.
Install
pip install python-pptx
Python, run on links.pptx
from pptx import Presentation
urls = []
for slide in Presentation("links.pptx").slides:
for shape in slide.shapes:
if not shape.has_text_frame:
continue
for paragraph in shape.text_frame.paragraphs:
for run in paragraph.runs:
if run.hyperlink.address:
urls.append((run.text, run.hyperlink.address))
print(urls)It printed
[('spec', 'https://example.com/spec')]txtfetch returns the address after the page text. To tie it to its word, read the link annotations with pypdf. Match the word by its position. This is a rough match.
Install
pip install pypdf
Python, run on links.pdf
from pypdf import PdfReader
reader = PdfReader("links.pdf")
for number, page in enumerate(reader.pages, start=1):
for ref in page.get("/Annots", []):
annot = ref.get_object()
action = annot.get("/A")
if annot.get("/Subtype") == "/Link" and action and "/URI" in action:
x0, y0, x1, y1 = (float(n) for n in annot["/Rect"])
words = []
def visit(text, cm, tm, font_dict, font_size):
x, y = tm[4], tm[5]
if x0 - 1 <= x < x1 - 1 and y0 <= y <= y1 and text.strip():
words.append(text.strip())
page.extract_text(visitor_text=visit)
print(number, action["/URI"], words)It printed
1 https://example.com/spec ['spec']
We have no snippet for Excel or ODT files. A scanned page has no link layer, so it has no URLs to read.
Web pages and the premium tier
We tested uploaded files. We did not test a page that txtfetch fetches from a URL. We did not measure links on the premium tier. We make no claim about them.
?quality=premium is sold as a tier, but it does not run today. VLM_ENABLED defaults to false, and no operator has switched the tier on in production. A premium request downgrades to the standard Tika path instead.
Read the HTML page, the Word page and the PDF page for the rest of each format.
Frequently asked questions
- Does txtfetch keep hyperlinks?
- Not as links. The link text always comes back. The target address does not come back for HTML, Word, PowerPoint or email. For PDF and Excel the address comes back as a separate line after the content.
- Does format=markdown give me [text](url) links?
- No. The Markdown output has headings, lists and tables. It has no links. A link in the source comes back as plain text.
- Does a URL typed in the text come back?
- Yes. A visible URL is ordinary text. It comes back as typed in every format.
- Why does my PDF address come back after the text?
- In our tests the PDF link address came back on its own line after the page text. It has no position, so nothing ties it to the word that held the link. Read the link annotations yourself if you need the pair.
- How do I get link targets for citations or a crawl?
- Read them from the source file next to the txtfetch call. The page shows short Python snippets for HTML, Word, PowerPoint and PDF. Each one ran on the sample files in the repo.
- Does the premium tier keep links?
- We did not measure links on the premium tier. We make no claim about it. The premium tier does not run today, so a premium request downgrades to the standard Tika path.
Watch it run, then take a key.
The playground replays a real recorded response for every sample document.
Open the playground →