Build Your Own Music List Widget for WordPress

I listen to a lot of music while I work. I mostly listen via Apple Music. My goal here is to share what I’m listening to on my own site. I also wanted to control the display of this information. I find music widgets for WordPress overly branded and ugly, so I built my own widget for WordPress. I’ve updated it a few times over the years, but I have been using it on my site for a long time. Here’s how you can build your own Music List for WordPress.

Requirements

  1. You’ll need a Last.fm account.
  2. You’ll need a Last.fm API key.

Last.fm does all the real work here. They have an awesome free API, and we will be using a REST request to grab our recently played songs. You can listen to music with Last.fm if you prefer, but there are other options below.

Optional

  1. A Spotify account. Once you sign up for both Last.fm and Spotify, you’ll need to turn on scrobbling to Last.fm within Spotify’s preferences. Spotify will need your to enter your Last.fm username and password.
  2. An Apple Music Account. To use an Apple Music account, you will need to download the Last.fm App to grab your recently listened tracks on a computer. There are iOS and Android apps for mobile, too.
  3. You can request your recently listened tracks directly from Spotify or Apple Music. I chose to use Last.fm’s API because it pulls from both sources. Please note — you need Apple developer credentials to request recent tracks directly from Apple Music (currently $99/year).

Onward

Alright, let’s get down to business — we’re building a WordPress widget that will live inside your theme. I will not get very deep into how to create widgets for WordPress — check out WordPress.org for more information. I will be focusing on how we acquire and output the data we will request from Last.fm.

TL;DR, a copy/paste of the code on GitHub into your theme’s functions.php file will have you rolling in no time.

<?php
class Music_List extends WP_Widget {

  /**
	 * Sets up the widget name, description, etc
	 */
  function __construct() {
    $widget_ops = array( 
      'classname' => 'widget_block music-list',
      'description' => __( 'Displays a list of recenty played music from Last.fm, Spotify, or Apple Music.', 'your_text_domain' ),
      'customize_selective_refresh' => true
    );
    parent::__construct( 
      'Music_List', 
      __( 'Music List', 'your_text_domain' ), 
      $widget_ops
    );

    add_action( 'widgets_init', function() { 
      register_widget( 'Music_List' ); 
    } );
  }

We fire it up with our “Music_List” class, and build our widget — it is named “Music List” and will appear on the Appearance > Widgets page in the admin. Depending on your theme, you will be able to display the widget in the footer or the sidebar.

  /**
	 * Outputs the content of the widget
	 *
	 * @param array $args
	 * @param array $instance The widget options
	 */
  public function widget( $args, $instance ) {
    extract( $args, EXTR_SKIP );
    // Set default widget options in case they are empty
    $title = empty( $instance['widget_title'] ) ? __( 'Listening to This', 'your_text_domain' ) : apply_filters( 'widget_title', $instance['widget_title'] );
    $amount = empty( $instance['display_amount'] ) ? '3' : apply_filters( 'display_amount', $instance['display_amount'] );
    $latest = '';
    $error = __( 'We are not listening to anything at the moment.', 'your_text_domain' );

    if ( isset( $before_widget ) ) { echo $before_widget . "\n"; }
    if ( !empty( $title ) ) { echo $before_title . $title . $after_title; }

The widget function determines the front end output, and it’s a beast, so I’ll break it up into sections. We set up default values for the $title, $amount, and $latest variables first. These values will be set in our widget’s options.

We output the widget $title with a default value of “Listening to This,” in case the widget_title option was not set in the WordPress admin.

We also provide a default value of “3” for $amount — this is the number of recently listened songs the widget will display.

$latest is left empty for now, but will hold an array of our tracks from Last.fm.

$error contains a message to display on the front end if something has gone wrong.

Customize the messaging as you see fit, and make sure to replace ‘your_text_domain’ with your theme’s text domain.

  // Set a nag message if Last.fm credentials are missing
    if ( empty( $instance['lastfm_username'] ) || empty( $instance['lastfm_api_key'] ) ) : ?>

      <p><?php _e( 'Please enter your ', 'your_text_domain' ); ?><a href="https://www.last.fm/" target="_blank">Last.fm</a><?php _e( ' credentials to use this widget.', 'your_text_domain' ); ?></p>

    <?php else :

We begin our list output by checking if the Last.fm username and API key options were populated on the back end. If either value is missing, a message will display on the front end to notify users that these credentials are required.

      // Setup the Last.fm request
      $username = apply_filters( 'lastfm_username', $instance['lastfm_username'] );
      $api_key = apply_filters( 'lastfm_api_key', $instance['lastfm_api_key'] );
      $request_url = 'https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=' . $username . '&limit=' . $amount . '&api_key=' . $api_key . '&format=json';
      $ch = curl_init();
      curl_setopt( $ch, CURLOPT_URL, $request_url );
      curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
      curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 4 );
      $tracks = curl_exec( $ch );
      $latest = ( curl_error( $ch ) ) ? 'broken' : $tracks;
      curl_close( $ch );

If the Last.fm credentials are present, we build a REST API request loop:

