Extending & internals

Pages Plus exposes no hooks of its own. As of 1.5.1 there is no apply_filters() and no do_action() anywhere in it: nothing here is a public contract you can hook into, and pretending otherwise would be worse than saying so. What follows is what it attaches to, and the handles, keys and class names you can build on.

What it hooks

Everything except the textdomain load and the duplicate handler is wired inside load-edit.php, scoped to the current screen's post type.

Hook Callback Removable
manage_{$type}_posts_columns freshet_pagesplus_columns yes
manage_{$type}_posts_custom_column freshet_pagesplus_render_column yes
manage_edit-{$type}_sortable_columns freshet_pagesplus_sortable_columns yes
the_title freshet_pagesplus_capture_path yes
restrict_manage_posts freshet_pagesplus_parent_filter, freshet_pagesplus_template_filter yes
post_row_actions, page_row_actions freshet_pagesplus_duplicate_link yes
bulk_actions-edit-{$type} freshet_pagesplus_register_bulk_duplicate yes
handle_bulk_actions-edit-{$type} freshet_pagesplus_handle_bulk_duplicate yes
admin_notices freshet_pagesplus_bulk_notice yes
admin_enqueue_scripts freshet_pagesplus_enqueue_assets yes
admin_footer freshet_pagesplus_print_paths yes
admin_action_freshet_pagesplus_duplicate freshet_pagesplus_duplicate_handler yes
pre_get_posts (parent filter, template filter) closure no
posts_search (path search) closure no
posts_clauses (parent sorting) closure no
the_posts (branch re-rooting) closure no

The named callbacks can be removed; the closures cannot, because an anonymous function has no handle to pass to remove_filter(). If you need one of those four off, deactivating the plugin is the honest answer.

Removing a piece

The named callbacks are added during load-edit.php, so remove them at a later priority on that same hook — anything earlier runs before they are attached:

add_action( 'load-edit.php', function () {
    remove_filter( 'post_row_actions', 'freshet_pagesplus_duplicate_link' );
    remove_filter( 'page_row_actions', 'freshet_pagesplus_duplicate_link' );
}, 20 );

Dropping a column is easier from the other side — filter the columns after they are added:

add_filter( 'manage_page_posts_columns', function ( array $columns ): array {
    unset( $columns['freshet_pagesplus_modified'] );
    return $columns;
}, 20 );

Keys and query arguments

String What it is
freshet_pagesplus_modified Modified column key
freshet_pagesplus_parent Parent column key; also the filter's query arg (top, or a parent ID)
freshet_pagesplus_template Template column key; also the filter's query arg (a template file, default, or empty)
orderby=freshet_pagesplus_parent Sort by parent title
freshet_pagesplus_duplicated Count appended to the redirect after a bulk duplicate

Each is also available as a constant — FRESHET_PAGESPLUS_COL_MODIFIED, FRESHET_PAGESPLUS_COL_PARENT, FRESHET_PAGESPLUS_COL_TEMPLATE, FRESHET_PAGESPLUS_PARENT_QV, FRESHET_PAGESPLUS_TEMPLATE_QV, FRESHET_PAGESPLUS_TOP_VALUE, FRESHET_PAGESPLUS_DUP_ACTION, FRESHET_PAGESPLUS_VERSION. So links into a filtered view compose:

$branch = add_query_arg(
    [ 'post_type' => 'page', FRESHET_PAGESPLUS_PARENT_QV => $parent_id ],
    admin_url( 'edit.php' )
);

Styling

The stylesheet and script share one src-less handle, freshet-pagesplus, registered and enqueued only on the list-table screens the plugin enhances. Add your own rules to it after it is registered:

add_action( 'admin_enqueue_scripts', function () {
    if ( wp_style_is( 'freshet-pagesplus', 'enqueued' ) ) {
        wp_add_inline_style( 'freshet-pagesplus', '.freshet-pagesplus-path{font-size:13px}' );
    }
}, 20 );

The class names are stable:

Class Element
.freshet-pagesplus-path The URL path under a title (an <a> when the post is viewable, a <span> otherwise)
.freshet-pagesplus-sub The #ID · age · author line in the Modified column
.column-freshet_pagesplus_modified and friends The columns themselves, via WordPress' usual .column-{key}

The path payload

Paths are collected while the table renders and printed in the admin footer as window.freshetPagesplusPaths{ postId: { path, href } }, where an empty href means "not viewable, render as text". The injector runs once and skips any row that already has a path element, so it is safe to read the payload yourself; it is not a supported API, and the shape may change.

Translation

Text domain freshet-pages-plus, loaded on init from the plugin's own /languages, so a bundled .mo works as well as one delivered by wordpress.org.