Mastodon, a decentralized social media/microblogging platform, has become quite popular the last weeks and months. There are many good reasons to consider a Mastodon migration, but for Free Software enthusiast or WordPress lovers (like me), there are a even more specific reasons to give Mastodon a try. Hope you find my reasons compelling, even if I’m biased, running the WordPress centered Mastodon instance wptoots.social.

1. You can choose an Mastodon instance relevant to the topics you are most interested in

Mastodon is decentralized and there are thousands of instances to chose from. So pick a home to start your Mastodon journey carefully! If you are specifically interested in WordPress sign up on a WordPress centered instance and become a part of its WordPress centered community. You will instantly find people with the same interests!

Take even into account that little instances are more at risk of shutting down, while big instances can be hit by so many users, that they might get slow at times. You can move your user, including followers, to another instance at any time (your posts will be left on the “old” instance, though).

2. The local timeline

On mastodon you have three timelines. A home, local and federated timeline.

Mastodon timelines

Have you chosen an Mastodon instance, related to a topics of your interest, the local timeline might be the most interesting for you. Here you only see posts from users of your instance and, at least for the WordPress instances, they are right on topic the most time. The home and federated timelines are a broader look into what’s going on with the people you follow and the people your instance users follow.

3. Instance rules let you post, share and discuss in a safe environment

Every instance has its own rules, everyone signing up agrees to them, everyone violating them gets suspended (if moderators do their job, which they do in almost every case). So unlike many other platforms, you know what you’re signing up for and what you can expect. If you want to discuss WordPress professionally, you’ll just have to find an instance with a matching set of rules.

There is an argument that instances, and users choosing instances, can contribute to even stronger discourse- and opinion bubbles. I think it’s too early to say if that’s true. But my hope is, that there is a big difference: Between actively and consciously picking a community – and being influenced by an algorithm, making you believe the whole world thinks like you.

4. Your content is not monetized

Content you produce and thoughts that you share shouldn’t be monetized without your active approval and only with the methods you have agreed to. Period.

5. Your content is not ranked by an algorithm optimized for engagement

Engagement, likes, shares and clicks are the daily bread and butter of commercial social media platform. Of course their algorithms are optimized to encourage engagement and that is in many cases favoring polarized opinions and heated discussions.

They are not optimized, though, to favor more complex professional thoughts, balanced argumentation or input, broadening your professional point of view. But that’s what we can get on Mastodon with our WordPress postings.

6. Mastodon is built, owned, funded and driven by its community

The Mastodon Community is running Mastodon. It’s actually amazing that that’s working, if you think about it. But there are other examples, aren’t there? The WordPress project comes to mind and all the other FOSS projects! It would seem like there is no better fit for the WordPress Community than a Community-driven social network …

7. Mastodon is Free Software

You can’t argue that one, can you?

8. Mastodon can’t be bought

Another obvious one, but there is more to it. Even if Mastodon could be bought (and single instances surely can), every admin in the community decides independently when to upgrade. If you don’t like the direction Mastodon is taking, you can fork it and run an own version. There is great power in that: Nothing can be changed against the explicit approval of the community. Democracy by design. And guess which other community can’t be bought either? You guessed right.

9. Most communities on Mastodon are actually nice

That might be changing over time, but for now I think this is true: Big parts of the community are positive, inclusive, accepting and open. Most people migrating, are tired of the vibe of the big commercial platforms and working actively for a more constructive behavior on their new platform. And who knows? Maybe community-driven social networks are just nicer!

10. Meaningful exchange seems to be the rule not the exception on Mastodon

That’s more of an personal experience over the last weeks on my own instance. Everything I put out there (especially the WordPress and/or community relevant stuff) is followed by meaningful comments, some good discussions, cheering and sharing. I’ve really learned a lot and find the response from my social network on Mastodon to be quite uplifting and enlightening. Never felt that on the birdsite, to be honest.

Start your Mastodon migration

Convinced? Here are some starting points for your Mastodon migration:

Find an instance! If you search for the WordPress community you are welcome to wptoots.social. Or try one of the other WordPress instances (I know of) wpbuilds.social or wp-social.net. If you are not only into WordPress, a bigger instance like fosstodon.org might be the right thing for you.

