Skip to content

Plugins

OpenFeed uses a plugin system to fetch content from various sources. Each plugin knows how to handle a specific type of URL — for example, the Reddit plugin understands the Reddit API and formats posts for the feed UI.

Plugins are matched using a canHandle(url) function. The first plugin that claims a URL wins. OpenFeed tries specific plugins before generic fallbacks like the RSS plugin.

Supported plugins

The following plugins ship as part of OpenFeed and do not need to be installed:

PluginHandlesAuth required
youtube-rssYouTube channel and video URLsNo
redditReddit subreddit and post URLsNo
substack-rssSubstack publication URLsNo
buttondownButtondown newsletter URLsNo
blueskyBluesky profile URLsNo
hacker-newsHacker NewsNo
dev-todev.to articlesNo
mediumMedium publication URLsNo
rssAny RSS or Atom feed URLNo
podcastsPodcast RSS feed URLsNo
open-meteoOpen-Meteo weather API URLsNo
githubGitHub repo and user URLsNo
cnnCNN articlesNo
washington-postWashington Post articlesNo
wall-street-journalWSJ articlesNo
politicoPolitico articlesNo
associated-pressAP News articlesNo
reutersReuters articlesNo
al-jazeeraAl Jazeera articlesNo
the-new-yorkerNew Yorker articlesNo
espnESPN articlesNo
centcomCENTCOM press releasesNo
nyt-crosswordNYT CrosswordNo
wordleWordle daily puzzleNo
tiktokTikTok profile URLsNo
instagram-firecrawlInstagram profile URLsFIRECRAWL_API_KEY
google-calendarGoogle Calendar feed URLsOAuth token
gmailGmail feed URLsOAuth token

If no plugin matches a URL, OpenFeed falls back to the default plugin which skips the source with a warning.

Installing plugins

You can install additional plugins from npm:

bash
npm install open-feed-plugin-example

Then register the plugin in your open-feed.yaml:

yaml
plugins:
  - open-feed-plugin-example

Configuring plugins

Some plugins accept an options object in your source config:

yaml
sources:
  - name: My Source
    url: https://example.com/
    options:
      limit: 10
      includeImages: true

Refer to individual plugin documentation for supported options.

Writing your own plugin

You can create and share your own plugins to pull custom sources into OpenFeed.

Basics

An OpenFeed plugin is a TypeScript module that exports a BackendFeedPlugin object with the following shape:

typescript
import type { BackendFeedPlugin, FetchFn, NewFeedItem } from 'open-feed/plugins/types'

const myPlugin: BackendFeedPlugin = {
  name: 'my-plugin',
  icon: `<svg>...</svg>`,  // optional SVG icon string

  // Return true if this plugin can handle the given URL
  canHandle: (url: string) => url.includes('mysite.com'),

  // Fetch and return items for the given source URL
  listItems: async (
    sourceUrl: string,
    fetchFn: FetchFn,
    options?: Record<string, unknown>
  ): Promise<readonly NewFeedItem[]> => {
    const response = await fetchFn(sourceUrl)
    const data = await response.json()
    return data.items.map(item => ({
      sourceName: 'My Source',
      sourceUrl,
      title: item.title,
      url: item.url,
      publishedAt: new Date(item.date),
      renderData: {
        richText: { text: item.body }
      }
    }))
  }
}

export default myPlugin

Each item must include at least one render method in renderData:

MethodDataHow it renders
video{ videoId?, url? }YouTube embed or <video>
richText{ html?, text }Formatted article content
audio{ url }<audio> player
embed{ url }<iframe> fallback

Local testing

Write tests using Vitest. Inject a mock fetchFn — never hit the network in tests:

typescript
import { describe, it, expect, vi } from 'vitest'
import myPlugin from './index'

describe('myPlugin', () => {
  it('handles mysite.com URLs', () => {
    expect(myPlugin.canHandle('https://mysite.com/feed')).toBe(true)
    expect(myPlugin.canHandle('https://other.com')).toBe(false)
  })

  it('returns items', async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      json: () => Promise.resolve({
        items: [{ title: 'Test', url: 'https://mysite.com/1', date: '2024-01-01', body: 'Hello' }]
      })
    })
    const items = await myPlugin.listItems('https://mysite.com', mockFetch)
    expect(items).toHaveLength(1)
    expect(items[0].title).toBe('Test')
  })
})

Publish

Publish your plugin to npm with the open-feed-plugin- prefix so others can discover it:

bash
npm publish --access public

We encourage you to share your plugins with the community in the OpenFeed Discord or by opening a PR to add it to the built-in plugin list.