Index: src/assets/css/audio.css
===================================================================
--- src/assets/css/audio.css	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/assets/css/audio.css	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -105,4 +105,13 @@
 .pat-link--youtube    { color: #FF0000; }
 .pat-link--soundcloud { color: #FF5500; }
+
+/* Link-only tracks (geen audiobestand): plek van de afspeelknop = stil muziek-icoon. */
+.pat-noplay {
+  display: inline-flex; align-items: center; justify-content: center;
+  flex: 0 0 38px; width: 38px; align-self: center;
+  color: var(--ink-faint, #a8a29e);
+}
+.pat-noplay svg { width: 18px; height: 18px; }
+.pat-row.pat-static { cursor: default; }
 
 /* ============================================================
Index: src/routes/admin-audio.js
===================================================================
--- src/routes/admin-audio.js	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/routes/admin-audio.js	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -394,4 +394,22 @@
 
 /** GET /admin/audio/api/:id — single track with all metadata */
+// Maak een track ZONDER audiobestand (alleen titel + open-in links). Verschijnt
+// in albums/playlists in de lijst, met open-in-iconen maar zonder afspeelknop.
+router.post('/create-link', requireGod, express.json(), (req, res) => {
+  const site = res.locals.site;
+  if (!site) return res.status(404).json({ error: 'Site required' });
+  const trackId = uuid();
+  const title = ((req.body && req.body.title) || 'Nieuwe track').toString().trim().slice(0, 200) || 'Nieuwe track';
+  try {
+    db.prepare(`
+      INSERT INTO audio_tracks (id, site_id, title, media_id, position)
+      VALUES (?, ?, ?, NULL, COALESCE((SELECT MAX(position) + 1 FROM audio_tracks WHERE site_id = ?), 0))
+    `).run(trackId, site.id, title, site.id);
+  } catch (e) {
+    return res.status(500).json({ error: e.message });
+  }
+  res.json({ ok: true, id: trackId });
+});
+
 router.get('/api/:id', requireGod, (req, res) => {
   const site = res.locals.site;
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/routes/posts.js	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -537,5 +537,5 @@
       html = AudioEmbedService.embedTrackShortcodes(html, (id) => {
         const r = byId.get(id);
-        if (!r || !r.filename) return null;
+        if (!r) return null;
         return {
           id: r.id,
@@ -548,5 +548,5 @@
           link_youtube: r.link_youtube || '',
           link_soundcloud: r.link_soundcloud || '',
-          url: audioUrl(r.filename),
+          url: r.filename ? audioUrl(r.filename) : '',  // '' = link-only track
         };
       });
@@ -566,9 +566,9 @@
       const byAlbum = new Map();
       for (const r of albumRows) {
-        if (!r.filename) continue;
+        // Link-only tracks (geen bestand) blijven in het album-overzicht (url '').
         if (!byAlbum.has(r.album)) byAlbum.set(r.album, []);
         byAlbum.get(r.album).push({
           id: r.id,
-          url: audioUrl(r.filename),
+          url: r.filename ? audioUrl(r.filename) : '',
           title: r.title || 'Untitled',
           artist: r.artist || '',
Index: src/services/AudioEmbedService.js
===================================================================
--- src/services/AudioEmbedService.js	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/services/AudioEmbedService.js	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -270,5 +270,20 @@
     return html.replace(/\[\[track:([A-Za-z0-9_-]+)\]\]/g, (match, id) => {
       const t = trackLookup(id);
-      if (!t || !t.url) return match;
+      if (!t) return match;
+      const titleH0 = this.escape(t.title || 'Untitled');
+      const artistH0 = this.escape(t.artist || '');
+      const creditBits0 = [this.escape(t.credit || ''), this.escape(t.license || '')].filter(Boolean).join(' · ');
+      // Link-only track (geen audiobestand): geen afspeelknop, wel info + open-in.
+      if (!t.url) {
+        return `<div class="post-audio-track post-audio-track--static" id="track-${id}">
+  <span class="pat-noplay" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg></span>
+  <div class="pat-info">
+    <div class="pat-title">${titleH0}</div>
+    ${artistH0 ? `<div class="pat-artist">${artistH0}</div>` : ''}
+    ${creditBits0 ? `<div class="pat-credit">${creditBits0}</div>` : ''}
+  </div>
+  ${this.openInLinks(t)}
+</div>`;
+      }
       const trackJson = JSON.stringify({
         id,
@@ -318,5 +333,7 @@
       // Stable DOM id for this rendering — used as data-pcms-album-id on tracks
       const albumDomId = 'album-' + Math.random().toString(36).slice(2, 10);
-      const albumJson = JSON.stringify(album.tracks)
+      // Alleen afspeelbare tracks (met url) in de queue; link-only tracks staan
+      // wel in de lijst maar niet in de afspeel-JSON.
+      const albumJson = JSON.stringify(album.tracks.filter((t) => t.url))
         .replace(/&/g, '&amp;').replace(/'/g, '&#39;').replace(/</g, '&lt;');
       const titleH = this.escape(album.title || name);
@@ -327,4 +344,15 @@
         const tTitle = this.escape(t.title || ('Track ' + (i + 1)));
         const tArtist = this.escape(t.artist || '');
+        // Link-only track: geen afspeelknop, wel nummer + info + open-in.
+        if (!t.url) {
+          return `    <li class="post-audio-track post-audio-track--static"${t.id ? ` id="track-${t.id}"` : ''}>
+      <span class="pat-track-num">${i + 1}.</span>
+      <div class="pat-info">
+        <div class="pat-title">${tTitle}</div>
+        ${tArtist && tArtist !== artistH ? `<div class="pat-artist">${tArtist}</div>` : ''}
+      </div>
+      ${this.openInLinks(t)}
+    </li>`;
+        }
         const tUrl = this.escape(t.url);
         return `    <li class="post-audio-track"${t.id ? ` id="track-${t.id}" data-pcms-track-id="${t.id}"` : ''} data-pcms-track-url="${tUrl}" data-pcms-album-id="${albumDomId}">
@@ -404,5 +432,7 @@
       // Audio-player.js reads data-pcms-album for queue. Same shape as
       // embedAlbumShortcodes — keep both in sync.
-      const tracksData = pl.tracks.map(t => ({
+      // Alleen afspeelbare tracks in de queue; link-only tracks staan wel in de
+      // lijst maar niet in de afspeel-JSON.
+      const tracksData = pl.tracks.filter(t => t.url).map(t => ({
         id:     t.id,
         url:    t.url,
@@ -426,5 +456,5 @@
       }
       const metaLine = this.escape(metaParts.join(' · '));
-      const firstUrl = this.escape(pl.tracks[0].url);
+      const firstUrl = this.escape((pl.tracks.find(t => t.url) || {}).url || '');
 
       // Track items — playlist-kind shows per-track cover thumbs, album-kind shows numbers
@@ -445,4 +475,18 @@
           : `<span class="pat-num">${i + 1}</span>`;
 
+        // Link-only track: geen klikbare afspeel-rij (statische div), wel open-in.
+        if (!t.url) {
+          return `    <li class="post-album-track-compact post-album-track-compact--static"${t.id ? ` id="track-${t.id}"` : ''}>
+      <div class="pat-row pat-static">
+        ${leader}
+        <span class="pat-meta">
+          <span class="pat-title">${tTitleH}</span>
+          ${showArtist ? `<span class="pat-artist">${tArtistH}</span>` : ''}
+        </span>
+        ${durHtml}
+      </div>
+      ${this.openInLinks(t)}
+    </li>`;
+        }
         const trackBase = String(t.url).split('?')[0];
         return `    <li class="post-album-track-compact"${t.id ? ` id="track-${t.id}" data-pcms-track-id="${t.id}"` : ''}>
Index: src/services/PlaylistService.js
===================================================================
--- src/services/PlaylistService.js	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/services/PlaylistService.js	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -104,5 +104,5 @@
 
     const mappedTracks = tracks
-      .filter(t => t.filename)  // skip orphaned references
+      // Link-only tracks (geen media-bestand) blijven in de lijst staan met url ''.
       .map(t => ({
         id: t.id,
@@ -114,5 +114,5 @@
         link_youtube: t.link_youtube || '',
         link_soundcloud: t.link_soundcloud || '',
-        url: urlFor ? urlFor(t.filename) : null,
+        url: (t.filename && urlFor) ? urlFor(t.filename) : '',
       }));
     // Geen eigen cover? Val terug op de eerste track-cover, zodat de kaart niet leeg is.
Index: src/views/pages/admin-audio.ejs
===================================================================
--- src/views/pages/admin-audio.ejs	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/views/pages/admin-audio.ejs	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -62,6 +62,10 @@
   <%# ── TRACK LIST ─────────────────────────────────────────── %>
   <section class="ax-card">
-    <div class="ax-card-title">
+    <div class="ax-card-title" style="display:flex;align-items:center;gap:.5rem">
       Tracks <span class="ax-count"><%= tracks.length %></span>
+      <button type="button" class="ax-btn ax-btn-secondary" id="add-link-track-btn"
+              style="margin-left:auto;font-size:.85rem" title="Een track zonder audiobestand — alleen titel + open-in links">
+        + Track zonder audio
+      </button>
     </div>
 
@@ -889,4 +893,27 @@
     });
   });
+
+  // ── "+ Track zonder audio": maak een link-only stub + open de editor ──
+  const addLinkBtn = document.getElementById('add-link-track-btn');
+  if (addLinkBtn) {
+    addLinkBtn.addEventListener('click', async () => {
+      addLinkBtn.disabled = true;
+      try {
+        const r = await fetch('/admin/audio/create-link', {
+          method: 'POST', credentials: 'same-origin',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ title: 'Nieuwe track' }),
+        });
+        const j = await r.json();
+        if (!r.ok || !j.ok) throw new Error(j.error || 'Aanmaken mislukt');
+        if (typeof window.openTrackEditor !== 'function') { location.reload(); return; }
+        window.openTrackEditor({ id: j.id, onSaved: () => location.reload() });
+      } catch (err) {
+        alert('Track aanmaken mislukt: ' + err.message);
+      } finally {
+        addLinkBtn.disabled = false;
+      }
+    });
+  }
 
   // ── Wire all "Edit" buttons to the track-editor modal ─────────
Index: src/views/shell.ejs
===================================================================
--- src/views/shell.ejs	(revision 183875b46fb28dbb72cffa7bc53cedaad373f8cd)
+++ src/views/shell.ejs	(revision d727e920fc53e75604231284032475736ffcdc0d)
@@ -168,5 +168,5 @@
      anywhere (admin previews, post embeds, etc). The player itself is
      a singleton — see the script tag near </body>. -->
-<link rel="stylesheet" href="/assets/css/audio.css?v=8">
+<link rel="stylesheet" href="/assets/css/audio.css?v=9">
 <!-- Eigen custom media-embeds (YouTube/SoundCloud/Spotify) in huisstijl. -->
 <link rel="stylesheet" href="/assets/css/embed.css?v=8">
