Snippets

Create a Filterable Portfolio with WordPress and Jetpack

Have you ever wanted to add a filterable portfolio to your personal site to showcase your content? In this post, we’re going to include simple scripts for you to add to your existing theme to help make it easy to add a filterable portfolio to your project.

Tools you’ll need:

Notes on Isotope:

The great thing about Isotope is that it is GPL licensed for Open Source projects. However, if your site is a commercial project, I would highly recommend purchasing a commercial license. This will help the developer to continue to support Isotope.

Step 1:

The first thing we need to do is setup Jetpack and enable the ‘Portfolio Custom Content Type’. To do this, while in your WordPress dashboard, go to Jetpack > Settings and activate the ‘Custom Content Type’ module.

jetpack-settings

Next, we need to make sure your theme supports the Portfolio Custom Content Type. To do this, we need to add the following code to your theme, during after_setup_theme:

add_theme_support( 'jetpack-portfolio' );

Once that is added, you’ll see that a new menu item has been added to your WordPress dashboard:

portfolio-menu-item

Step 2:

We’ll need to properly enqueue all needed files for your theme. You can add this to wherever your theme is already enqueuing files.

wp_register_script( 'wds-isotope', get_template_directory_uri() . '/js/isotope.pkgd.min.js', array( 'jquery' ), $version, true );
wp_register_script( 'wds-portfolio', get_template_directory_uri() . '/js/wds-portfolio.js', array( 'jquery' ), $version, true );

if ( is_page_template( 'homepage-template.php' ) ) {
    wp_enqueue_script( 'wds-isotope' );
    wp_enqueue_script( 'wds-portfolio' );
}

Notice that we are using a conditional to only enqueue our scripts on any page using our ‘homepage-template.php’ page template. This is so that we are not enqueuing our JS files site wide. Now that they have been enqueued, download Isotope and add it to your theme’s JavaScript folder. Make sure this folder is named ‘js’. If not, you’ll need to update the above script and change the location of where it is being enqueued. Once you have added the Isotope file to your JS folder, create a new file and name it ‘wds-portfolio.js’. Add the following jQuery code and save the file into your ‘js’ folder.

/**
 * Portfolio functions
 */
( function( $ ) {
     $( window ).load( function() {

        // Portfolio filtering
        var $container = $( '.portfolio' );

        $container.isotope( {
            filter: '*',
            layoutMode: 'fitRows',
            resizable: true, 
          } );

        // filter items when filter link is clicked
        $( '.portfolio-filter li' ).click( function(){
            var selector = $( this ).attr( 'data-filter' );
                $container.isotope( { 
                    filter: selector,
                } );
          return false;
        } );
    } );
} )( jQuery );

Step 3:

Now that we have enqueued your files, let’s create a page template that is specific for your portfolio, and one to show everything on the homepage.

Create a new file and name it ‘homepage-template.php’. Copy and paste the following code:

<?php
/*
 * Template Name: Homepage Template
 *
 * @package wds_portfolio
*/

get_header();
?>