  1. We set a variable ($latest) containing the return JSON of our Last.fm request (recently listened songs).
  2. If the request returns an error, Last.fm is probably down, so we set $latest to “broken.”
      // If there's an error, output a message
      if ( $latest == 'broken' ) : ?>

        <p><?php echo $error; ?></p>

      <?php else :

If $latest is set to ‘broken,’ we output the $error message we set at the start of our widget class.

        // Setup the data
        $latest = json_decode( $latest, true );
        // If we have tracks, let's go!
        if ( $latest['recenttracks'] && $latest['recenttracks']['track'] ) :
          $i = 0;
          // Begin to loop over the tracks, and cherrypick data
          foreach ( $latest['recenttracks']['track'] as $track ) :
            // Setup variables
            date_default_timezone_set("America/New_York"); // Set this to your time zone
            $artist = ( isset( $track['artist']['#text'] ) ) ? $track['artist']['#text'] : ''; // Name of the artist
            $title = ( isset( $track['name'] ) ) ? $track['name'] : ''; // Name of the song
            $url = ( isset( $track['url'] ) ) ? $track['url'] : ''; // Link to the track on Last.fm
            $albumArt = ( isset( $track['image'][2]['#text'] ) ) ? $track['image'][2]['#text'] : ''; // Large-sized album art
            $nowPlaying = ( isset( $track['@attr']['nowplaying'] ) ) ? true : ''; // True if the song is playing right now
            $datePlayed = ( isset( $track['date']['uts'] ) ) ? human_time_diff( $track['date']['uts'] ) : ''; // Date/Time played
            // Output the track data... ?>

If everything is working as expected, we decode the JSON data we retrieved from Last.fm, converting the JSON to a PHP array. We then run a test to determine if the data contains our tracks. If $latest contains our tracks, we loop through each track and setup our values (all commented above). We also set up a counter ($i = 0) that we will use output the correct number of songs (specified in $amount). Please note that I’ve set the default timezone to “America/New_York” — you will need to set this to your own timezone for the date/time since played to display correctly.

          <div class="wp-block-media-text alignwide" style="grid-template-columns:25% auto; margin-top: 0; margin-bottom: 3rem;">
            <figure class="wp-block-media-text__media">
              <a style="text-decoration: none;" href="<?php esc_attr_e( $url ); ?>" target="_blank">
                <?php if ( $albumArt !== '' ) : ?>
                  <img class="size-thumbnail" style="border-radius: 50%;" src="<?php esc_attr_e( $albumArt ); ?>" alt="<?php printf( __( 'Album Art for %s', 'your_text_domain' ), $title ); ?>">
                <?php else : ?>
                  <div class="size-thumbnail" style="width: 175px; height: 175px; border-radius: 50%; font-size: 70px; background-color: black; color: white; display: flex; align-items: center; justify-content: center;">♪</div>
                <?php endif; ?>
              </a>
            </figure>
            <div class="wp-block-media-text__content" style="padding-top: 0; padding-bottom: 0;">
              <p class="has-normal-font-size">
                <strong><?php echo $artist; ?></strong>
                <br>
                <a href="<?php esc_attr_e( $url ); ?>" target="_blank"><?php echo $title; ?></a>
                <br>
                <small style="opacity: 0.7;">
                <?php ( $nowPlaying == true ) ? _e( 'Listening now', 'your_text_domain' ) : printf( __( '%s ago', 'your_text_domain' ), $datePlayed ); ?>
                </small>
              </p>
            </div>
          </div>
        
