You can’t always tell what parts of a web page are linkable. Some headings have ids, some sections don’t. On mobile, inspecting element IDs is nearly impossible.
This tool lets you paste any URL and see a list of anchors you can link to. It detects both <a name=””> and any element with an id. You control what tags to include—headings, paragraphs, divs, or navigation blocks.
It returns a flat list of absolute URLs. Each line shows the tag name, the anchor (#something), and a short preview of its content.
You may want to use this when auditing documentation or refactoring internal links. One example: a team maybe has a design spec page with 34 sections, but only 12 have anchor IDs. Use this to see what could be linked from the table of contents and what had to be fixed.
Have you ever linked someone to a specific part of a FAQ, only to realize it didn’t work? Why do so many sites miss this?
You can run this script on your own PHP server. The source is short. It skips local IPs, limits file size, and auto-detects HTTP or HTTPS. No frontend dependencies.
<?php
function isValidUrl($url) {
$parts = parse_url($url);
return in_array($parts['scheme'] ?? '', ['http', 'https']) &&
filter_var($url, FILTER_VALIDATE_URL);
}
function isPrivateIp($host) {
$ip = gethostbyname($host);
return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
}
function safeFetchHtml($url) {
$context = stream_context_create(['http' => ['timeout' => 5]]);
return @file_get_contents($url, false, $context, 0, 5 * 1024 * 1024); // 5MB max
}
function getAbsoluteUrl($url) {
$parts = parse_url($url);
return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['path']) ? $parts['path'] : '');
}
function truncate($str, $length = 400) {
$str = trim(preg_replace('/\s+/', ' ', $str));
return mb_strlen($str) > $length ? mb_substr($str, 0, $length) . '…' : $str;
}
function extractAnchors($url, $includeTags) {
$html = safeFetchHtml($url);
if (!$html) return [];
libxml_use_internal_errors(true);
$doc = new DOMDocument();
@$doc->loadHTML($html);
$xpath = new DOMXPath($doc);
$base = getAbsoluteUrl($url);
$anchors = [];
$queryParts = ['//a[@name]', '//a[@id]'];
foreach ($includeTags as $tag) {
$queryParts[] = '//' . $tag . '[@id]';
}
foreach ($xpath->query(implode(' | ', $queryParts)) as $node) {
$anchor = $node->getAttribute('name') ?: $node->getAttribute('id');
if (!$anchor) continue;
$text = truncate($node->textContent);
$anchors[] = [
'anchor' => $anchor,
'label' => $text,
'tag' => strtolower($node->nodeName)
];
}
$seen = [];
$unique = [];
foreach ($anchors as $a) {
if (!isset($seen[$a['anchor']])) {
$seen[$a['anchor']] = true;
$unique[] = [
'href' => $base . '#' . $a['anchor'],
'label' => htmlspecialchars($a['tag']) . ' <strong>#' . htmlspecialchars($a['anchor']) . '</strong> — ' . htmlspecialchars($a['label'])
];
}
}
return $unique;
}
// Form state
$url = $_POST['url'] ?? '';
if ($url && !preg_match('#^https?://#i', $url)) {
$url = 'https://' . $url;
}
$containerTags = ['div', 'section', 'main', 'nav', 'aside'];
$contentTags = ['a','h1','h2','h3','h4','h5','h6','p','span','ul','ol','article'];
$allTags = array_merge($contentTags, $containerTags);
$includeTags = array_filter($allTags, fn($tag) => isset($_POST['include_' . $tag]));
$links = ($url && isValidUrl($url) && !isPrivateIp(parse_url($url, PHP_URL_HOST))) ? extractAnchors($url, $includeTags) : [];
$allContainersChecked = count(array_filter($containerTags, fn($tag) => isset($_POST["include_$tag"]))) === count($containerTags);
$allContentChecked = count(array_filter($contentTags, fn($tag) => isset($_POST["include_$tag"]))) === count($contentTags);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page Anchors</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html{
background-color:#f9f9f9;}
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background: #fff;
color: #111;
margin: 0 auto;
padding: 2rem;
max-width: 50em;
}
h1 { font-size: 2rem; font-weight: 600; }
form {
background: linear-gradient(135deg, #e3f2fd, #fce4ec);
padding: 1.5rem;
border-radius: 1rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 2rem;
}
input[type="url"], input[type="text"] {
width: 95%;
padding: 0.75rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 0.8rem;
margin-bottom: 1rem;
}
label { display: block; font-size: 0.95rem; }
.checkbox-group { margin-bottom: 1rem; }
.checkbox-group h3 { margin-bottom: 0.5rem; font-size: 1rem; }
.checkbox-row {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 0.5rem;
}
button {
background-color: #007aff;
color: white;
padding: 0.75rem 1rem;
font-size: 1rem;
border: none;
border-radius: 0.8rem;
cursor: pointer;
}
ul { list-style: none; padding: 0; }
li { margin-bottom: 0.8rem; }
a {
color: #007aff;
text-decoration: none;
word-break: break-word;
}
a:hover { text-decoration: underline; }
.link-label {
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
</style>
</head>
<body>
<section style="margin-bottom: 2rem;">
<h1>Extract linkable anchors from any webpage</h1>
<a href="https://savolai.net/category/notes/software-development-notes/">savolai.net</a>
<p style="font-size: 1.1rem; color: #555;">Uncover linkable sections in any page.
<button id="toggleInfo" type="button" style="background: none; border: none; color: #007aff; font-size: 1rem; padding: 0; margin-bottom: 1rem; cursor: pointer;">
What is this?
</button>
<div id="infoPanel" style="display: none;">
<p>This tool finds internal anchors—<code>id</code> or <code>name</code> attributes—that can be directly linked to. Includes content sections like headings and optional containers like divs.<a href="https://savolai.net/notes/software-development-notes/extract-linkable-anchors-from-any-webpage/">Blog post</a></p>
</div>
</section>
<?php if ($url && $links): ?>
<h3>Anchors found in <?= htmlspecialchars($url) ?></h3>
<ul>
<?php foreach ($links as $link): ?>
<li><a href="<?= htmlspecialchars($link['href']) ?>" target="_blank"><span class="link-label"><?= $link['label'] ?></span></a></li>
<?php endforeach; ?>
</ul>
<?php elseif ($url): ?>
<p>No valid anchors found or unable to fetch the page.</p>
<?php endif; ?>
<form method="post">
<input type="text" name="url" placeholder="Enter a domain or URL" required value="<?= htmlspecialchars($url) ?>">
<div class="checkbox-group">
<h3>Content elements</h3>
<div class="checkbox-row">
<?php foreach ($contentTags as $tag): ?>
<label><input type="checkbox" name="include_<?= $tag ?>" <?= isset($_POST["include_$tag"]) || !$_POST ? 'checked' : '' ?>> <?= htmlspecialchars($tag) ?></label>
<?php endforeach; ?>
<label><input type="checkbox" id="checkAllContent" <?= $allContentChecked ? 'checked' : '' ?>> <strong>All content elements</strong></label>
</div>
</div>
<div class="checkbox-group">
<h3>Container elements</h3>
<div class="checkbox-row">
<?php foreach ($containerTags as $tag): ?>
<label><input type="checkbox" name="include_<?= $tag ?>" <?= isset($_POST["include_$tag"]) ? 'checked' : '' ?>> <?= htmlspecialchars($tag) ?></label>
<?php endforeach; ?>
<label><input type="checkbox" id="checkAllContainers" <?= $allContainersChecked ? 'checked' : '' ?>> <strong>All container elements</strong></label>
</div>
</div>
<button type="submit">Find Anchors</button>
</form>
<script>
document.getElementById('toggleInfo').addEventListener('click', () => {
const panel = document.getElementById('infoPanel');
const show = panel.style.display !== 'block';
panel.style.display = show ? 'block' : 'none';
document.getElementById('toggleInfo').textContent = show ? 'Hide explanation' : 'What is this?';
});
document.getElementById('checkAllContent').addEventListener('change', e => {
const tags = <?= json_encode($contentTags) ?>;
tags.forEach(tag => {
const el = document.querySelector(`input[name="include_${tag}"]`);
if (el) el.checked = e.target.checked;
});
});
document.getElementById('checkAllContainers').addEventListener('change', e => {
const tags = <?= json_encode($containerTags) ?>;
tags.forEach(tag => {
const el = document.querySelector(`input[name="include_${tag}"]`);
if (el) el.checked = e.target.checked;
});
});
</script>
</body>
</html>