<div id="page" class="hfeed site">
<div id="main" class="site-main">
    
        <?php if ( ! get_theme_mod( 'wds_portfolio_hide_portfolio_page_content' ) ) : ?>
            <?php while ( have_posts() ) : the_post(); ?>

                <?php the_title( '<header class="page-header"><h1 class="page-title">', '</h1></header>' ); ?>

                <div class="page-content">
                    <?php
                        the_content();
                        wp_link_pages( array(
                            'before'      => '<div class="page-links"><span class="page-links-title">' . __( 'Pages:', 'wds_portfolio' ) . '</span>',
                            'after'       => '</div>',
                            'link_before' => '<span>',
                            'link_after'  => '</span>',
                        ) );
                    ?>
                </div><!-- .page-content -->

            <?php endwhile; // end of the loop. ?>
        <?php endif; ?>

        <div class="portfolio-filter">
            <ul>
                <li id="filter--all" class="filter active" data-filter="*"><?php _e( 'View All', 'wds_portfolio' ) ?></li>
                <?php 
                    // list terms in a given taxonomy
                    $taxonomy = 'jetpack-portfolio-type';
                    $tax_terms = get_terms( $taxonomy );

                    foreach ( $tax_terms as $tax_term ) {
                    echo '<li class="filter" data-filter=".'. $tax_term->slug.'">' . $tax_term->slug .'</li>';
                    }
                ?>
            </ul>
        </div>

        <div class="portfolio">
            <?php
                if ( get_query_var( 'paged' ) ) :
                    $paged = get_query_var( 'paged' );
                elseif ( get_query_var( 'page' ) ) :
                    $paged = get_query_var( 'page' );
                else :
                    $paged = -1;
                endif;

                $posts_per_page = get_option( 'jetpack_portfolio_posts_per_page', '-1' );

                $args = array(
                    'post_type'      => 'jetpack-portfolio',
                    'paged'          => $paged,
                    'posts_per_page' => $posts_per_page,
                );

                $project_query = new WP_Query ( $args );

                if ( post_type_exists( 'jetpack-portfolio' ) && $project_query -> have_posts() ) :

                    while ( $project_query -> have_posts() ) : $project_query -> the_post();

                        get_template_part( 'content', 'portfolio' );

                    endwhile;

                    wds_portfolio_paging_nav( $project_query->max_num_pages );

                    wp_reset_postdata();

                else :
            ?>

                <section class="no-results not-found">
                    <header class="page-header">
                        <h1 class="page-title"><?php _e( 'No Project Found', 'wds_portfolio' ); ?></h1>
                    </header><!-- .page-header -->

                    <div class="page-content">
                        <?php if ( current_user_can( 'publish_posts' ) ) : ?>

                            <p><?php printf( __( 'Ready to publish your first project? <a href="%1$s">Get started here</a>.', 'wds_portfolio' ), esc_url( admin_url( 'post-new.php?post_type=jetpack-portfolio' ) ) ); ?></p>

                        <?php else : ?>

                            <p><?php _e( 'It seems we can&rsquo;t find what you&rsquo;re looking for. Perhaps searching can help.', 'wds_portfolio' ); ?></p>

                        <?php endif; ?>
                    </div><!-- .page-content -->
                </section><!-- .no-results -->
            <?php endif; ?>
            </div><!-- .portfolio -->

<?php get_footer(); ?>

Next, we’ll need to create a ‘content-portfolio.php’ template file so we know what portfolio item to grab. Create a new file and name it ‘content-portfolio.php’. Here is our code for the content file:

Edit: See Stefan’s comment for necessary change for line 19.

<?php
/**
 * The template for displaying Projects on index view
 *
 * @package wds-portfolio
 */

// get Jetpack Portfolio taxonomy terms for portfolio filtering
$terms = get_the_terms( $post->ID, 'jetpack-portfolio-type' );
                        
if ( $terms && ! is_wp_error( $terms ) ) : 

    $filtering_links = array();

    foreach ( $terms as $term ) {
        $filtering_links[] = $term->slug;
    }
                        
    $filtering = join( ", ", $filtering_links );
?>

<article id="post-<?php the_ID(); ?>" <?php post_class( $filtering ); ?>>
    <a href="<?php the_permalink(); ?>" rel="bookmark" class="image-link" tabindex="-1">
        <?php  if ( '' != get_the_post_thumbnail() ) : ?>
                <?php the_post_thumbnail( 'wds-portfolio-img' ); ?>
        <?php endif; ?>
    </a>
</article><!-- #post-## -->

<?php
endif;

What this code does is include the page content of whatever the page you create that uses this page template. We are also grabbing all content that is published with the Portfolio Custom Content Type.

The following sections are necessary for filtering our portfolio items.

