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:

  1. Resolve your handle to a DID (your permanent identifier)
  2. Find your PDS endpoint
  3. 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 →