• Skip to main content
  • Skip to primary sidebar
  • Skip to footer

nSiteful Web Builders

Building a Better Web - One Site at a Time.

  • Home
  • About
    • Testimonials
    • Resources
    • Partner With Me
    • Frequently Asked Questions
  • Web Sites
  • Online Marketing
  • WordPress Support
    • Customized WordPress Training
    • 60-for-60 Sessions
  • Web Applications
  • Blog
    • At-A-Glance
    • Blog Articles Grouped by Category
    • Case Studies
    • General
    • Portfolio
    • Reviews
    • Snippets
    • Techniques
  • Contact
    • Purchase Retainer Consulting Hours
    • About Retainer Consulting Hours
    • Book a Meeting with Jeff
    • Tell Me About Your Web Project
    • nSiteful Newsletter Archives
    • nSiteful Rewards

By Jeff - 4/2/2019
Cats: Case Studies · Tags: Plugins, WordPress

case-studies

I Built a WordPress Plugin for Downloadable Files

If you’ve ever embedded a PDF file or other downloadable file in WordPress posts or pages using the standard “Add Media” button, you probably know what a pain it can be if and when — but most likely when — that file needs to be updated.

You can’t just upload the newly updated file to the Media Library and expect “voila” to happen.

Even if the updated file has the same filename as the original, WordPress will rename it on upload rather than replace the old one with the new one. (I’ve tried a plugin that offers this replacement functionality, but it wasn’t user-friendly and fell short in other ways.)

What you’ll have to do to replace the file is:

  1. remember all the places you embedded it
  2. remove the old embed in all those places
  3. add the new embed in all those places

Not the end of the earth, but not the best use of anyone’s time, either. Especially if there are lots of documents that get updated or some documents that get updated a lot or some combination of the two.

So I created my own plugin — “Downloads” — for replacing downloadable files.

(Note: This is a plugin with a small “p”. More work is needed before it can be submitted for inclusion in the WordPress repository.)

A Web site for a private school was the inspiration.

The project that inspired this solution was a Web-site redesign for a private school. This school — like many, I suppose — offers a whole lot of documents, most of them in PDF format, for students and parents, both actual and prospective.

Lots of downloadable documents.

There are:

  • school-year calendars for each of three academic programs
  • application forms
  • forms for requesting transcripts
  • field trip permission forms
  • handbooks
  • course descriptions
  • event flyers
  • and more.

In the rest of this article I’ll describe the basic features, elements and operation of the plugin, without getting too much into the weeds. If you’re interested in learning more, let me know in the comments.

Features of the Plugin

Feature #1. Insert Documents by Shortcode

One of the main components of the plugin is a custom shortcode used to embed each Download (more on the shortcode below).

Feature #2. Conditional Display

Each downloadable document can have either a “Start Showing On…” date and/or a “Stop Showing After…” date. Wherever a Download is embedded in a page or post (or widget, for that matter), the plugin “knows” whether to display the link based on those dates and the current date.

This means two things:

  • You can embed a Download on a page or post even before you want it to appear on the front end of the site. This is almost exactly like post-dating a blog post to schedule it to appear in the future.
  • Conversely, the plugin will automatically hide any Download that should not appear after a specific date. (For example, you might want to remove a field trip application form after the field trip occurs.) You needn’t edit the page(s) or post(s) to manually remove the embed, as long as you specified the proper date parameters.

Feature #3. Automatic Inclusion on Front End

screen shot of Academic Calendars page
Click image to enlarge
The site has a dedicated page for Academic Calendars. As I mentioned earlier, there are calendars for each of the three main programs the school offers. In addition, at any given time of year, the calendars for multiple school years for any given program might be displayed. For example, in the spring of the 2018-2019 school year, both the current school year calendar (2018-2019) and the next school year calendar (2019-2020, if available) for the K-12 will be displayed.

I made a custom template for this page. The template’s code automatically displays the link for every downloadable document whose type (custom taxonomy) is “Calendar” as long as the current date is on or after the designated “Start Showing On…” date and on or before the “Stop Showing After…” date.

Feature #4. Automatic Replacement

This is the feature that drove me to create the plugin in the first place.

Invariably, Downloads need to be replaced from time to time, due either to errors or actual changes.

Because each downloadable document is identified by its post ID — rather than by its filename — you can just replace the obsolete document with the new one in the Downloads administrative panel, and voila!

First click the “Remove” icon…
Remove file screen shot

Then add the new file.
Replace file screen shot

There are five main elements of this plugin:

  1. a custom post type for downloadable documents (“Downloads”)
  2. custom fields (using the Advanced Custom Fields plugin for specifying metadata for each document)
  3. a custom taxonomy
  4. a shortcode for displaying the download links where needed
  5. a custom page template for displaying calendars

