Addon Developer Guide
Build addons that extend Eclipse Music with new music sources. Your addon is a simple HTTP server that provides search results and stream URLs. Eclipse handles everything else — playback, UI, queue management, playlists, lyrics, and more.
How It Works
An addon is a web server (Node.js, Python, Go, Rust — any language) that exposes these endpoints:
| Endpoint | Required | Purpose |
|---|---|---|
| GET /manifest.json | Yes | Describes your addon |
| GET /search?q={query} | Yes | Returns search results |
| GET /stream/{id} | Yes | Returns a playable stream URL |
| GET /album/{id} | No | Returns album tracks (for browsing) |
| GET /artist/{id} | No | Returns artist top tracks + albums |
| GET /playlist/{id} | No | Returns playlist tracks |
Users install your addon by pasting its URL into Eclipse. Your addon appears in the search dropdown, and Eclipse routes playback through it. If you implement the optional detail endpoints, users can browse albums, artists, and playlists natively inside Eclipse.
Quick Start
1. Create the manifest
Your server must respond to GET /manifest.json:
{
"id": "com.yourname.myaddon",
"name": "My Music Addon",
"version": "1.0.0",
"description": "Streams music from my service",
"icon": "https://your-cdn.com/addon-icon.png",
"resources": ["search", "stream"],
"types": ["track", "album", "artist"]
}
Required fields:
| Field | Type | Description |
|---|---|---|
| id | String | Unique identifier (reverse domain, e.g. com.yourname.myaddon) |
| name | String | Display name shown in Eclipse's search dropdown |
| version | String | Semver version (e.g. 1.0.0) |
| resources | Array | What your addon provides: "search", "stream", or both |
Optional fields:
| Field | Type | Description |
|---|---|---|
| description | String | Brief description |
| icon | String | URL to your addon's icon (PNG/JPEG, square, recommended 128x128 or larger). Shown in the search provider dropdown, addon management, and anywhere your addon is referenced. Falls back to a puzzle piece icon if not provided. |
| types | Array | Content types: "track", "album", "artist" |
2. Implement search
Respond to GET /search?q={query} with:
{
"tracks": [
{
"id": "track_123",
"title": "Song Title",
"artist": "Artist Name",
"album": "Album Name",
"duration": 240,
"artworkURL": "https://example.com/cover.jpg",
"isrc": "USRC12345678",
"format": "mp3"
}
],
"albums": [
{
"id": "album_456",
"title": "Album Title",
"artist": "Artist Name",
"artworkURL": "https://example.com/album.jpg",
"trackCount": 12,
"year": "2024"
}
],
"artists": [
{
"id": "artist_789",
"name": "Artist Name",
"artworkURL": "https://example.com/artist.jpg",
"genres": ["Pop", "Rock"]
}
],
"playlists": [
{
"id": "playlist_123",
"title": "Best of 2024",
"creator": "Curator Name",
"artworkURL": "https://example.com/playlist.jpg",
"trackCount": 50
}
]
}
All four arrays are optional — return only what you have.
Track fields:
| Field | Type | Required | Description |
|---|---|---|---|
| id | String | Yes | Unique ID within your addon |
| title | String | Yes | Song title |
| artist | String | Yes | Artist name |
| album | String | No | Album name |
| duration | Int | No | Duration in seconds |
| artworkURL | String | No | Cover art URL |
| isrc | String | No | ISRC code — highly recommended (enables MusicKit metadata enrichment) |
| format | String | No | Audio format: "mp3", "flac", "aac", "m4a" |
| streamURL | String | No | Direct stream URL (if provided, Eclipse skips the /stream call) |
Playlist fields (in search response):
| Field | Type | Required | Description |
|---|---|---|---|
| id | String | Yes | Unique playlist ID |
| title | String | Yes | Playlist name |
| description | String | No | Playlist description |
| artworkURL | String | No | Cover image URL |
| creator | String | No | Playlist creator name |
| trackCount | Int | No | Number of tracks |
3. Implement stream resolution
Respond to GET /stream/{id} with:
{
"url": "https://cdn.example.com/audio/track_123.mp3",
"format": "mp3",
"quality": "320kbps"
}
Response fields:
| Field | Type | Required | Description |
|---|---|---|---|
| url | String | Yes | Direct HTTP/HTTPS URL to the audio file |
| format | String | No | Audio format |
| quality | String | No | Quality description (e.g. "320kbps", "lossless") |
| expiresAt | Number | No | Unix timestamp when the URL expires |
The URL must be a direct link to an audio file — no HTML pages, no redirects to login pages. Eclipse supports MP3, AAC, M4A, FLAC, WAV, and OGG.
4. Album details (optional)
If your addon returns albums in search results, implement GET /album/{id} so users can browse album tracks:
{
"id": "album_456",
"title": "Album Title",
"artist": "Artist Name",
"artworkURL": "https://example.com/album.jpg",
"year": "2024",
"description": "Optional album description",
"trackCount": 12,
"tracks": [
{
"id": "track_1",
"title": "Track One",
"artist": "Artist Name",
"duration": 240,
"artworkURL": "https://example.com/cover.jpg",
"streamURL": "https://cdn.example.com/track1.mp3"
}
]
}
Without this endpoint, Eclipse falls back to searching Apple Music by album name. If your content isn't on Apple Music (unreleased music, remixes, etc.), you need this endpoint.
5. Artist details (optional)
If your addon returns artists in search results, implement GET /artist/{id} so users can see top tracks and albums:
{
"id": "artist_789",
"name": "Artist Name",
"artworkURL": "https://example.com/artist.jpg",
"bio": "Optional artist biography",
"genres": ["Hip-Hop", "R&B"],
"topTracks": [
{
"id": "track_1",
"title": "Hit Song",
"artist": "Artist Name",
"duration": 200,
"streamURL": "https://cdn.example.com/hit.mp3"
}
],
"albums": [
{
"id": "album_1",
"title": "Album Name",
"artist": "Artist Name",
"artworkURL": "https://example.com/album.jpg",
"trackCount": 14,
"year": "2024"
}
]
}
Without this endpoint, Eclipse falls back to searching Apple Music by artist name.
6. Playlist details (optional)
If your addon provides playlists, implement GET /playlist/{id}:
{
"id": "playlist_123",
"title": "Playlist Name",
"description": "Curated collection",
"artworkURL": "https://example.com/playlist.jpg",
"creator": "Curator Name",
"tracks": [
{
"id": "track_1",
"title": "Track One",
"artist": "Artist Name",
"duration": 240,
"streamURL": "https://cdn.example.com/track1.mp3"
}
]
}
Complete Example (Node.js)
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// Manifest
app.get('/manifest.json', (req, res) => {
res.json({
id: 'com.example.demo',
name: 'Demo Addon',
version: '1.0.0',
description: 'A demo addon for Eclipse Music',
resources: ['search', 'stream'],
types: ['track']
});
});
// Search
app.get('/search', (req, res) => {
const query = req.query.q || '';
// Replace with your actual search logic
res.json({
tracks: [
{
id: 'demo_1',
title: `Demo: ${query}`,
artist: 'Demo Artist',
duration: 180,
format: 'mp3'
}
]
});
});
// Stream resolution
app.get('/stream/:id', (req, res) => {
const trackId = req.params.id;
// Replace with your actual stream URL logic
res.json({
url: `https://your-cdn.com/audio/${trackId}.mp3`,
format: 'mp3',
quality: '320kbps'
});
});
app.listen(3000, () => {
console.log('Addon running on http://localhost:3000');
});
npm init -y
npm install express cors
node index.js
Complete Example (Python)
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
@app.route('/manifest.json')
def manifest():
return jsonify({
'id': 'com.example.demo',
'name': 'Demo Addon',
'version': '1.0.0',
'description': 'A demo addon for Eclipse Music',
'resources': ['search', 'stream'],
'types': ['track']
})
@app.route('/search')
def search():
query = request.args.get('q', '')
# Replace with your actual search logic
return jsonify({
'tracks': [{
'id': 'demo_1',
'title': f'Demo: {query}',
'artist': 'Demo Artist',
'duration': 180,
'format': 'mp3'
}]
})
@app.route('/stream/<track_id>')
def stream(track_id):
# Replace with your actual stream URL logic
return jsonify({
'url': f'https://your-cdn.com/audio/{track_id}.mp3',
'format': 'mp3',
'quality': '320kbps'
})
if __name__ == '__main__':
app.run(port=3000)
pip install flask flask-cors
python app.py
Hosting
Your addon must be accessible via HTTPS. Options:
| Platform | Free Tier | Notes |
|---|---|---|
| Cloudflare Workers | 100k req/day | Serverless, global CDN |
| Vercel | Generous | Serverless functions |
| Railway | $5/mo | Full server |
| Fly.io | 3 shared VMs | Docker containers |
| Your own server | — | Any VPS with HTTPS |
For local development, use http://localhost:3000 — Eclipse accepts HTTP for local network addresses.
Testing on a real iPhone/iPad: Use your Mac's local IP instead of localhost (e.g. http://192.168.1.100:3000/). Run ipconfig getifaddr en0 on your Mac to get it. Phone must be on the same WiFi. Make sure your server binds to 0.0.0.0 (all interfaces), not just 127.0.0.1.
How Eclipse Uses Your Addon
Search Flow
- User selects your addon in the search dropdown
- User types a query
- Eclipse calls
GET /search?q={query}on your server - Your results appear in Eclipse's search UI (same layout as Apple Music/Tidal results)
- If tracks have ISRC codes, Eclipse enriches them with MusicKit metadata (high-quality artwork, genre, lyrics availability)
Playback Flow
- User taps play on a track from your addon
- Eclipse calls
GET /stream/{trackId}on your server - Your server returns the stream URL
- Eclipse plays it through its audio engine (supports MP3, AAC, FLAC, etc.)
- All audio features work automatically — EQ, reverb, speed control, lock screen, AirPlay, CarPlay
Album/Artist/Playlist Browsing
When a user taps an album, artist, or playlist from your search results:
- If you implement
/album/{id},/artist/{id}, or/playlist/{id}— Eclipse loads the data from your addon - If you don't — Eclipse falls back to searching Apple Music (MusicKit) by name. This works for mainstream content but fails for content not on Apple Music (unreleased music, remixes, etc.)
- Add
"catalog"to your manifestresourcesto enable the Playlists filter tab in search
Default Playback
Users can set your addon as the Default Playback source in Settings → Addon Management. When set:
- Songs from the Home tab, Radio, AI DJ, Genre Map, editorial playlists, and Smart Shuffle are played through your addon
- Eclipse searches your addon by
"artist title"to find a matching track, then plays the stream URL - If your addon can't find the song, playback will fail gracefully
Playlist/Library Integration
When a user saves your addon's tracks to a playlist or their library:
- Eclipse stores your
addon ID,track ID, andstream URLalongside the song metadata - Tracks with permanent stream URLs (podcast episodes, direct MP3 links) play without your addon installed
- Tracks with expiring URLs need your addon installed — Eclipse calls
/stream/{id}to get a fresh URL - Users can add tracks to favorites, playlists, My Library, and collaborative playlists
- Offline download is supported for tracks with stream URLs
Playlist Import
When your addon is installed, it appears as a match service in the Import Playlist flow. Users can import Spotify/Apple Music playlists and match songs through your addon's search endpoint.
Collaborative Playlists
Addon tracks work in collaborative playlists. Both users need the same addon installed for playback (unless the track has a permanent stream URL stored in sourceUrl).
ISRC: The Secret Weapon
If your tracks include ISRC (International Standard Recording Code), Eclipse can:
- Fetch high-quality artwork from Apple Music
- Show genre tags, album info, and editorial notes
- Display lyrics (synced and unsynced)
- Cross-reference with other services for fallback playback
Requirements
- HTTPS (except localhost for development)
- CORS enabled (
Access-Control-Allow-Origin: *) - JSON responses with
Content-Type: application/json - Manifest at the root path (
/manifest.json) - Reasonable response times (<5 seconds for search, <3 seconds for stream)
Installing Your Addon in Eclipse
- Open Eclipse Music
- Go to Settings → Connections → Add Connection → Addon
- Paste your addon's URL (e.g.
https://your-addon.com/orhttps://your-addon.com/manifest.json) - Eclipse fetches the manifest and shows a preview
- Tap Install
Your addon appears in the search dropdown. If it supports streaming, it also appears in Settings → Addon Management → Default Playback picker.
Capabilities Reference
Resources
| Value | Description |
|---|---|
| "search" | Your addon can search for music (shows in search dropdown) |
| "stream" | Your addon can resolve playable URLs (shows in playback picker) |
| "catalog" | Your addon supports detail endpoints (/album/{id}, /artist/{id}, /playlist/{id}) |
Types
| Value | Description |
|---|---|
| "track" | Individual songs |
| "album" | Albums/collections |
| "artist" | Artists |
| "playlist" | Curated playlists |
| "file" | Generic audio files |
FAQ
Can I build an addon in any language?
Yes — any language that can serve HTTP with JSON responses works. Node.js, Python, Go, Rust, PHP, Ruby, Java, C#, etc.
Does my addon need a database?
No — your addon just needs to respond to HTTP requests. How you source the data is up to you.
Can my addon require authentication?
Yes — you can include tokens in your addon URL (e.g. https://my-addon.com/{user_token}/manifest.json). Eclipse stores the full URL.
What happens if my addon is offline?
Eclipse falls back gracefully — tracks from your addon won't play, but the rest of the app works fine. Downloaded/offline tracks still play from local files.
Can I update my addon without users reinstalling?
Yes — just update your server. Eclipse fetches the manifest each time. Users don't need to reinstall unless the URL changes.
What audio formats are supported?
MP3, AAC, M4A, FLAC, WAV, OGG. FLAC streams instantly via Eclipse's native FLAC streaming engine.
Can my addon provide album/artist detail pages?
Yes! Implement GET /album/{id} and GET /artist/{id} endpoints, and add "catalog" to your manifest's resources array. When users tap an album or artist from your search results, Eclipse will load the details from your addon. Without these endpoints, Eclipse falls back to Apple Music (MusicKit) for album/artist browsing — which won't work for content not on Apple Music (unreleased music, remixes, etc.).
How do I group content into albums/eras?
Return them as albums in your /search response, then implement /album/{id} to return the tracks for each group. For example, an unreleased music addon could group songs by "era" and return each era as an album.
Can users save addon tracks offline?
Yes — tracks with a streamURL (direct URL) can be downloaded for offline listening. Users can save individual tracks or bulk-download entire playlists. Offline playback works without your addon server.
Can my addon be set as the default playback source?
Yes — if your manifest includes "stream" in resources, your addon appears in Settings → Addon Management → Default Playback. When selected, Eclipse searches your addon for songs played from Home, Radio, DJ, editorial playlists, etc. Return the best match from your search endpoint.
What if my addon uses token-based URLs?
Include the token in your base URL: https://my-addon.com/{user_token}/manifest.json. Eclipse strips /manifest.json and uses the rest as the base URL. All subsequent calls (/search, /stream, /album, etc.) include the token prefix automatically.
Can I return year as a number instead of a string?
Yes — Eclipse accepts "year": 2024 (number) or "year": "2024" (string) for albums. Both work.
Do I need to return all search result types?
No. Return only what you have. A podcast addon might return only tracks. A music library addon might return tracks, albums, and artists. Eclipse handles missing arrays gracefully.
Can users import playlists through my addon?
Yes — your addon appears as a match service in the Import Playlist flow. When importing a Spotify or Apple Music playlist, Eclipse searches your addon for each track and creates a local playlist with matches.