Find your community! That is incredibly difficult in the beginning and motivated me to make a list. Everyone on it has at least one community badge on profiles.wordpress.org and has agreed to be listed.

Enjoy!

Share!

While I’m at it, I thought a Mastodon WordPress instance for community members, searching a home on Mastodon would be cool.

https://wptoots.social

Mastodon as decentralized social network seems like the perfect fit for a group as diverse and strong as the WordPress community. The instance should be able to handle a couple of hundred users, is actively maintained and has daily backup.

There is even a curated list of the WordPress Community on Mastodon.

And I promise: Instance will run indefinitely!

As a little extra you can add a WordPress batch in your Nickname (like Twitters verified icon) and show the entire fediverse what you stand for.

Hope for everyone loving WordPress out there to share and join! And follow me if you like:

@danielauener@wptoots.social

Current WordPress Rest API extension list

If you want to have all the endpoints as plugin, there is one in the plugin directory: WUXT Headless WordPress API Extensions

I love the WordPress Rest API and switching more and more from theme development to a headless WP approach, with an nice front-end framework. Right now I’m favoring Nuxt.js, which is build on Vue.js (check out wuxt, my very own dockerized nuxt/wp development environment).

For using WPs full strength with the Rest API I’ve collected/build a useful snippet library with WordPress Rest API extensions. I’ll try to maintain the following list as development goes on. All of the following extensions can be embedded in the functions.php file. If you wondering about the wuxt_ prefix, I’ve got the code from my Wuxt project and the prefix is as good as anyone.

Front-page extension

Everything starts with a nice front-page, but there no obvious way to get the WordPress front-page via the Rest API. To read the settings, you have to be authorized, which makes things unnecessary complicated. So here a custom endpoint for getting the front-page.

GET: /wp-json/wp/v2/front-page

<?php

/**
 * Adds a front-page endpoint for generell front-page settings in the
 * Front-end
 */
add_action( 'rest_api_init', 'wuxt_front_page_route' );


function wuxt_front_page_route() {
    register_rest_route( 'wp', '/v2/front-page', array(
        'methods'  => 'GET',
        'callback' => 'wuxt_get_front_page'
    ) );
}


function wuxt_get_front_page( $object ) {

    $request  = new WP_REST_Request( 'GET', '/wp/v2/posts' );

    $frontpage_id = get_option( 'page_on_front' );
    if ( $frontpage_id ) {
      $request  = new WP_REST_Request( 'GET', '/wp/v2/pages/' . $frontpage_id );
    }

    $response = rest_do_request( $request );
    if ($response->is_error()) {
        return new WP_Error( 'wuxt_request_error', __( 'Request Error' ), array( 'status' => 500 ) );
    }

    $embed = $object->get_param( '_embed' ) !== NULL;
    $data = rest_get_server()->response_to_data( $response, $embed );

    return $data;

}

?>

Right now, there is no way I know of for getting menus from the WordPress Rest API. I’m not sure if it’s the most effective way, but here my WordPress Rest API custom endpoint for menus. It registers even a standard ‘main’ menu as default if no location is requested.

Note: The most work for this snippet is done by the menu-class of Michael Cox, you have to include it to get the endpoint to work.

GET: /wp-json/wp/v2/menu?location=<location>

<?php

/**
 * Adds a menu endpoint
 */
require_once('/path/to/Menu.php');

add_action('init', 'wuxt_register_menu');
add_action('rest_api_init', 'wuxt_route_menu');

function wuxt_register_menu()
{
    register_nav_menu('main', __('Main meny'));
}

function wuxt_route_menu()
{
    register_rest_route('wp', '/v2/menu', array(
        'methods' => 'GET',
        'callback' => 'wuxt_get_menu',
    ));
}

function wuxt_get_menu($params)
{
    $params = $params->get_params();
    $theme_locations = get_nav_menu_locations();

    if (!isset($params['location'])) {
        $params['location'] = 'main';
    }

    if ( ! isset( $theme_locations[$params['location']] ) ) {
        return new WP_Error( 'wuxt_menu_error', __( 'Menu location does not exist' ), array( 'status' => 404 ) );
    }

    $menu_obj = get_term( $theme_locations[$params['location']], 'nav_menu' );
    $menu_name = $menu_obj->name;
    $menu = new Menu( $menu_name );
    return $menu->getTree();
}

