Build a feed with an end
Let people follow, block and mute, read a chronological home timeline that fans out in the background and ends with a caught-up marker, and show trending tags per region.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You are building a small social app. People follow each other, post, and read a home timeline that stays fast whether an author has ten followers or ten million. The timeline ends: it shows where the person last stopped, pauses after a session, and never inserts items while they read. It takes about 30 minutes.
You need
- A MainSDK app with a
dbsection and a model for your posts (hereposts). - Users that have an id. A subject is
{ id }, and may carrytenantandregion.
1. Declare the feed
feeds:
driver: db # local keeps everything in process, for tests
items: posts
page: { end: true, pause_after: 200 }
trending: { window: 1h, k: 20, min-count: 25 }2. Follow, block and mute
import { feeds } from "mainsdk";
await feeds.follow({ id: "ana" }, { id: "ben" }); // { state: "following" }
await feeds.mute({ id: "ana" }, { id: "ben" }, { until: "2026-12-01T00:00:00Z" });
await feeds.block({ id: "ben" }, { id: "cy" }); // deletes follows both ways
const counts = await feeds.counts({ id: "ben" }); // { followers, following }3. Publish and read
import { feeds } from "mainsdk";
declare const post: { id: string };
await feeds.publish({ id: "ben" }, { id: post.id, visibility: "public" });
const home = await feeds.timeline({ id: "ana" }, { limit: 20 });publish returns once the reference and the fan-out job are accepted. No follower's timeline is written in the request, and the author sees their own post at the top on the next read. The page ends with a caught-up or pause entry; send continue: true only when the person asks for more.
4. Show a count, not a push
Subscribe the client to feeds.new on user:<id> when you have a realtime port, and show "3 new posts". Never add the items to the list the person is reading.
5. Add trending
import { feeds } from "mainsdk";
feeds.count("tag:launch", { by: { id: "ana" } });
const top = await feeds.trending({ k: 10 }); // { region, window, keys }Keys counted for fewer than min-count people are hidden. Count tags, not people.
Check it worked
pnpm mainsdk checkpasses with noMAIN-FEED-0010to0013findings.- The home timeline of a follower shows the new post, and a blocked person's posts are gone from it.
- Every page you read ends with a
caught-uporpauseentry.
If it fails
MAIN-FEED-0010: the feed has no end, or a client requests the next page with no person acting. Keeppage.endon and wait for a click.MAIN-FEED-0011: ranking in an app with a children's audience. Serve chronological only.MAIN-FEED-0001: one side blocks the other. That is the block working.
Prompt for your coding agent
Add follows, blocks, mutes and a home timeline to my app with MainSDK's
feedsinterface. Keep the timeline chronological with a caught-up end and a pause, show new posts as a count and never push them in, and count trending tags per region with a minimum group size.
Was this page helpful?