4

Scenario

I'm considering writing a custom wp-json endpoint to list ALL permalinks for everyone post in my wordpress.

Question

Is it possible to do this with a rest query + filters? eg. http://wpsite.com/wp-json/wp/v2/posts?filter[only-permalinks]

Current Solution

I ended up writing a custom endpoint, code below:

added to the bottom of functions.php

function get_all_slugs() {
    $posts = get_posts( array(
        'numberposts' => -1,
        'post_type' => "screen",
    ) );

    if ( empty( $posts ) ) {
        return null;
    }

    $posts_data = array();

    foreach( $posts as $post ) {
        $id = $post->ID; 

        $posts_data[] = (object) array( 
            'id' => $id, 
            'slug' => $post->post_name, 
            //'title' => $post->post_title,
        );
    }

    return $posts_data;
}
add_action( 'rest_api_init', function () {
    register_rest_route( 'row/v1', '/all-slugs', array( // /(?P<post_type>\d+)', array(
        'methods' => 'GET',
        'callback' => 'get_all_slugs',
    ) );
} );
2

Is it possible to do this with a rest query + filters? eg. http://wpsite.com/wp-json/wp/v2/posts?filter[only-permalinks]

Yes, since 4.9.8 (see #43874) it's possible to render only fields needed with the _fields parameter.

Examples

Render only the permalinks:

https://example.com/wp-json/wp/v2/posts?_fields=link

[ 
    {
        link: "https://example.com/foo/"
    },
    {
        link: "https://example.com/bar/"
    },
]

Render only the post IDs and permalinks:

https://example.com/wp-json/wp/v2/posts?_fields=id,link

[ 
    {
        id: 123,
        link: "https://example.com/foo/"
    },
    {
        id: 234,
        link: "https://example.com/bar/"
    },
]

Render only the post IDs and the slugs:

https://example.com/wp-json/wp/v2/posts?_fields=id,slug

[ 
    {
        id: 123,
        slug: "foo"
    },
    {
        id: 234,
        slug: "bar"
    },
]

Fetching All Available Items

Currently we have to fetch all available items with paging, like:

https://example.comwp-json/wp/v2/posts?_fields=slug&per_page=100&page=1
https://example.comwp-json/wp/v2/posts?_fields=slug&per_page=100&page=2
...

where per_page is 10 by default and with maximum value of 100.

Fetching all items in a single request can result in fatal errors if the number of items exceeds the available resources.

The ticket #43998 to allow unbound requests (something like per_page=-1) for logged in users, was closed as wontfix because it doesn't scale and has performance issues.

There's a ticket #45140 to increase the upper bound for per_page to few hundreds.

Usually there's no need to display so many items in a user interface and there exists javascript techniques to handle the pagination.

If more is really needed then one could filter the rest query via rest_{$post_type}_query to override the maximum default value of posts_per_page, at the risk of fatal errors if available resources are exceeded.

  • This is great! Is there any way to bend the 100 posts per page max? – Jacksonkr Jan 2 at 2:47
  • I updated the answer with some additional notes.Hope it helps @Jacksonkr – birgire Jan 2 at 3:18
  • 1
    It seems like this method can not get all posts without pagination. Not that getting thousands of posts is a good idea, but the OP asks for all. – Justin Waulters Jan 2 at 4:31
  • 1
    We don't know the use case, so we try to mention the downsides of it and the reason core doesn't allow unbound REST queries :) I can imagine if per_page=-1 were allowed on all WP sites, it would be a dream for attackers. All methods currently needs some kind of PHP plugin code to override the per_page limitation in REST. If this is a one time only export, one could also try something like: wp post list --fields=ID,name --post_type=post --post_status=publish --format=json > export.json in wp-cli to avoid timeout (I think one might e.g. need to increase PHP memory limit for larger cases). – birgire Jan 2 at 12:19
  • 1
    Considering your point about attackers & resources I can see why the 100 limit exists. A new endpoint is definitely the way to go for the customization I need. I'm marking this as the answer as you you nailed the op. Cheers! – Jacksonkr Jan 2 at 16:59
2

I don't believe there is a default endpoint to let you get all the posts and return only the permalinks.

You could use the posts endpoint to get large chunks of permalinks (I don't believe it will let you return all of them in a single request) and paginate the request. To get it to return only the permalinks you could use the json_prepare_post filter all of which is outlined as part of this tutorial.

However, if you must have all the permalinks in a single return, the most efficient way of doing it would be to add a new endpoint as outlined here. In the callback function you could do something like this but instead of echoing the permalinks put them in an array, json_encode() the array, and then return it.

Since this could become a monster of a process with a lot of posts, I'd recommend using the Transient API to store the request(s) - whether you choose to paginate and use the posts endpoint or return everything with a custom endpoint.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.