Add custom page template to WordPress through a plugin

This code will let you have a page template in your plugin, and show that template in the “Page Templates” dropdown.
It can also work for other post type templates, just change the filter “theme_page_templates” as needed.
WP 4.7 only.
Setup:

  1. Replace “cppt” with your own plugin prefix.
  2. Replace custom_plugin_page_template.php with whatever made-up template name you want.
  3. Replace templates/custom-template.php with the real template (and directory) you want to load.

Step 1: Add a fake page template to the dropdown.

We’re going to look for the key here, but the filename shouldn’t exist in the theme.

function cppt_add_template_to_dropdown( $posts_templates ) {
	$posts_templates['custom_plugin_page_template.php'] = 'Custom Plugin Page Template';
	return $posts_templates;
}
add_filter( 'theme_page_templates', 'cppt_add_template_to_dropdown' );

Step 2: Add the same fake page template to the template cache. The only tricky part.

function cppt_register_custom_page_template( $atts ) {
	// The same key used for the page template cache.
	$cache_key = 'page_templates-' . md5( get_theme_root() . '/' . get_stylesheet() );
	
  // Get cached value.
	$templates = wp_get_theme()->get_page_templates();
	if ( empty( $templates ) ) $templates = array();
	
	// Clear old cache.
	wp_cache_delete( $cache_key , 'themes');
	
	// Add our template.
	$templates['custom_plugin_page_template'] = 'Custom Plugin Page Template';
	
	// Save cache
	wp_cache_add( $cache_key, $templates, 'themes', 1800 );
	
	return $atts;
}
add_filter( 'wp_insert_post_data', 'cppt_register_custom_page_template' );

// Legacy support
if ( version_compare( floatval( get_bloginfo( 'version' ) ), '4.7', '<' ) ) {
	add_filter( 'page_attributes_dropdown_pages_args', 'cppt_register_custom_page_template' );
}

Step 3: Return the real PHP file from our plugin folder.

function cppt_include_custom_page_template( $template ) {
	global $post;
	if ( !$post ) return $template;
	
	// Check if the assigned page template matches our custom php file.
	$page_template = get_post_meta( $post->ID, '_wp_page_template', true );
	
	if ( $page_template == 'custom_plugin_page_template' ) {
		
		// It's a match. Use the real template directory.
		$file = plugin_dir_path(__FILE__) . '/templates/custom-template.php';
		
		if ( file_exists( $file ) ) return $file;
	}
	
	return $template;
}
add_filter( 'template_include', 'cppt_include_custom_page_template' );