Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/MusicalEntityController.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/MusicalEntityController.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/MusicalEntityController.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -2,12 +2,16 @@
 
 import com.ukim.finki.develop.finkwave.model.dto.LikeStatusDto;
+import com.ukim.finki.develop.finkwave.model.dto.PublishAlbumRequestDto;
+import com.ukim.finki.develop.finkwave.model.dto.PublishSongRequestDto;
 import com.ukim.finki.develop.finkwave.service.LikeService;
+import com.ukim.finki.develop.finkwave.service.MusicalEntityService;
 import lombok.AllArgsConstructor;
 import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
 
 @RestController
@@ -17,4 +21,5 @@
 
     private final LikeService likeService;
+    private final MusicalEntityService musicalEntityService;
 
     @PostMapping("/{id}/like")
@@ -22,3 +27,15 @@
         return ResponseEntity.ok(likeService.toggleLike(entityId));
     }
+
+    @PostMapping(value = "/publish/song", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public HttpEntity<?> publishSong(@ModelAttribute PublishSongRequestDto publishSongRequestDto) throws IOException {
+        musicalEntityService.handleSongPublish(publishSongRequestDto);
+        return ResponseEntity.status(HttpStatus.CREATED).build();
+    }
+
+    @PostMapping(value = "/publish/album", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public HttpEntity<?> publishAlbum(@ModelAttribute PublishAlbumRequestDto publishAlbumRequestDto) throws IOException {
+        musicalEntityService.handleAlbumPublish(publishAlbumRequestDto);
+        return ResponseEntity.status(HttpStatus.CREATED).build();
+    }
 }
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/MusicalEntity.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/MusicalEntity.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/MusicalEntity.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -3,18 +3,10 @@
 import java.time.LocalDate;
 
+import jakarta.persistence.*;
+import lombok.*;
 import org.hibernate.annotations.OnDelete;
 import org.hibernate.annotations.OnDeleteAction;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.Table;
-import lombok.Getter;
-import lombok.Setter;
 
 @Getter
@@ -23,7 +15,11 @@
 @Table(name = "musical_entities", schema = "project")
 @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class MusicalEntity {
     @Id
     @Column(name = "id", nullable = false)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
 
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Song.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Song.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Song.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -15,4 +15,5 @@
     @Id
     @Column(name = "id", nullable = false)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
 
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/AlbumSongsDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/AlbumSongsDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/AlbumSongsDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -0,0 +1,18 @@
+package com.ukim.finki.develop.finkwave.model.dto;
+
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class AlbumSongsDto {
+    private String title;
+    private String link;
+    private List<ArtistContributionSummaryDto> contributors;
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ArtistContributionSummaryDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ArtistContributionSummaryDto.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ArtistContributionSummaryDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -3,10 +3,20 @@
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // this dto is useful when we already have all the necessary info for the song and just need the contribution info
 @Getter
+@Setter
+@NoArgsConstructor
 @AllArgsConstructor
 public class ArtistContributionSummaryDto {
+    Long id = null;
     private String artistName;
     private String role;
+
+    public ArtistContributionSummaryDto(String artistName, String role) {
+        this.artistName = artistName;
+        this.role = role;
+    }
 }
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishAlbumRequestDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishAlbumRequestDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishAlbumRequestDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -0,0 +1,24 @@
+package com.ukim.finki.develop.finkwave.model.dto;
+
+import java.util.List;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.annotation.Nullable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class PublishAlbumRequestDto {
+    private String title;
+    private String genre;
+    private String link;
+    @Nullable
+    private MultipartFile cover;
+    private List<AlbumSongsDto> albumSongs;
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishSongRequestDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishSongRequestDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/PublishSongRequestDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -0,0 +1,23 @@
+package com.ukim.finki.develop.finkwave.model.dto;
+
+import jakarta.annotation.Nullable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class PublishSongRequestDto {
+    private String title;
+    private String genre;
+    private String link;
+    @Nullable
+    private MultipartFile cover;
+    private List<ArtistContributionSummaryDto> contributors;
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/UserSearchResultDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/UserSearchResultDto.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/UserSearchResultDto.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -3,6 +3,7 @@
 
 public record UserSearchResultDto(
-    String fullName,
-    String username,
-    String profilePhoto
+        Long id,
+        String fullName,
+        String username,
+        String profilePhoto
 ){}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -17,5 +17,5 @@
 
     @Query(value = """
-        SELECT u.full_name, u.username, u.profile_photo from users u
+        SELECT u.user_id, u.full_name, u.username, u.profile_photo from users u
         WHERE (u.full_name ILIKE '%' || :searchTerm || '%' or u.username ILIKE '%' || :searchTerm || '%')
             and u.listener = true and u.artist = false
@@ -25,5 +25,5 @@
 
     @Query(value = """
-        SELECT u.full_name, u.username, u.profile_photo from users u
+        SELECT u.user_id, u.full_name, u.username, u.profile_photo from users u
         WHERE (u.full_name ILIKE '%' || :searchTerm || '%' or u.username ILIKE '%' || :searchTerm || '%') and u.artist = true
         LIMIT :limit
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ArtistService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ArtistService.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ArtistService.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -1,7 +1,10 @@
 package com.ukim.finki.develop.finkwave.service;
 
+import com.ukim.finki.develop.finkwave.exceptions.UserNotFoundException;
+import com.ukim.finki.develop.finkwave.model.Artist;
 import com.ukim.finki.develop.finkwave.model.dto.ArtistContributionDto;
 import com.ukim.finki.develop.finkwave.repository.ArtistContributionRepository;
 
+import com.ukim.finki.develop.finkwave.repository.ArtistRepository;
 import org.springframework.transaction.annotation.Transactional;
 import lombok.AllArgsConstructor;
@@ -14,4 +17,5 @@
 public class ArtistService {
     private final ArtistContributionRepository artistContributionRepository;
+    private final ArtistRepository artistRepository;
 
 
@@ -21,3 +25,7 @@
     }
 
+    public Artist getArtistById(Long id){
+        return artistRepository.findById(id).orElseThrow(UserNotFoundException::new);
+    }
+
 }
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -134,5 +134,6 @@
     }
 
-    private User createNonAdminUser(AuthRequestDto authRequestDto) throws IOException {
+    @Transactional
+    public User createNonAdminUser(AuthRequestDto authRequestDto) throws IOException {
         User.UserBuilder userBuilder = User.builder()
                 .username(authRequestDto.username())
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/MusicalEntityService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/MusicalEntityService.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/MusicalEntityService.java	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -0,0 +1,208 @@
+package com.ukim.finki.develop.finkwave.service;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.ukim.finki.develop.finkwave.exceptions.InvalidFileException;
+import com.ukim.finki.develop.finkwave.model.Album;
+import com.ukim.finki.develop.finkwave.model.Artist;
+import com.ukim.finki.develop.finkwave.model.ArtistContribution;
+import com.ukim.finki.develop.finkwave.model.ArtistContributionId;
+import com.ukim.finki.develop.finkwave.model.MusicalEntity;
+import com.ukim.finki.develop.finkwave.model.Song;
+import com.ukim.finki.develop.finkwave.model.dto.AlbumSongsDto;
+import com.ukim.finki.develop.finkwave.model.dto.ArtistContributionSummaryDto;
+import com.ukim.finki.develop.finkwave.model.dto.PublishAlbumRequestDto;
+import com.ukim.finki.develop.finkwave.model.dto.PublishSongRequestDto;
+import com.ukim.finki.develop.finkwave.repository.AlbumRepository;
+import com.ukim.finki.develop.finkwave.repository.ArtistContributionRepository;
+import com.ukim.finki.develop.finkwave.repository.MusicalEntityRepository;
+import com.ukim.finki.develop.finkwave.repository.SongRepository;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+@Service
+public class MusicalEntityService {
+    private final AuthService authService;
+    private final ArtistService artistService;
+    private final SongRepository songRepository;
+    private final AlbumRepository albumRepository;
+    private final ArtistContributionRepository artistContributionRepository;
+    MusicalEntityRepository musicalEntityRepository;
+
+    private static final long MAX_FILE_SIZE = 2_000_000;
+    private static final String MAIN_VOCAL_ROLE = "MAIN VOCAL";
+    
+    @Transactional
+    public void handleSongPublish(PublishSongRequestDto requestDto) throws IOException {
+        Long userId = authService.getCurrentUserID();
+        Artist artist = artistService.getArtistById(userId);
+
+        MusicalEntity musicalEntity = createMusicalEntity(
+                requestDto.getTitle(),
+                requestDto.getGenre(),
+                requestDto.getCover(),
+                artist
+        );
+
+        Song song = new Song();
+        song.setAlbum(null);
+        song.setLink(requestDto.getLink());
+        song.setMusicalEntities(musicalEntity);
+        songRepository.save(song);
+
+        // add publishing artist
+        saveContribution(artist, musicalEntity, MAIN_VOCAL_ROLE);
+
+        // add additional contributors
+        saveContributionsFromDto(musicalEntity, requestDto.getContributors());
+    }
+
+    @Transactional
+    protected MusicalEntity createMusicalEntity(String title, String genre, MultipartFile cover, Artist releasedBy)
+            throws IOException {
+        MusicalEntity.MusicalEntityBuilder builder = MusicalEntity.builder()
+                .title(title)
+                .genre(genre)
+                .releaseDate(LocalDate.now())
+                .releasedBy(releasedBy);
+
+        if (cover != null && !cover.isEmpty()) {
+            try {
+                String filename = saveCoverPhoto(cover);
+                builder.cover("profile-pictures/" + filename);
+            } catch (Exception ignored) {}
+        }
+
+        return musicalEntityRepository.save(builder.build());
+    }
+
+    @Transactional
+    protected void saveContribution(Artist artist, MusicalEntity musicalEntity, String role) {
+        ArtistContributionId id = new ArtistContributionId();
+        id.setArtistId(artist.getId());
+        id.setMusicalEntityId(musicalEntity.getId());
+
+        ArtistContribution contribution = new ArtistContribution();
+        contribution.setId(id);
+        contribution.setArtist(artist);
+        contribution.setMusicalEntity(musicalEntity);
+        contribution.setRole(role);
+
+        artistContributionRepository.save(contribution);
+    }
+
+    @Transactional
+    protected void saveContributionsFromDto(MusicalEntity musicalEntity, List<ArtistContributionSummaryDto> contributors) {
+        if (contributors == null) return;
+
+        for (ArtistContributionSummaryDto contributorDto : contributors) {
+            if (contributorDto.getId() == null) continue;
+
+            Artist contributor = artistService.getArtistById(contributorDto.getId());
+            saveContribution(contributor, musicalEntity, contributorDto.getRole());
+        }
+    }
+    // todo: add business logic (max songs per album, max contributors etc.)
+
+    @Transactional
+    public void handleAlbumPublish(PublishAlbumRequestDto requestDto) throws IOException {
+        Long userId = authService.getCurrentUserID();
+        Artist artist = artistService.getArtistById(userId);
+
+        MusicalEntity albumMusicalEntity = createMusicalEntity(
+                requestDto.getTitle(),
+                requestDto.getGenre(),
+                requestDto.getCover(),
+                artist
+        );
+
+        Album album = new Album();
+        album.setMusicalEntities(albumMusicalEntity);
+        album = albumRepository.save(album);
+
+        // add publishing artist
+        saveContribution(artist, albumMusicalEntity, MAIN_VOCAL_ROLE);
+
+        // track artists already added to avoid duplicates
+        Set<Long> albumContributorIds = new HashSet<>();
+        albumContributorIds.add(artist.getId());
+
+        List<AlbumSongsDto> albumSongs = requestDto.getAlbumSongs();
+        for (AlbumSongsDto songDto : albumSongs) {
+            createSongForAlbum(albumMusicalEntity, album, songDto, artist, albumContributorIds);
+        }
+    }
+
+
+    @Transactional
+    protected void createSongForAlbum(MusicalEntity albumMe, Album album, AlbumSongsDto songDto,
+                                     Artist publishingArtist, Set<Long> albumContributorIds) {
+        MusicalEntity songMusicalEntity = MusicalEntity.builder()
+                .title(songDto.getTitle())
+                .releaseDate(albumMe.getReleaseDate())
+                .releasedBy(albumMe.getReleasedBy())
+                .cover(albumMe.getCover())
+                .genre(albumMe.getGenre())
+                .build();
+        songMusicalEntity = musicalEntityRepository.save(songMusicalEntity);
+
+        Song song = new Song();
+        song.setLink(songDto.getLink());
+        song.setAlbum(album);
+        song.setMusicalEntities(songMusicalEntity);
+        songRepository.save(song);
+
+        // add publishing artist as MAIN VOCAL to this song
+        saveContribution(publishingArtist, songMusicalEntity, MAIN_VOCAL_ROLE);
+
+        // add song-specific contributors
+        List<ArtistContributionSummaryDto> songContributors = songDto.getContributors();
+        if (songContributors != null) {
+            for (ArtistContributionSummaryDto contributorDto : songContributors) {
+                if (contributorDto.getId() == null) continue;
+                
+                Artist contributor = artistService.getArtistById(contributorDto.getId());
+                
+                // first add to song
+                saveContribution(contributor, songMusicalEntity, contributorDto.getRole());
+                
+                // then add to album if not already added
+                if (!albumContributorIds.contains(contributor.getId())) {
+                    saveContribution(contributor, album.getMusicalEntities(), contributorDto.getRole());
+                    albumContributorIds.add(contributor.getId());
+                }
+            }
+        }
+    }
+
+
+
+    private String saveCoverPhoto(MultipartFile coverPhoto) throws IOException {
+        String contentType = coverPhoto.getContentType();
+        if (contentType == null || !contentType.startsWith("image/")) {
+            throw InvalidFileException.invalidType();
+        }
+
+        if (coverPhoto.getSize() > MAX_FILE_SIZE) {
+            throw InvalidFileException.tooLarge(MAX_FILE_SIZE);
+        }
+
+        String filename = UUID.randomUUID() + "-" + coverPhoto.getOriginalFilename();
+        Path path = Paths.get("uploads/profile-pictures", filename);
+        Files.copy(coverPhoto.getInputStream(), path);
+        return filename;
+    }
+}
Index: frontend/src/pages/PublishSong.tsx
===================================================================
--- frontend/src/pages/PublishSong.tsx	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ frontend/src/pages/PublishSong.tsx	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -23,5 +23,5 @@
 	const [genre, setGenre] = useState("");
 	const [link, setLink] = useState("");
-	const [_coverFile, setCoverFile] = useState<File | null>(null); // TODO: Use in API call
+	const [coverFile, setCoverFile] = useState<File | null>(null);
 	const [coverPreview, setCoverPreview] = useState<string | null>(null);
 	const [contributors, setContributors] = useState<Contributor[]>([]);
@@ -93,5 +93,4 @@
 				`/users/search?type=ARTIST&q=${encodeURIComponent(query)}&limit=5`,
 			);
-
 			const filtered = response.data.filter(
 				(artist: ArtistSearchResult) =>
@@ -129,5 +128,5 @@
 		}
 
-		if (contributors.some((c) => c.username === artist.username)) {
+		if (contributors.some((c) => c.id === artist.id)) {
 			toast.error("This contributor is already added");
 			return;
@@ -137,5 +136,5 @@
 			...contributors,
 			{
-				username: artist.username,
+				id: artist.id,
 				fullName: artist.fullName,
 				role: selectedContributorRole,
@@ -148,6 +147,6 @@
 
 	// Remove contributor
-	const handleRemoveContributor = (username: string) => {
-		setContributors(contributors.filter((c) => c.username !== username));
+	const handleRemoveContributor = (id: number) => {
+		setContributors(contributors.filter((c) => c.id !== id));
 	};
 
@@ -242,5 +241,5 @@
 		}
 
-		if (song.contributors.some((c) => c.username === artist.username)) {
+		if (song.contributors.some((c) => c.id === artist.id)) {
 			toast.error("This contributor is already added");
 			return;
@@ -249,5 +248,5 @@
 		const role = albumSongSelectedRole[songIndex] || ROLE_OPTIONS[0].value;
 		const newContributor: Contributor = {
-			username: artist.username,
+			id: artist.id,
 			fullName: artist.fullName,
 			role,
@@ -263,21 +262,16 @@
 	};
 
-	const handleRemoveAlbumSongContributor = (
-		songIndex: number,
-		username: string,
-	) => {
+	const handleRemoveAlbumSongContributor = (songIndex: number, id: number) => {
 		const song = albumSongs[songIndex];
 		handleAlbumSongChange(
 			songIndex,
 			"contributors",
-			song.contributors.filter((c) => c.username !== username),
+			song.contributors.filter((c) => c.id !== id),
 		);
 	};
 
-	// Form submission
 	const handleSubmit = async (e: React.FormEvent) => {
 		e.preventDefault();
 
-		// Validation
 		if (!title.trim()) {
 			toast.error("Please enter a title");
@@ -295,5 +289,4 @@
 			}
 		} else {
-			// Album validation
 			for (let i = 0; i < albumSongs.length; i++) {
 				if (!albumSongs[i].title.trim()) {
@@ -311,13 +304,53 @@
 
 		try {
-			// TODO: replace with API call
-			// const formData = new FormData();
-			// formData.append('title', title);
-			// formData.append('genre', genre);
-			// if (coverFile) formData.append('cover', coverFile);
-			// ...
-
-			// Simulate API call
-			await new Promise((resolve) => setTimeout(resolve, 1000));
+			const formData = new FormData();
+			formData.append("title", title);
+			formData.append("genre", genre);
+			if (coverFile) formData.append("cover", coverFile);
+
+			// Helper to append contributors in indexed format for Spring @ModelAttribute
+			const appendContributors = (
+				formData: FormData,
+				contributors: Contributor[],
+				prefix: string,
+			) => {
+				contributors.forEach((c, i) => {
+					formData.append(`${prefix}[${i}].id`, c.id.toString());
+					formData.append(`${prefix}[${i}].artistName`, c.fullName);
+					formData.append(`${prefix}[${i}].role`, c.role);
+				});
+			};
+
+			if (releaseType === "album") {
+				// Append album songs in indexed format
+				albumSongs.forEach((song, songIndex) => {
+					formData.append(`albumSongs[${songIndex}].title`, song.title);
+					formData.append(`albumSongs[${songIndex}].link`, song.link);
+					// Append nested contributors for each song
+					song.contributors.forEach((c, contribIndex) => {
+						formData.append(
+							`albumSongs[${songIndex}].contributors[${contribIndex}].id`,
+							c.id.toString(),
+						);
+						formData.append(
+							`albumSongs[${songIndex}].contributors[${contribIndex}].artistName`,
+							c.fullName,
+						);
+						formData.append(
+							`albumSongs[${songIndex}].contributors[${contribIndex}].role`,
+							c.role,
+						);
+					});
+				});
+				await axiosInstance.post("/musical-entity/publish/album", formData, {
+					headers: { "Content-Type": "multipart/form-data" },
+				});
+			} else {
+				formData.append("link", link);
+				appendContributors(formData, contributors, "contributors");
+				await axiosInstance.post("/musical-entity/publish/song", formData, {
+					headers: { "Content-Type": "multipart/form-data" },
+				});
+			}
 
 			toast.success(
@@ -612,5 +645,5 @@
 									{contributors.map((contributor) => (
 										<div
-											key={contributor.username}
+											key={contributor.id}
 											className="flex items-center gap-2 bg-[#282828] rounded-full py-1.5 px-3"
 										>
@@ -626,7 +659,5 @@
 											<button
 												type="button"
-												onClick={() =>
-													handleRemoveContributor(contributor.username)
-												}
+												onClick={() => handleRemoveContributor(contributor.id)}
 												className="text-gray-400 hover:text-red-400 transition-colors"
 											>
@@ -687,5 +718,5 @@
 													searchResults.map((artist) => (
 														<button
-															key={artist.username}
+															key={artist.id}
 															type="button"
 															onClick={() => handleAddContributor(artist)}
@@ -808,5 +839,5 @@
 												{song.contributors.map((contributor) => (
 													<div
-														key={contributor.username}
+														key={contributor.id}
 														className="flex items-center gap-2 bg-[#1a1a2e] rounded-full py-1 px-2.5 text-sm"
 													>
@@ -826,5 +857,5 @@
 																handleRemoveAlbumSongContributor(
 																	index,
-																	contributor.username,
+																	contributor.id,
 																)
 															}
@@ -902,5 +933,5 @@
 																		(artist) => (
 																			<button
-																				key={artist.username}
+																				key={artist.id}
 																				type="button"
 																				onClick={() =>
Index: frontend/src/utils/types.ts
===================================================================
--- frontend/src/utils/types.ts	(revision 92db381e21eb0f44de317188e162a4362d50faf0)
+++ frontend/src/utils/types.ts	(revision db5fb23a9c7d281ecb48c2ebf32e7ff4d9a24c38)
@@ -121,5 +121,5 @@
 
 export interface Contributor {
-	username: string;
+	id: number;
 	fullName: string;
 	role: string;
@@ -127,4 +127,5 @@
 
 export interface ArtistSearchResult {
+	id: number;
 	username: string;
 	fullName: string;
