# Find Broken Internal Links (WP-CLI)

> Adds a "wp check-internal-links" command that scans published content for links to your own site that no longer point to a post, page or file, and lists them with the post they appear in.

- Type: PHP
- Runs: Run everywhere
- Category: [Developer](https://wpide.com/library/category/developer/)
- Tags: wp-cli, links, seo
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/cli-check-internal-links/

## Code

```php
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
    return;
}

WP_CLI::add_command( 'check-internal-links', function ( $args, $assoc_args ) {
    $home       = untrailingslashit( home_url() );
    $post_types = isset( $assoc_args['post_type'] ) ? array_map( 'sanitize_key', explode( ',', $assoc_args['post_type'] ) ) : array( 'post', 'page' );
    $items      = array();
    $paged      = 1;

    do {
        $query = new WP_Query( array(
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => 100,
            'paged'          => $paged++,
            'no_found_rows'  => true,
        ) );

        foreach ( $query->posts as $post ) {
            if ( ! preg_match_all( '/href=["\']([^"\'#]+)/i', $post->post_content, $matches ) ) {
                continue;
            }
            foreach ( array_unique( $matches[1] ) as $url ) {
                if ( 0 === strpos( $url, '/' ) && 0 !== strpos( $url, '//' ) ) {
                    $url = $home . $url;
                }
                if ( 0 !== strpos( $url, $home ) ) {
                    continue; // External link.
                }
                $path = (string) wp_parse_url( $url, PHP_URL_PATH );
                if ( '' === trim( $path, '/' ) || false !== strpos( $path, '/wp-' ) ) {
                    continue; // Homepage, admin or uploads.
                }
                if ( url_to_postid( $url ) || file_exists( ABSPATH . ltrim( $path, '/' ) ) ) {
                    continue;
                }
                $items[] = array( 'post_id' => $post->ID, 'post_title' => $post->post_title, 'link' => $url );
            }
        }
    } while ( count( $query->posts ) === 100 );

    if ( ! $items ) {
        WP_CLI::success( 'No broken internal links found.' );
        return;
    }

    WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', $items, array( 'post_id', 'post_title', 'link' ) );
}, array(
    'shortdesc' => 'Lists internal links in content that do not resolve to anything.',
    'synopsis'  => array(
        array( 'type' => 'assoc', 'name' => 'post_type', 'optional' => true, 'description' => 'Comma-separated post types. Default: post,page.' ),
        array( 'type' => 'assoc', 'name' => 'format', 'optional' => true, 'default' => 'table', 'options' => array( 'table', 'csv', 'json', 'count' ) ),
    ),
) );
```

## Notes

It checks links without making HTTP requests, so archive, search and plugin-made URLs may show up; review the list before editing.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Find Broken Internal Links (WP-CLI)" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
