The workhorse is WP_Theme::get_page_templates()
(wrapped by the helper function get_page_templates()
). If you check out the source, you’ll see:
/**
* Filter list of page templates for a theme.
*
* @since 3.9.0
* @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
*
* @param array $page_templates Array of page templates. Keys are filenames,
* values are translated names.
* @param WP_Theme $this The theme object.
* @param WP_Post|null $post The post being edited, provided for context, or null.
*/
return (array) apply_filters( 'theme_page_templates', $page_templates, $this, $post );
Example:
function wpse_226324_page_templates( $templates ) {
// Remove tpl-home.php template
unset( $templates['tpl-home.php'] );
// Add custom template
$templates['tpl-custom.php'] = 'Custom Template';
return $templates;
}
add_filter( 'theme_page_templates', 'wpse_226324_page_templates' );
See also theme_page_templates
code reference.