Manually creating a Bluesky custom feed

First, decide on a domain name where you'll host your feed(s). We'll use 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": [
    "id": "",
    "service": [
            "id": "#bsky_fg",
            "type": "BskyFeedGenerator",
            "serviceEndpoint": ""

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 document defines BskyFeedGenerator service endpoint
... 'displayName': 'first feedgen',
... 'description': 'description here',
... 'createdAt': '2024-01-21T06:19:24.520Z'
... }

>>> 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
... )

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., 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:

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/"

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


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

← Back