This guide creates a simple client-side application that uses the Spotify Web API to get user profile data. We'll show both TypeScript and JavaScript code snippets, make sure to use the code that is correct for your application.
External applications can use the Spotify Web API to retrieve Spotify content, such as song data, album data and playlists. However, in order to access user-related data with the Spotify Web API, an application must be authorized by the user to access that particular information.
PrerequisitesTo work through this guide you'll need:
Login to the Spotify Developer Dashboard. If necessary, accept the latest Developer Terms of Service to complete your account set up.
Creating a Spotify appWe will need to register a new app to generate valid credentials - we'll use these credentials later to perform API calls. Follow the apps guide to learn how to create an app and generate the necessary credentials.
Once you've created your app, make a note of your client_id
.
This app uses Vite as a development server. We'll scaffold a new project with the Vite create
command and use their default template to give us a basic app:
_10
npm create vite@latest spotify-profile-demo -- --template vanilla-ts
Select y
when it prompts you to install Vite.
Change directory to the new app directory that Vite just created and start the development server:
_10
cd spotify-profile-demo
The default Vite template creates some files that we won't need for this demo, so you can delete all of the files in ./src/
and ./public/
This demo is going to be a single page application that runs entirely in the browser. We're going to replace the provided index.html
file with a simple HTML page that constitutes the user interface to display the user's profile data.
Start by deleting the content of the index.html
file and replacing it with a html
and head
tag that references a TypeScript/JavaScript file (src/script.ts
, or src/script.js
, we'll create this file later).
_16
<meta charset="utf-8">
_16
<title>My Spotify Profile</title>
_16
<script src="src/script.ts" type="module"></script>
_16
<!-- Note- We're referring directly to the TypeScript file,
_16
and we're using the `type="module"` attribute.
_16
Vite will transpile our TypeScript to JavaScript
_16
so that it can run in the browser. -->
Inside the body
, we'll add some markup to display the profile data:
_13
<h1>Display your Spotify profile data</h1>
_13
<section id="profile">
_13
<h2>Logged in as <span id="displayName"></span></h2>
_13
<span id="avatar"></span>
_13
<li>User ID: <span id="id"></span></li>
_13
<li>Email: <span id="email"></span></li>
_13
<li>Spotify URI: <a id="uri" href="#"></a></li>
_13
<li>Link: <a id="url" href="#"></a></li>
_13
<li>Profile Image: <span id="imgUrl"></span></li>
Some elements in this block have id
attributes. We'll use these to replace the element's text with the data we fetch from the Web API.
We're going to use the Web API to get the user's profile data. We'll use the authorization code flow with PKCE to get an access token, and then use that token to call the API.
How it worksCreate a src/script.ts
or src/script.js
file and add the following code:
_26
const clientId = "your-client-id-here"; // Replace with your client id
_26
const code = undefined;
_26
redirectToAuthCodeFlow(clientId);
_26
const accessToken = await getAccessToken(clientId, code);
_26
const profile = await fetchProfile(accessToken);
_26
async function redirectToAuthCodeFlow(clientId: string) {
_26
// TODO: Redirect to Spotify authorization page
_26
async function getAccessToken(clientId: string, code: string) {
_26
// TODO: Get access token for code
_26
async function fetchProfile(token: string): Promise<any> {
_26
// TODO: Call Web API
_26
function populateUI(profile: any) {
_26
// TODO: Update UI with profile data
This is the outline of our application.
On the first line there is a clientId
variable - you'll need to set this variable to the client_id
of the Spotify app you created earlier.
The code now needs to be updated to redirect the user to the Spotify authorization page. To do this, let's write the redirectToAuthCodeFlow
function:
_35
export async function redirectToAuthCodeFlow(clientId: string) {
_35
const verifier = generateCodeVerifier(128);
_35
const challenge = await generateCodeChallenge(verifier);
_35
localStorage.setItem("verifier", verifier);
_35
const params = new URLSearchParams();
_35
params.append("client_id", clientId);
_35
params.append("response_type", "code");
_35
params.append("redirect_uri", "http://127.0.0.1:5173/callback");
_35
params.append("scope", "user-read-private user-read-email");
_35
params.append("code_challenge_method", "S256");
_35
params.append("code_challenge", challenge);
_35
document.location = `https://accounts.spotify.com/authorize?${params.toString()}`;
_35
function generateCodeVerifier(length: number) {
_35
let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
_35
for (let i = 0; i < length; i++) {
_35
text += possible.charAt(Math.floor(Math.random() * possible.length));
_35
async function generateCodeChallenge(codeVerifier: string) {
_35
const data = new TextEncoder().encode(codeVerifier);
_35
const digest = await window.crypto.subtle.digest('SHA-256', data);
_35
return btoa(String.fromCharCode.apply(null, [...new Uint8Array(digest)]))
In this function, a new URLSearchParams
object is created, and we add the client_id
, response_type
, redirect_uri
and scope
parameters to it. The scope parameter is a list of permissions that we're requesting from the user. In this case, we're requesting the user-read-private
and user-read-email
scopes - these are the scopes that allow us to fetch the user's profile data.
The redirect_uri
parameter is the URL that Spotify will redirect the user back to after they've authorized the application. In this case, we're using a URL that points to our local Vite dev server.
You need to make sure this URL is listed in the Redirect URIs section of your Spotify Application Settings in your Developer Dashboard.
You will also notice that we are generating PKCE verifier and challenge data, we're using this to verify that our request is authentic. We're using local storage to store the verifier data, which works like a password for the token exchange process.
To prevent the user from being stuck in a redirect loop when they authenticate, we need to check if the callback contains a code
parameter. To do this, the first three lines of code in the file are modified like this:
_11
const clientId = "your_client_id";
_11
const params = new URLSearchParams(window.location.search);
_11
const code = params.get("code");
_11
redirectToAuthCodeFlow(clientId);
_11
const accessToken = await getAccessToken(clientId, code);
_11
const profile = await fetchProfile(accessToken);
In order to make sure that the token exchange works, we need to write the getAccessToken
function.
_19
export async function getAccessToken(clientId: string, code: string): Promise<string> {
_19
const verifier = localStorage.getItem("verifier");
_19
const params = new URLSearchParams();
_19
params.append("client_id", clientId);
_19
params.append("grant_type", "authorization_code");
_19
params.append("code", code);
_19
params.append("redirect_uri", "http://127.0.0.1:5173/callback");
_19
params.append("code_verifier", verifier!);
_19
const result = await fetch("https://accounts.spotify.com/api/token", {
_19
headers: { "Content-Type": "application/x-www-form-urlencoded" },
_19
const { access_token } = await result.json();
In this function, we load the verifier from local storage and using both the code returned from the callback and the verifier to perform a POST to the Spotify token API. The API uses these two values to verify our request and it returns an access token.
Now, if we run npm run dev
, and navigate to http://127.0.0.1:5173 in a browser, we'll be redirected to the Spotify authorization page. If we authorize the application, we'll be redirected back to our application, but no data will be fetched and displayed.
To fix this, we need to update the fetchProfile
function to call the Web API and get the profile data. Update the fetchProfile
function:
_10
async function fetchProfile(token: string): Promise<any> {
_10
const result = await fetch("https://api.spotify.com/v1/me", {
_10
method: "GET", headers: { Authorization: `Bearer ${token}` }
_10
return await result.json();
In this function, a call is made to https://api.spotify.com/v1/me
using the browser's Fetch API to get the profile data. The Authorization
header is set to Bearer ${token}
, where token
is the access token that we got from the https://accounts.spotify.com/api/token
endpoint.
If we add a console.log
statement to the calling code we can see the profile data that is returned from the API in the browser's console:
_10
const profile = await fetchProfile(token);
_10
console.log(profile); // Profile data logs to console
Finally, we need to update the populateUI
function to display the profile data in the UI. To do this, we'll use the DOM to find our HTML elements and update them with the profile data:
_15
function populateUI(profile: any) {
_15
document.getElementById("displayName")!.innerText = profile.display_name;
_15
if (profile.images[0]) {
_15
const profileImage = new Image(200, 200);
_15
profileImage.src = profile.images[0].url;
_15
document.getElementById("avatar")!.appendChild(profileImage);
_15
document.getElementById("id")!.innerText = profile.id;
_15
document.getElementById("email")!.innerText = profile.email;
_15
document.getElementById("uri")!.innerText = profile.uri;
_15
document.getElementById("uri")!.setAttribute("href", profile.external_urls.spotify);
_15
document.getElementById("url")!.innerText = profile.href;
_15
document.getElementById("url")!.setAttribute("href", profile.href);
_15
document.getElementById("imgUrl")!.innerText = profile.images[0]?.url ?? '(no profile image)';
You can now run your code by running npm run dev
in the terminal and navigating to http://127.0.0.1:5173
in your browser.
At the moment, even though we're using TypeScript, we don't have any type safety around the data being returned from the Web API. To improve this, we can create a UserProfile
interface to describes the data that we expect to be returned from the API. Adding an interface will define the shape of the object that we're expecting, this will make using the data type-safe and will allow for type prompts while coding, making a more pleasant developer experience if you extend this project in future.
To do this, create a new file called types.d.ts
in the src
folder and add the following code:
_23
interface UserProfile {
_23
display_name: string;
_23
filter_enabled: boolean,
_23
filter_locked: boolean
_23
external_urls: { spotify: string; };
_23
followers: { href: string; total: number; };
We can now update our calling code to expect these types:
_10
async function fetchProfile(token: string): Promise<UserProfile> {
_10
function populateUI(profile: UserProfile) {
You can view and fork the final code for this demo on GitHub: Get User Profile Repository.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4