Element #1. The Custom Post Type

The Custom Post Type itself is very simple. This new post type (named “Download”) supports only the ‘title’ and “page-attributes” features of WordPress’ post edit screen. The ‘title’ is the internal (for office use only) name for the download. Enabling page attributes permits assigning order values, for the purpose of sorting, where desired.

Element #2. Custom Fields

This is where the meaty stuff of the plugin happens.

Here’s a screen shot of the ACF Field Group for downloads:

screen shot of custom fields admin screen
Click image to enlarge

The fields:

  • Title (required, internal use only)
  • Academic Program (optional; e.g., “K-12”, “Independent Study”, etc.)
  • Front-End Document Title (required)
  • Document Description (optional and hidden on front end by default)
  • Document File (required)
  • Start Showing On… date (optional, supports date-picker)
  • Stop Showing After… date (optional, supports date-picker)

Element #3. Custom Taxonomy

screen shot of download categories boxI registered a custom taxonomy called “download_cat” and associated it with the Downloads custom post type. As of this writing, the only download_cat value that has any special power is “Calendar”. As I mentioned above, the template’s code automatically displays the HTML for every downloadable document whose type (download_cat) is “Calendar” as long as the current date is on or after the designated “Start Showing On…” date and on or before the “Stop Showing After…” date.

Element #4. The Downloads Shortcode

Format for Shortcode:

The format for the shortcode that embeds a Download into a page or post is as follows. The only required attribute is the id. Where I show optional values for attributes (e.g., “true|false”), the first element is the default.

[ea_download id="nnn" block="true|false" show_description="false|true" href_only="false|true"]

Shortcode Function:

Here’s the actual code for generating the HTML from the shortcode. If you’re interested in a description of how it works, let me know.

function nwb_shortcode_download($atts) {
	global $post;
	$atts = shortcode_atts( array(
		'id' => '',
		'block' => 'true',
		'show_description' => 'false',
		'href_only' => 'false',
	), $atts, 'ea_download' );
	if ( empty($atts['id']) ) {
		return;
	}
	$doc_id = $atts['id'];
	$data = get_post_custom($doc_id);
	$program 		=	$data['program'][0];
	$title 			= 	$data['document_title'][0];
	$description 	= 	$data['document_description'][0];
	$show_from 		= 	$data['show_from'][0];
	$show_through 	= 	$data['show_through'][0];
	$file 			=	$data['document_file'][0];
	
	// Test from and to dates
	$today = date('Y-m-d');
	if ( !empty($show_from) && $today < $show_from ) {
		return;
	}
	if ( !empty($show_through) && $today > $show_through ) {
		return;
	}
	$file_url 		= 	wp_get_attachment_url($file);
	$href_start = '<a target="_blank" href="' . $file_url . '">';
	$href_end = '</a>';
	

	/* To return only the link */
	if ( 'true' == $atts['href_only']) {
		$output = $file_url; 
		return $output;
	}
	if ( 'true' == $atts['block']) {
		$output = '<div class="download">';
		$output .= '<div class="name">' . $href_start . $title . $href_end . '</div>';
		if ( $atts['show_description'] == 'true' && !empty($description)) {
			$output .= '<div class="description">' . $description . '</div>';
		}
		$output .= '</div>';
	} else {
		$output = '<span class="name">' . $href_start . $title . $href_end . '</span>';
	}
	return $output;
}
add_shortcode('ea_download', 'nwb_shortcode_download');

Element #5. The Custom Page Template for Academic Calendars

Here are pertient blocks of code from the custom page template for grabbing and diplaying academic calendars. Again, if anyone is interested in more information, please let me know.

Custom Query:

global $post;
$args = array(
	'post_type' => 'download',
	'order' => 'ASC',
	'orderby' => 'menu_order',
	'tax_query' => array(
		array(
			'taxonomy' => 'download_cat',
			'field' => 'slug',
			'terms' => 'calendar',
		),
	),
	'posts_per_page' => -1,
);
$cal_query = new WP_Query($args);

Rendering the HTML on the page:

<?php if ( $cal_query->have_posts() ) : while ( $cal_query->have_posts() ) : $cal_query->the_post(); ?>
	<?php 
		$doc_id = $post->ID;
		$data = get_post_custom($doc_id);
		$program 		=	$data['program'][0];
		$title 			= 	$data['document_title'][0];
		$description 	= 	$data['document_description'][0];
		$show_from 		= 	$data['show_from'][0];
		$show_through 	= 	$data['show_through'][0];
		$file 			=	$data['document_file'][0];
		// Test from and to dates
		$today = date('Y-m-d');
		if ( !empty($show_from) && $today < $show_from ) {
			continue;
		}
		if ( !empty($show_through) && $today > $show_through ) {
			continue;
		}
		$file_url 		= 	wp_get_attachment_url($file);
		$href_start = '<a target="_blank" href="' . $file_url . '">';
		$href_end = '</a>';
		/* Assemble the output */
		$output = '<section>';
		$output .= '<div class="download">';
		$output .= '<div class="name">' . $href_start . $title . $href_end . '</div>';
		if ( $atts['show_description'] == 'true' && !empty($description)) {
			$output .= '<div class="description">' . $description . '</div>';
		}
		$output .= '</div>';
		$output .= '</section>';
		echo $output; 
	?>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
