Do It Yourself
Don't trust me? Don't need me 🥺? Here's how to fetch and display your link lists with plain JavaScript. No dependencies!
The basic idea
Your links are stored on your PDS as ATProto records. To fetch them, you need to:
- Resolve your handle to a DID (your permanent identifier)
- Find your PDS endpoint
- Fetch the record
The code
// Fetch a link list from any PDS
async function fetchLinkList(handle, rkey) {
// Step 1: Resolve handle to DID
const didRes = await fetch(
`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`
);
const { did } = await didRes.json();
// Step 2: Get PDS endpoint from DID document
const plcRes = await fetch(`https://plc.directory/${did}`);
const didDoc = await plcRes.json();
const pds = didDoc.service.find(s => s.id === '#atproto_pds').serviceEndpoint;
// Step 3: Fetch the link list record
const recordRes = await fetch(
`${pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=lol.linkring.list&rkey=${rkey}`
);
const { value } = await recordRes.json();
return value;
}
// Example usage
const list = await fetchLinkList('yourhandle.bsky.social', 'your-list-rkey');
console.log(list.name, list.links);
Display it
// Render links to the page
function renderLinks(list, container) {
container.innerHTML = `
<h2>${list.name}</h2>
${list.description ? `<p>${list.description}</p>` : ''}
<ul>
${list.links.map(link => `
<li>
<a href="${link.url}" target="_blank">${link.title || link.url}</a>
${link.description ? `<br><small>${link.description}</small>` : ''}
</li>
`).join('')}
</ul>
`;
}
// Put it together
const list = await fetchLinkList('yourhandle.bsky.social', 'abc123');
renderLinks(list, document.getElementById('my-links'));
Get all your lists
// Fetch all link lists for a user
async function fetchAllLists(handle) {
const didRes = await fetch(
`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`
);
const { did } = await didRes.json();
const plcRes = await fetch(`https://plc.directory/${did}`);
const didDoc = await plcRes.json();
const pds = didDoc.service.find(s => s.id === '#atproto_pds').serviceEndpoint;
const listRes = await fetch(
`${pds}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=lol.linkring.list`
);
const { records } = await listRes.json();
return records.map(r => ({
rkey: r.uri.split('/').pop(),
...r.value
}));
}
Finding the rkey
The rkey is the last part of the list URL. If your list is at:
https://linkring.lol/@yourhandle/lists/3abc123xyz
Then the rkey is 3abc123xyz.
Or just use fetchAllLists() to get everything and pick the one you want.
Webrings too
Same deal, different collection name:
// For webrings, use this collection instead:
collection = 'lol.linkring.webring.ring'
That's it. Your data, your code, no middleman. Learn more about how ATProto makes this possible →