<div class="portfolio-filter">
    <ul>
        <li id="filter--all" class="filter active" data-filter="*"><?php _e( 'View All', 'wds_portfolio' ) ?></li>
        <?php 
            // list terms in a given taxonomy
            $taxonomy = 'jetpack-portfolio-type';
            $tax_terms = get_terms( $taxonomy );

            foreach ( $tax_terms as $tax_term ) {
            echo '<li class="filter" data-filter=".'. $tax_term->slug.'">' . $tax_term->slug .'</li>';
            }
        ?>
    </ul>
</div>

The above code block grabs all taxonomy terms from the Portfolio Custom Content Type in a list. This will allow us to filter our portfolio items.

The next block of code will display our portfolio posts:

<div class="portfolio">
<?php
    if ( get_query_var( 'paged' ) ) :
        $paged = get_query_var( 'paged' );
    elseif ( get_query_var( 'page' ) ) :
        $paged = get_query_var( 'page' );
    else :
        $paged = 1;
    endif;

    $posts_per_page = get_option( 'jetpack_portfolio_posts_per_page', '-1' );

    $args = array(
        'post_type'      => 'jetpack-portfolio',
        'paged'          => $paged,
        'posts_per_page' => $posts_per_page,
    );

    $project_query = new WP_Query ( $args );

    if ( post_type_exists( 'jetpack-portfolio' ) && $project_query -> have_posts() ) :

        while ( $project_query -> have_posts() ) : $project_query -> the_post();

            get_template_part( 'content', 'portfolio' );

        endwhile;

        wds_portfolio_paging_nav( $project_query->max_num_pages );

        wp_reset_postdata();

    else :
?>

    <section class="no-results not-found">
        <header class="page-header">
            <h1 class="page-title"><?php _e( 'No Project Found', 'wds_portfolio' ); ?></h1>
        </header><!-- .page-header -->

        <div class="page-content">
            <?php if ( current_user_can( 'publish_posts' ) ) : ?>

                <p><?php printf( __( 'Ready to publish your first project? <a href="%1$s">Get started here</a>.', 'wds_portfolio' ), esc_url( admin_url( 'post-new.php?post_type=jetpack-portfolio' ) ) ); ?></p>

            <?php else : ?>

                <p><?php _e( 'It seems we can&rsquo;t find what you&rsquo;re looking for. Perhaps searching can help.', 'wds_portfolio' ); ?></p>

            <?php endif; ?>
        </div><!-- .page-content -->
    </section><!-- .no-results -->
<?php endif; ?>
</div><!-- .portfolio -->

The key to the above block are the class names. The ‘portfolio’ class is being called in our wds-portfolio.js file. This tells our jQuery that all posts under this class are to be filtered.

The above code block also includes a specific query that grabs all posts from our ‘Portfolio Custom Content Type’.

Step 4:

Now it’s time to add some styling. The following styling will cover the filter list items and your portfolio items. Feel free to adjust it to match the look of your website.

.portfolio-filter {
  font-size: 14px;
  font-size: 1.4rem;
  overflow: hidden;
  text-transform: uppercase;
}

.portfolio-filter ul {
  margin: 0 0 2em;
  padding: 0;
  text-align: center;
  width: 100%;
}

.portfolio-filter ul li {
  background: #f0f0f0;
  color: #999;
  cursor: pointer;
  display: inline-block;
  list-style-type: none;
  margin: 0 1em 1em 0;
  padding: .2em .5em;
  text-align: center;
}

.portfolio-filter ul li:first-child {
  margin-right: .8em;
}

.portfolio-filter ul li:focus, .portfolio-filter ul li:hover {
  color: #000;
}

.portfolio .type-jetpack-portfolio {
  float: left;
  margin: 0 -1px -1px 0;
  width: 100%;
}

@media screen and (min-width: 480px) {
  .portfolio .type-jetpack-portfolio {
    max-width: 100%;
    min-height: inherit;
  }
}

@media screen and (min-width: 768px) {
  .portfolio .type-jetpack-portfolio {
    max-width: 239px;
    max-height: 180px;
  }
}

