Skip to content

Feeds

Every feed is a flat, reverse-chronological timeline of activities — no ranking, no aggregation. A feed read is exactly three statements on the backend, constant in page size, no matter how many activities exist (spec §6). Feeds are addressed by (group, id), e.g. timeline:maya or user:maya.

There is no “create feed” call. A feed is just an address — naming one in a read, a write or a follow is all it takes to exist. Reading a feed nobody has posted to returns an empty page, not a 404. group is yours to choose; the three conventional ones are user (someone’s own posts), timeline (the feed aggregating who they follow), and flat (a shared feed like flat:explore).

A feed has no read ACL, and posting is publishing. Anyone holding a valid token for your tenant can read any feed by name, and every follower of a feed sees everything that fans out into it. There is no visibility flag on an activity, no per-feed permission, and no way to un-publish something except deleting it — after it has already reached every timeline that follows.

That is deliberate (spec §1), and it has one consequence worth designing around:

The producer is the only gate. If something must not be seen, do not post it.

The trap is that the decision lives in your code, at a moment that doesn’t feel like a publishing decision — a database trigger firing on a row write. A private group’s match, a hidden listing, a draft: if your app already has a predicate for “this one isn’t public”, it has to run before addActivity, not in the UI that renders the card. A reader filter is not a gate; the data has already left.

The same applies to objects: an object is resolved into the sidecar for anyone who can read an activity that refs it, so a private session’s title and score are public the moment a public activity points at them.

If some content is genuinely private, keep it out of dropin and render it from your own database, alongside the feed rather than inside it.

One exception, and it is not addressable by feed name: notifications are owner-scoped. A user token reads only its own; a server token must name the owner it is acting for. Everything reachable as group:id is tenant-open.

import { DropInClient } from '@dropinnodex/client'
const client = new DropInClient({ url, apiKey, tokenProvider })
const page = await client.feed('user', 'maya').get({ limit: 20 })
page.results // Activity[] — newest first
page.next // opaque cursor, or null when there's no more

user:<id> is a single user’s own posts. The client and react hooks expose sugar for it; DropInServer only has the generic feed(group, id) — there’s no userFeed shortcut server-side.

// client.userFeed(id) is sugar for client.feed('user', id)
const page = await client.userFeed('maya').get({ limit: 20 })

timeline:<id> aggregates activities from every feed <id> follows (spec §1) — the “who I follow” feed. Same sugar pattern as userFeed.

// client.timeline(id) is sugar for client.feed('timeline', id)
const page = await client.timeline('maya').get({ limit: 20 })

There’s no special “explore” concept in the SDK — it’s an ordinary feed, conventionally named e.g. flat:explore, that other feeds follow so their posts fan out into one global timeline (this is exactly how the showcase app’s Explore tab works — see apps/showcase/lib/seed.ts).

const page = await client.feed('flat', 'explore').get({ limit: 20 })
  • Activities — posting into a feed.
  • Pagination — how next and loadNext() work across pages.
  • Follow — the edges that decide what a timeline aggregates.
  • Notifications — the separate flat feed for follows and reactions aimed at a user.
  • How it works — fan-out on write, and why a feed read is three statements regardless of page size.
  • Promoted activities — content served alongside a feed’s first page, outside the follow graph and outside the cursor.