Primal mega_feed_directive Cache Endpoint

The mega_feed_directive is Primal's primary and most versatile feed endpoint. It accepts a JSON-encoded specification string that determines what type of feed to return. This single endpoint replaces the need for many specialized feed endpoints.

Request Format

["REQ", "subscription_id", {
  "cache": ["mega_feed_directive", {
    "spec": "<json_specification>",
    "limit": 20,
    "offset": 0,
    "since": 0,
    "until": 0,
    "user_pubkey": "<optional_viewer_pubkey>"
  }]
}]

Parameters

- spec (required): JSON-encoded string specifying the feed type and parameters
- limit (optional): Maximum number of items to return (default varies by spec type)
- offset (optional): Pagination offset
- since (optional): Unix timestamp for time-based pagination
- until (optional): Unix timestamp for time-based pagination
- user_pubkey (optional): Viewer's pubkey for personalization

The spec Field

The spec field is a JSON-encoded string (not a JSON object) that contains the feed specification. Each spec has an id field that determines the feed type, plus additional parameters specific to that type.

Common Spec Structure

{
  "id": "<feed_type>",
  "kind": "notes" | "reads",  // Content type
  // ... type-specific parameters
}

---

Feed Type Reference

Search Feeds

#### Advanced Search (advsearch)

Search for content across Nostr. This is what Primal's web app uses for the search feature.

Spec Format:

{
"id": "advsearch",
"query": "<search_query>"
}

Example:

const spec = JSON.stringify({
id: "advsearch",
query: "bitcoin"
});

const response = await server.megaFeedDirective(spec, { limit: 20 });

Example Request:

["REQ", "search_123", {
"cache": ["mega_feed_directive", {
"spec": "{\"id\":\"advsearch\",\"query\":\"bitcoin\"}",
"limit": 20,
"user_pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"
}]
}]

User Search (Kind 0):

{
"id": "advsearch",
"query": "kind:0 alex"
}

---

User Feeds

#### User's Notes Feed (feed)

Get notes from a specific user.

Spec Format:

{
"id": "feed",
"kind": "notes",
"pubkey": "<user_pubkey>"
}

Example:

const spec = JSON.stringify({
id: "feed",
kind: "notes",
pubkey: "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"
});

const response = await server.megaFeedDirective(spec, { limit: 20 });

---

Trending Feeds

#### Latest Notes (latest)

Latest notes from users the viewer follows.

Spec Format:

{
"id": "latest",
"kind": "notes"
}

With Replies:

{
"id": "latest",
"kind": "notes",
"include_replies": true
}

Example:

const spec = JSON.stringify({
id: "latest",
kind: "notes",
include_replies: true
});

---

#### Global Trending (global-trending)

Globally trending notes across various time windows.

Spec Format:

{
"id": "global-trending",
"kind": "notes",
"hours": 1 | 4 | 12 | 24 | 48 | 168 // 1h, 4h, 12h, 24h, 48h, 7d
}

Examples:

24-hour trending:

const spec = JSON.stringify({
id: "global-trending",
kind: "notes",
hours: 24
});

Weekly trending:

const spec = JSON.stringify({
id: "global-trending",
kind: "notes",
hours: 168 // 7 days = 168 hours
});

Example Request:

["REQ", "trending_24h", {
"cache": ["mega_feed_directive", {
"spec": "{\"id\":\"global-trending\",\"kind\":\"notes\",\"hours\":24}",
"limit": 20
}]
}]

---

#### All Notes (Firehose) (all-notes)

Latest global notes from across the network. Can be very high volume.

Spec Format:

{
"id": "all-notes",
"kind": "notes"
}

Example:

const spec = JSON.stringify({
id: "all-notes",
kind: "notes"
});

// Use with caution - this is a firehose!
const response = await server.megaFeedDirective(spec, { limit: 50 });

---

Article/Long-form Content Feeds

#### Latest Articles (latest)

Same as notes, but for long-form content (kind 30023).

Spec Format:

{
"id": "latest",
"kind": "reads"
}

Example:

const spec = JSON.stringify({
id: "latest",
kind: "reads"
});

---

#### Trending Articles (global-trending)

Trending long-form articles.

Spec Format:

{
"id": "global-trending",
"kind": "reads",
"hours": 24 | 48 | 168
}

---

Topic/Hashtag Feeds

#### Topic Feed