<?php endif; ?>

Tying it Up

Here are screen shots of the “Forms & Downloads” page followed by the code that makes it happen.

Screen shots

screen shot of forms/downloads page, top
Click image to enlarge
screen shot of forms/downloads page, bottom
Click image to enlarge

The code entered into the page:

<h2 id="orientation">Orientation, Handbooks &amp; General Forms</h2>

[ea_download id="4709"]
[ea_download id="5099"]
[ea_download id="4785"]

<h2 id="distance-learning">Distance Learning</h2>

[ea_download id="4787"]
[ea_download id="4788"]
[ea_download id="4790"]

<h2 id="summer-school">Summer School</h2>

[ea_download id="4792" show_description='true']
[ea_download id="4791" show_description='true']

<h2 id="other-forms">Other Forms &amp; Downloads</h2>

[ea_download id="5473" show_description="true"]
[ea_download id="4786"]
[ea_download id="4771" show_description='true']
[ea_download id="5550" show_description='true']
[ea_download id="5553" show_description='true']

Comments Welcome

I guess I ended up covering a lot of ground after all. But if you have questions or are interested in knowing more, please let me know in the comments below.

Like what you see? Share with others and join my mailing list. No long-term commitment, unsubscribe any time.

Related Posts

  1. Add Dynamic Table of Contents to a Series of WordPress Posts
  2. PHP Shortcode: Custom extensible PHP shortcode function for non-WordPress Web sites
  3. Dive Into WordPress Custom Post Types – Part 2
  4. FooGallery Plugin for WordPress: First Look
  5. Tipsy WordPress Plugin from iThemes

Reader Interactions

Leave a Reply Cancel reply

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

Primary Sidebar

mailchimp signup

Subscribe to get notified when new articles are published. Unsubscribe any time. No spam. I promise. Check out my newsletter archives.

social

Twitter Facebook LinkedIn

Recent Articles

  • Custom MemberPress Pricing Pages December 19, 2025
  • CSS Data Attribute Override December 11, 2025
  • MemberPress Rules Using Custom Taxonomies and Custom Roles December 10, 2025
  • WP Staging Plugin: a First Look November 21, 2025
  • How to overlap elements with HTML and CSS (Grid) only August 13, 2025

Filter By Category/Tag

Categories

  • Case Studies (8)
  • For Staff (1)
  • General (72)
  • Portfolio (7)
  • Reviews (14)
  • Snippets (23)
  • Techniques (52)

Popular Tags

Advanced Custom Fields Blogging Child Themes Content Marketing CSS Customer Service Custom Fields Custom Post Types Diagnostics Domain Names Facebook FooGallery Genesis Hosting HTML Images iPhone Libra Live Chat Marketing Media MemberPress MemberPress Courses Membership Sites Mobile-Friendly MySQL Photo Gallery php Pinterest Plugins Post Formats Pricing Project Management SEBA SEO Seth Godin Shortcodes Social Networking Surveys Taxonomies Twitter Video Web design Web forms WordPress

Footer

Background

Web Sites | WordPress Support | Web Applications.

Formally trained in liberal arts and education (I have a B.A. in Government from Harvard and studied Secondary Education at Rutgers Graduate School), I have honed my skills in the communication arts and sciences as a teacher, trainer, instructional designer, writer, photographer, calligrapher, helpdesk manager, database programmer, and multimedia developer.

(I've also been a group counselor, waiter, bartender, bicycle messenger boy, computer salesman, carpenter's helper, financial analyst, and school board president.)

Tech

Systems since 1983.
Web sites since 1994.
PHP since 2001.
WordPress since 2007.

Contact

Book Meeting
770-772-5134
Email Jeff
Send Money
All Ways

Copyright 2026, nSiteful Web Builders, Inc.

Cookies Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
View preferences
  • {title}
  • {title}
  • {title}
https://iframe.mediadelivery.net/embed/392008/42d18bc1-2adc-4741-b733-053d08d09c32
https://vz-000c5976-3ab.b-cdn.net/42d18bc1-2adc-4741-b733-053d08d09c32/play_720p.mp4

Receive occasional emails from Jeff