          <?php
          // Increment for each track
          $i++;
          // Stop when we hit the limit set in widget options (default = 3)
          if ( $i == $amount ) { 
            break;
          }
          // End track loop
          endforeach;

Now we begin creating each item. I’ve chosen a media object to display the song’s album art and information side-by-side. I am using WordPress’ default media object block CSS classes here so that styles will match any theme built for WordPress 5+. I’ve added additional minimal inline styles to scale down the overall vertical height of the widget and to make the album art circular. Please adjust these styles to your own preference. If you are using an outdated version of WordPress, you will need to supply your own CSS altogether.

The <figure> item contains the album art. If the album art doesn’t exist, we use a placeholder. I’ve included an HTML entity of a music note and a simple black background. Please customize this placeholder if you prefer. It will only appear if no album art is available for the song.

The second half of our media object (.wp-block-media-text__content) contains the artist name, the song title, and the time the song was played. If the song is currently playing, “Listening now” is output, if the song has finished playing, the time difference between now and when the song was played (<time diff> ago) is displayed. When the counter equals $amount, we stop outputting songs.

        else : 
        // There are no tracks, so output the error message... ?>

        <p><?php echo $error; ?></p>

        <?php endif; ?>
      <?php endif; ?>
    <?php endif; ?>
  
    <?php if ( isset( $after_widget ) ) { echo "\n\t\t" . $after_widget . "\n"; }
  }

We include one final fallback. If the JSON from Last.fm is not formatted as expected or if it contains no tracks, we output the $error message established at the start of our widget class. This concludes the front end output — our list is done!

  /**
	 * Outputs the options form on admin
	 *
	 * @param array $instance The widget options
	 */
  public function form( $instance ) {
    // Setup options
    $instance = wp_parse_args( ( array ) $instance, array( 'widget_title' => '', 'lastfm_username' => '', 'lastfm_api_key' => '', 'display_amount' => '' ) );
    $title = $instance['widget_title'];
    $username = $instance['lastfm_username'];
    $api_key = $instance['lastfm_api_key'];
    $amount = $instance['display_amount'];
    ?>

    <p>
      <label for="<?php esc_attr_e( $this->get_field_id( 'widget_title' ) ); ?>"><?php _e( 'Title:', 'your_text_domain' ); ?>
        <input class="widefat" id="<?php esc_attr_e( $this->get_field_id( 'widget_title' ) ); ?>" name="<?php esc_attr_e( $this->get_field_name( 'widget_title' ) ); ?>" type="text" value="<?php esc_attr_e( $title ); ?>">
      </label>
    </p>

// Abridged for brevity, full code is on Github

The rest of our Music_List class builds and updates the widget options in the WordPress admin. This code is pretty straightforward. We create four fields in the “form” function, and update the options in the “update” function. The four options include:

  1. widget_title the title that appears at the top of the music list on the front end. Defaults to “Listening to This.”
  2. lastfm_username the Last.fm user name used in the API request. No default.
  3. lastfm_api_key the Last.fm API key used in the API request. No default.
  4. display_amount the number of songs to display in the widget (up to 5). Defaults to 3.

There you have it — a recently played music list widget that you can style to match your site! The code on Github is yours to use as you see fit. No credit is necessary. Happy trails!