?>

Filtering Categories and taxonomies

When filtering taxonomies with an Rest API request, you are stuck with OR-queries, because the category endpoint doesn’t give you the full complexity of a tax_query. That means you can get posts which are either in category A or B. The following adjustment doesn’t give you the full complexity either, but it lets you switch all tax_queries to an AND-relation, so that you can select posts which are both in category A and B.

GET: /wp-json/wp/v2/posts/?categories=1,2&and=true

<?php

  /**
   * Ads AND relation on rest category filter queries
   */
  add_action( 'pre_get_posts', 'wuxt_override_relation' );

  function wuxt_override_relation( $query ) {

    // bail early when not a rest request
  	if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
  		return;
  	}

    // check if we want to force an "and" relation
    if ( ! isset( $_GET['and'] ) || !$_GET['and'] || 'false' === $_GET['and'] || !is_array( $tax_query = $query->get( 'tax_query' ) ) ) {
  		return;
  	}

    foreach ( $tax_query as $index => $tax ) {
      $tax_query[$index]['operator'] = 'AND';
    }

  	$query->set( 'tax_query', $tax_query );

  }

?>

Loading ACF Meta-fields

Integrating meta-fields from the Advanced-Custom-Fields plugin into the Rest API responses can be done with this plugin. If you need a simpler solution (only integrating meta fields into post-objects, not writing them), or simply a bit more control, the following snippet can get you started. It sets all ACF-fields to show_in_rest, which lets them appear in the post-objects meta-section:

<?php

    /**
     * Register meta fields from ACF
     */
    add_action( 'init', 'wuxt_register_acf_meta' );

    function wuxt_register_acf_meta() {

        if( function_exists( 'acf_get_field_groups' ) ){
            $result = array();
            $acf_field_groups = acf_get_field_groups();
            foreach( $acf_field_groups as $acf_field_group) {
                foreach($acf_field_group['location'] as $group_locations) {
                    foreach($group_locations as $rule) {
                        foreach(acf_get_fields( $acf_field_group ) as $field) {
                            register_meta( 'post', $field['name'], array( 'show_in_rest' => true ) );
                        }
                    }

                }

            }
        }

    }

?>

SEO-Data

The register_meta trick is handy, even for other plugins. If you want to integrate data from our favorite SEO add-on, Yoast WordPress SEO, into the post objects, you can do it like that:

<?php

    /**
     * Register meta fields for WordPress SEO
     */
    add_action( 'init', 'wuxt_register_yoast_meta' );

    function wuxt_register_yoast_meta() {
      if(in_array('wordpress-seo/wp-seo.php', apply_filters('active_plugins', get_option('active_plugins')))){

          $allowed_yoast_keywords = array(
              '_yoast_wpseo_title',
              '_yoast_wpseo_bctitle',
              '_yoast_wpseo_metadesc',
              '_yoast_wpseo_focuskw',
              '_yoast_wpseo_meta-robots-noindex',
              '_yoast_wpseo_meta-robots-nofollow',
              '_yoast_wpseo_meta-robots-adv',
              '_yoast_wpseo_canonical',
              '_yoast_wpseo_redirect',
              '_yoast_wpseo_opengraph-description',
          );

          foreach( $allowed_yoast_keywords as $field) {
              register_meta( 'post', $field, array( 'show_in_rest' => true ) );
          }

      }
    }

?>

Building Urls

If you are building a front-end app on top of WordPress, you have to think about how to structure your urls. WordPress has two default post-types (posts & pages) and in the urls is not distinguished which type you are requesting, so http://wp-site.expl/something might lead to a page or a post, dependent on the type of the object with the slug something.

That means, that if you want to mirror that behaviour in your app, you have to do two requests for each url, one searching pages, one searching posts. To make that one request, use the following.

GET: /wp-json/wp/v2/slug/<post-or-page-slug>

<?php

/**
 * Adds a slug endpoint for getting the page or post for a given slug
 */
