A music discovery and curation app for Farcaster. Share and discover tracks from YouTube Music, Spotify, SoundCloud, Bandcamp, and more.
- Submit Music: Paste links from supported platforms and share them to Farcaster
- Automatic Metadata Extraction: Uses oEmbed APIs to fetch track info, artwork, and embeds
- Mini App Embeds: Tracks appear in Farcaster feeds with play buttons and artwork
- Player View: Full-screen player with embedded streaming from original platforms
- Discovery Feed: Browse recently shared music and top-tipped tracks
- Tipping System: Support artists and curators with tips (numbers for now, tokenizable later)
- Dark Mode Design: Glassmorphic UI with smooth animations
- YouTube / YouTube Music
- Spotify
- SoundCloud
- Bandcamp
- Framework: Next.js 15 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS
- SDK: @farcaster/frame-sdk
- Icons: Lucide React
- Deployment: Vercel (recommended)
- Node.js 18+
- npm or yarn
- Clone the repository:
cd music-curator
- Install dependencies:
npm install
- Set up environment variables:
cp .env.local .env.local
# Edit .env.local and set NEXT_PUBLIC_BASE_URL to your domain
- Run the development server:
npm run dev
To test Farcaster embeds locally, use cloudflared:
cloudflared tunnel --url http://localhost:3000
This will give you a public URL that you can use to test embeds at warpcast.com/~/developers/embeds
music-curator/
├── app/
│ ├── api/
│ │ └── tracks/ # API routes for track CRUD operations
│ ├── feed/ # Discovery feed page
│ ├── play/ # Player page (opened from embeds)
│ ├── track/[id]/ # Individual track pages with meta tags
│ ├── layout.tsx # Root layout
│ └── page.tsx # Home page with submit form
├── components/
│ ├── MusicCard.tsx # Track card for feed display
│ ├── Player.tsx # Full-screen music player
│ └── SubmitForm.tsx # Music link submission form
├── lib/
│ ├── farcaster.ts # Farcaster SDK utilities
│ ├── music-parser.ts # URL parsing & metadata extraction
│ └── store.ts # In-memory track storage
├── types/
│ └── music.ts # TypeScript types
└── public/
└── .well-known/
└── farcaster.json # Mini App manifest
Located at public/.well-known/farcaster.json
, this defines your Mini App metadata:
{
"miniapp": {
"version": "1",
"name": "Music Curator",
"homeUrl": "https://your-domain.app/",
"primaryCategory": "music"
}
}
Each track at /track/[id]
includes the fc:miniapp
meta tag for proper Farcaster embed rendering:
{
"version": "1",
"imageUrl": "[track artwork]",
"button": {
"title": "▶ Play",
"action": {
"type": "launch_frame",
"url": "/play?trackId=[id]"
}
}
}
import sdk from '@farcaster/frame-sdk';
await sdk.actions.ready(); // Hide splash screen
const context = await sdk.context;
const user = {
fid: context.user?.fid,
username: context.user?.username
};
await sdk.actions.composeCast({
text: "🎵 Check out this track",
embeds: ["https://your-domain.app/track/123"]
});
Get tracks with optional sorting:
?sort=recent
- Recently shared (default)?sort=most_tipped
- Top tipped tracks?limit=20
- Limit results (default: 20)
Submit a new track:
{
"id": "timestamp",
"url": "https://youtube.com/watch?v=...",
"platform": "youtube",
"title": "Track Title",
"artist": "Artist Name",
"artwork": "https://...",
"embedUrl": "https://youtube.com/embed/...",
"tips": 0,
"sharedBy": { "fid": 123, "username": "user" },
"timestamp": 1234567890
}
Get a single track by ID
Tip a track:
{
"action": "tip"
}
- Push your code to GitHub
- Import project in Vercel
- Set environment variables:
NEXT_PUBLIC_BASE_URL=https://your-domain.vercel.app
- Deploy
After deployment, update public/.well-known/farcaster.json
with your production domain:
{
"accountAssociation": {
"header": "[base64 header]",
"payload": "[base64 payload]",
"signature": "[base64 signature]"
},
"miniapp": {
"iconUrl": "https://your-domain.vercel.app/icon.png",
"homeUrl": "https://your-domain.vercel.app/",
"splashImageUrl": "https://your-domain.vercel.app/splash.png"
}
}
To generate proper account association credentials:
- Follow Farcaster Account Association docs
- Use your Farcaster account's private key to sign the domain
- Update the manifest with the generated signature
- Deploy your app or use cloudflared for local testing
- Share a track to get the
/track/[id]
URL - Test the embed at: warpcast.com/~/developers/embeds
- Verify the Mini App button appears in the embed
- Update
detectPlatform()
inlib/music-parser.ts
- Add extraction logic for the platform
- Add oEmbed URL if available
- Update type definition in
types/music.ts
Modify the iframe rendering logic in components/Player.tsx
based on platform requirements.
The current implementation uses in-memory storage (lib/store.ts
). For production, consider:
- PostgreSQL with Prisma
- Supabase
- Firebase Firestore
- PlanetScale
- In-memory storage (resets on server restart)
- Placeholder account association (use real credentials in production)
- Tips are just numbers (implement token transfers later)
- Some platforms may block iframe embeds (CORS/X-Frame-Options)
- Persistent database storage
- Real account association with signature verification
- Token-based tipping system
- Playlist curation
- Search and filtering
- User profiles
- Social features (follows, comments)
- Analytics dashboard
MIT
Contributions welcome! Please open an issue or PR.
Built with ❤️ for the Farcaster community