.portfolio .type-jetpack-portfolio img {
  display: block;
}

.portfolio .type-jetpack-portfolio:hover {
  background: #000;
  opacity: .6;
  transition: all .4s ease-in-out;
  -webkit-transition: all .4s ease-in-out;
  -moz-transition: all .4s ease-in-out;
  -o-transition: all .4s ease-in-out;
}

Step 5:

Now that all code and files are added to our theme, let’s go back into our WordPress dashboard to start publishing items in our Portfolio Custom Content Type. When publishing a new portfolio, be sure to add ‘Project Types’ to your posts. This will categorize your posts and your page will show the project types as fitlerable items–which will help in filtering your portfolio.

featured-imate

Added Bonus!

If you are running into any issues, we have included a free theme that you can download. You can compare your code with the files with this theme. This will help you debug any possible issues.

wds-portfolio

Live Demo Download Now

Like our free theme?

It’s on GitHub! Pull requests are welcome to help improve our theme.

Further Theme Development Resources

WordPress Theme Development Standards
All WebDevStudios’ posts on Theme Development and Designing

23 thoughts on “Create a Filterable Portfolio with WordPress and Jetpack

  1. Very nice tutorial. Is it possible to integrate JetPack slideshow for single project view and formatted description?

    Thanks for the tut.

  2. Thank you for a great tutorial. I’ve tried to get it to work several times. I’m working in a child theme and I don’t think my files are enqueuing properly. I can get the filter list and portfolio items to show up on the homepage template, but the filter does not work. (I haven’t added in any CSS yet) Any advice?

    (Your theme works when I activate it. Also, it seems like my parent theme has some js files enqueued that seem to work.)

    Also, how does @package in the header work with your code vs. my current theme?

    1. Hi,
      I am also having the same issue with this tutorial and can’t seem to get the filters to work. I was wondering if you figured it out?

      -Ryan

  3. Hi.

    Thanks for a great tutorial. It works great.

    But I would like to know what I should be modifying if I am to remove the ‘Show All’ filter and replace it with the alphabetically superior filter as the default?
    And how do you direct the portfolio images to the image link instead of a page?

    Thanks.

    1. I had that same issue and could not figure it out. I was hoping that you had figured it out and could fill me in?

  4. Hi Lisa,
    first of all: Thanks a lot for sharing your knowledge and for providing us with this great tutorial!

    I ran into some problems when I tried to add more than one category to a portfolio-item. The filtering stopped to work properly.

    When inspecting the html-code I found out that the generated css-classes of the portfolio items (which represent the category names) where separated by a comma which is no valid html. I fixed it by changing the

    $filtering = join( “, “, $filtering_links );

    to

    $filtering = join( ” “, $filtering_links );

    in the file “content-portfolio.php”

    Now it seems to work fine!
    Maybe this can help if someone runs into the same issue.

    1. Hey Stefan!

      Thanks for the feedback! This post was actually by an employee that isn’t with us anymore, but our rockstar dev Michael took at look at what you sent us and included a note in the post about it since you’re on point!

      Thanks so much for the feedback!

  5. Hi Lisa,
    thank you very much for your great tutorial, he was very helpful for me. Maybe you know how to do this portfolio – masonry?

  6. I’m trying this tutorial. However, when I try to view the portfolio page even that I have a custom template for it(template-portfolio.php), it gets the archive.php. Not sure what I’ve been missing here….

  7. Hello,

    I can’t find the demo site anymore. I got the filtering working but for some reason i cannot get the :active working.

    1. Same issue, I fix adding this code in wds-portfolio.js
      // filter items when filter link is clicked
      $( ‘.portfolio-filter li’ ).click( function(){
      var selector = $( this ).attr( ‘data-filter’ );
      $container.isotope( {
      filter: selector,
      } );
      $(‘.portfolio-filter li’).removeClass(‘active’);
      $(this).addClass(‘active’);
      return false;
      } );

Have a comment?

Your email address will not be published. Required fields are marked *