Notes filtered by a specific hashtag/topic.

Spec Format:

{
"id": "topic",
"kind": "notes",
"topic": "<hashtag_without_#>"
}

Example:

const spec = JSON.stringify({
id: "topic",
kind: "notes",
topic: "bitcoin"
});

---

Drafts

#### User Drafts (drafts)

User's draft posts (kind 31234).

Note: Drafts use a different cache endpoint (drafts), not mega_feed_directive, but are mentioned here for completeness.

---

Response Format

The response contains events of various kinds providing feed data and metadata:

Event Types in Response

| Kind | Purpose |
|------|---------|
| 0 | User metadata |
| 1 | Note (text post) |
| 6 | Repost |
| 9735 | Zap receipt |
| 30023 | Long-form article |
| 31234 | Draft |
| 10000100 | Feed range (pagination info) |
| 10000107 | Note statistics |
| 10000113 | Note actions |
| 10000118 | User statistics |

Feed Range Event (kind 10000100)

Provides pagination information for the feed:

{
  "kind": 10000100,
  "content": "{\"since\":1738976703,\"until\":1739062799,\"order_by\":\"created_at\",\"elements\":[\"note_id_1\",\"note_id_2\",...]}",
  ...
}

The elements array provides the order of items in the feed.

---

Common Patterns

Personalized Feeds

Many feeds support a user_pubkey parameter to personalize results based on the viewer's follows and preferences:

["REQ", "feed_123", {
  "cache": ["mega_feed_directive", {
    "spec": "{\"id\":\"latest\",\"kind\":\"notes\"}",
    "limit": 20,
    "user_pubkey": "viewer_pubkey_here"
  }]
}]

Pagination

Use until/since with the timestamps from the feed range event:

// First page
const page1 = await server.megaFeedDirective(spec, { limit: 20 });

// Get 'until' from feed range event
const until = page1.events.find(e => e.kind === 10000100);
const range = JSON.parse(until.content);

// Next page
const page2 = await server.megaFeedDirective(spec, {
limit: 20,
until: range.until,
offset: 20
});

Content Type Filtering

Use the kind field in the spec to filter between:
- "notes" - Short-form posts (kind 1)
- "reads" - Long-form articles (kind 30023)

---

Complete Examples

Search with Pagination

const searchSpec = JSON.stringify({
  id: "advsearch",
  query: "nostr protocol"
});

let offset = 0;
const limit = 20;

// Get first page
const page1 = await server.megaFeedDirective(searchSpec, {
limit,
offset,
userPubkey: viewerPubkey
});

console.log(Found ${page1.posts.length} posts);

// Get second page
offset += limit;
const page2 = await server.megaFeedDirective(searchSpec, {
limit,
offset,
userPubkey: viewerPubkey
});

Trending Feed with Time Window

// Get 4-hour trending
const trendingSpec = JSON.stringify({
  id: "global-trending",
  kind: "notes",
  hours: 4
});

const trending = await server.megaFeedDirective(trendingSpec, {
limit: 50
});

for (const post of trending.posts) {
console.log(${post.event.content} (score: ${post.stats?.score}));
}

User Feed

const userSpec = JSON.stringify({
  id: "feed",
  kind: "notes",
  pubkey: "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd"
});

const userPosts = await server.megaFeedDirective(userSpec, {
limit: 20,
userPubkey: viewerPubkey // For personalized stats
});

---

Discovering Available Feeds

Primal provides endpoints to discover available feed configurations:

Get Home Feed Configurations

["REQ", "home_feeds", {
  "cache": ["get_home_feeds"]
}]

Returns a list of feed configurations with their specs.

Get Article Feed Configurations

["REQ", "reads_feeds", {
  "cache": ["get_reads_feeds"]
}]

---

Notes

1. Spec is a String: The spec field must be a JSON-encoded string, not a raw JSON object
2. Feed-Specific Limits: Default limits vary by feed type (check server response)
3. Authentication: Some feeds (like latest) require user_pubkey to work properly
4. Rate Limiting: Firehose feeds (all-notes) may have stricter rate limits
5. Backwards Compatibility: Older feed types may still be supported but consider using mega_feed_directive equivalents

---

See Also

- Primal Server API Documentation
- PrimalServer TypeScript Client
- Web App Feed Implementations: repos/primal-web-app/src/lib/feed.ts