add_action('rest_api_init', 'wuxt_slug_route');


function wuxt_slug_route()
{
    register_rest_route('wp', '/v2/slug/(?P<slug>\S+)', array(
        'methods'  => 'GET',
        'callback' => 'wuxt_get_slug'
    ));
}


function wuxt_get_slug($object)
{

    $slug = $object->get_param('slug');

    $request = new WP_REST_Request('GET', '/wp/v2/posts');
    $request->set_param('slug', $slug);

    $response = rest_do_request($request);

    if (!$response->data) {

        $request = new WP_REST_Request('GET', '/wp/v2/pages');
        $request->set_param('slug', $slug);

        $response = rest_do_request($request);
    }

    if (!$response->data) {
        return new WP_Error('wuxt_no_such_slug', __('Slug does not exist'), array('status' => 404));
    }

    $embed = $object->get_param('_embed') !== NULL;
    $data = rest_get_server()->response_to_data($response, $embed);

    return $data[0];
}

?>

More to come …

Hope you found something of the code above useful. Send me your extensions in the comments and I will happily integrate them!

When I’m running WordPress inside a docker container, I usually only mirror the wp-content directory from the container to the host. You shouldn’t change anything in the other WordPress directories anyway and you avoid many volume syncs. The problem with this approach is that you can’t run the wp-command from the host, you have to run WP-CLI inside Docker, that means logging in to the container running the commands and logging out again. Assuming your container is called wp-container the following commands would do that:

  
docker exec -ti wp-container bash
wp plugin list
exit
  

It’s just a little inconvenience, but why not save some command when we can?

I’m using gulp as task-manager for most of my projects, and added a task which forwards all commands to WP-CLI inside Docker. It even checks if the container is running and if WP-CLI is installed inside the container. If not it will be automatically installed.

var gulp = require('gulp');
var spawn = require('child_process').spawn;

var checkContainers = function(names, done) {
  var exec = require('child_process').exec;
  exec('docker ps --format {{.Names}}', function(error, stdout, stderr) {
    done(names.map(function(name) {
      return stdout.split("\n").indexOf(name) >= 0;
    }).reduce(function(running, next) {
      return running && next;
    }, true));
  });
};

var checkWPCli = function(container, done) {
  var exec = require('child_process').exec;
  exec('docker exec ' + container + ' bash -c \'wp\'', function(error, stdout, stderr) {}).on('exit', function(code) {
    done(127 !== code);
  });
};

var installWPCli = function(container, done) {
  var exec = require('child_process').exec;
  exec('docker exec ' + container + ' bash -c \'apt-get update && apt-get install -y less && curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp && wp --allow-root cli\'', function(error, stdout, stderr) {}).on('exit', function(code) {
    done(0 === code);
  });
};

var runWPCli = function(container, done) {
  var command = process.argv.slice(process.argv.indexOf('-c') + 1);

  if (!command.length) {
    console.log('ERROR: Provide a valid wp-cli command!');
    return done();
  }

  var exec = require('child_process').exec;
  exec('docker exec ' + container + ' bash -c \'wp --allow-root ' + command.join(' ') + '\'', function(error, stdout, stderr) {
    console.log(stdout);
  }).on('exit', function(code) {
    done();
  });
};

gulp.task( 'wp', function(done) {

  checkContainers(['wp-container'], function(containerRunning) {

    if (!containerRunning) {
      console.log('ERROR: wp-container container is not running. Try "docker-compose up -d"')
      return done();
    }

    checkWPCli('wp-container', function(wpCliRunning) {
      if (!wpCliRunning) {

        console.log('WARNING: wp cli not installed, trying auto install ...');

        installWPCli('wp-container', function(wpCliRunning) {

          console.log('SUCCESS: wp cli installed!');
          runWPCli('wp-container', done);

        });
      } else {

        runWPCli('wp-container', done);

      }
    });
  });

});

So, with that gulp task in place, you will be able to run the following commands and every other WP-CLI command as well:

gulp wp -c "plugin list"
gulp wp -c "plugin install advanced-custom-fields"

What do you think?

Is that to much hassle for running some wp-commands or would you use the gulp forward in your daily work? Let me know!