Manually creating a Bluesky custom feed

First, decide on a domain name where you'll host your feed(s). We'll use example.com for demo purposes.

On this host create a DID document at /.well-known/did.json with the domain name in the id field and the serviceEndpoint field:

{
    "@context": [
        "https://www.w3.org/ns/did/v1"
    ],
    "id": "did:web:example.com",
    "service": [
        {
            "id": "#bsky_fg",
            "type": "BskyFeedGenerator",
            "serviceEndpoint": "https://example.com"
        }
    ]
}

Next, add a record to your repo's app.bsky.feed.generator collection with a did pointing to the domain you selected:

>>> record_details = {
... 'did': 'did:web:example.com', # DID document defines BskyFeedGenerator service endpoint
... 'displayName': 'first feedgen',
... 'description': 'description here',
... 'createdAt': '2024-01-21T06:19:24.520Z'
... }

>>> record_data = atproto.models.com.atproto.repo.put_record.Data(
... repo = 'did:plc:4nsduwlpivpuur4mqkbfvm6a', # your user repo DID
... collection = 'app.bsky.feed.generator',
... rkey = 'test1', # describe your feed using a-z and dashes only
... record = record_details
... )

>>> client.com.atproto.repo.put_record(record_data)
Response(cid='bafyreicve366fluvlmbyygwm4yfn5bab6ahoznn4ii7ytjfzu4dgfm44zi', uri='at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/test1')

Your Bluesky PDS first fetches the DID document of the did property (i.e., did:web:example.com) which defines the serviceEndpoint location of your feed generator.

Once it has this, your PDS fires off another request to a app.bsky.feed.getFeedSkeleton NSID hosted at the service endpoint with the feed at-uri provided as a feed query parameter.

Pulling it all together, the request URL will look like this:

https://example.com/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/test1

This NSID should return a JSON document in the following shape with a Content-Type: application/json header:

{
  "cursor": "",
  "feed": [
    {
      "post": "at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.post/3kjhr4hwujn2q"
    }
  ]
}

If there is a cursor value, the PDS uses that to paginate results. Leave it empty if there's only one page.

Refs: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getFeedSkeleton.json


Created: 2024-01-21
Last Updated: 2024-01-23

← Back