• 2023

  • WordPress is 20 Years Old

  • Happy Birthday WordPress! 20 looks good on you!

    For me, it began with Xanga and Myspace. For others, maybe it was LiveJournal or something else. Blogging exploded during this decade like never before. Blogging began as a teenage refuge. An outlet for my younger self to express myself. It gave me incredible relief to share my thoughts, ideas, and interests. It overjoyed me when others would join me in the song and dance of commenting or sharing my posts. To no one’s surprise, millions of others felt the same way.

    At university, that interest remained strong and grew even further as I delved deeper into a design career. As my design education went on, it became clear that university could only go so far in terms of teaching web development. To scratch my itch for building for the web, I got involved in as many web-related projects as possible. We built websites for parties, school projects, web forms, magazines, and stores. I desperately wanted to build websites as a career and express myself even further as a design-orientated web developer.

    When WordPress was introduced to me, I couldn’t believe it. It was like the missing piece of the puzzle. Manually editing HTML files as blog posts instantly became a thing of the past. Everything, just clicked for me with it too. The WordPress Template Hierarchy blew my mind. Understanding the ins-and-outs of how WordPress works, made me instantly hirable basically anywhere for a short while. Hand-building WordPress sites for clients was a complete joy, and later, building site-generators was even more fun.

    Maintaining a legacy WordPress site is like being working on an Audi sometimes. But, WordPress at large has been one of the greatest joys of web publishing since Markdown and I’m so excited for what comes next. I owe the WordPress community so much, so here’s to another year around the sun WordPress!

    Today is the 20th anniversary of the first release of WordPress. None of us knew what we were getting into when it started, but we had a shared conviction that the four freedoms of the GPL combined with a mission to democratize publishing was something worth spending our time on. There will be celebrations in cities around the world, please join if there’s one happening near you.

  • 2022

  • An image generated by DALL-E: "an aerial photo of geometric farmland"

    So long Twitter, hello fediverse

  • For as long as I’ve known the web, I have known the little textarea element. It’s a simple element. In all likelihood, it’s just about as ubiquitous as the input tag on websites. It’s a captivating little thing. Before the modern inventions of React or complex JavaScript libraries, all it ever really contained was text.

    I suppose that’s the case to this today. But, it has long evolved into a springboard for authoring webpages. Modern publication inventions such as tweets, blogs, posts, blocks and countless others all stem from the textarea (sprinkle in some JavaScript magic, some drop zones and you have yourself a little “composer” where you can add images, video and more).

    Looking over the W3C spec for the textarea and looking back on SMS character limits (they were varied to say the least), it doesn’t take much imagination to see why Twitter came about in the first place. It seemed that short-form blogging was always destined to become a thing. Twitter’s success can largely be attributed to the fact that there’s really a lack of competition in the short-form blogging space.

    Tumblr and WordPress have always occupied the space between short and long-form blogging, sure. But, the spiritual successor to status messages (aka away messages)? Twitter has owned that (and marketed themselves as such) ever since it became a mainstream social network.

    While Twitter’s previous management has a long and well-documented history of running this company into the ground, Musk maintains no exception either. He’s nosediving and it’s headed for a calamitous user exodus. With no Trust & Safety board and a hostile CEO at the helm, banning tweets to Mastodon, banning journalists, then re-instating some of them — he’s clearly on a tyrannical, pathetic war path toward creating a platform that benefits Elon, elites and promotes a right-wing stochastic terrorist echo chamber.

    Needless to say, I’m getting the heck out of dodge. Like many others before me, I am kissing my neat little Twitter handle goodbye. Meanwhile, I was enthralled and delighted by Matt Mullenweg’s Decoder interview. There’s a brief point where he discusses what it means to be a good steward of Tumblr and how it has humbled him. Between Automattic’s Tumblr, the fediverse and this blog (which is also powered by WordPress an Automattic invention), I’m absolutely delighted to leave Twitter behind. Not to mention, the kind folks at Tumblr are considering adding ActivityPub support to their network which would effectively make Tumblr that largest Mastodon instance on the fediverse.

    You may be asking, “but, wait — how will I find my friends on Mastodon?!” Well, I have some good news! First off, don’t deactivate your Twitter. Follow these steps to get started with Mastodon

    1. First, join an instance. Doesn’t matter where you join! You can move freely about the fediverse. Think of each instance like an email handle.
    2. Next, add your Mastodon handle to your Twitter profile (this will make it easier for folks to find you in the fediverse).
    3. Finally, go here and sign in with your Twitter creds to find your friends who have also moved to the fediverse: movetodon.org

    With your help Twitter can be given a proper burial. It should go down as one of the worst acquisitions in business history and become the cautionary tale that it deserves. The textarea and microblogging on the other hand is never going away. In fact, I would argue that the slow death of Twitter reveals what we all want deep down — each of us want to own a little piece of web. One step closer to a de-commodified web utopia.

    Update: Elon banned links to Instagram, Mastodon and other social platforms and then reversed that decision. Then ran a poll on wether or not to step down, which ended with 58% in favor of him stepping down. Despite claiming he would abide by the results, there’s been no indication he would do so. Even more concerning, it appears he’s spellbound by the idea of restricting poll voting on Twitter to Blue subscribers. Welcome to hell, Elon.

    If you are so inclined, you can find me in several places on the web now!

    I’m on Tumblr where I may shitpost, share photos, re-blog cool things and whatnot: tumblr.com/petrey

    Elsewhere, on the fediverse I have several handles. However, I’m mostly here with my fellow hackers and unix computer club community: tilde.zone/@petrey

    Follow me wherever you’d like, but wherever you go, this blog remains ✨

  • 2020

  • Refactored and Re-loaded

  • It’s a tough business to manage a website.

    Between, the pandemic, looming anxiety, and a medically necessary surgery to repair my right knee (more on that later), it seems I’ve left my website in a sorry state for a few months now. I migrated my website to a static infrastructure thanks to Gatsby and Netlify earlier this year. I’ve largely been really happy with this setup of publishing in WordPress, and triggering Netlify page/post generations via a web hook. The pagespeed score alone was incredible. I could sing praises all day long, but what I want to write about today, is refactoring.

    I let my v1 repo sit untouched for roughly 8 months. Yikes. I had plenty of errors and was hastily written. It was not typed at all, and was largely a mess. I decided to get serious, and wrote a list of must-haves for a v2:

    • Typescript baby
    • styled-components
    • Better support for more post types (such as bookmarks or projects)
    • More effective linting setup and better error handling
    • Client-side Apollo-powered GraphQL queries for bookmarks and dynamic content where possible (such as comments, coming later)
    • DRY out the code base, and remove unused dependencies
    • More effective theming approach

    A tall order indeed. However, it was possible! I powered through it, and renamed my .js files to .ts and .tsx and largely followed Gatsby’s own provided typescript example, which was really helpful when it came time to debug build-time errors locally. As I was working on this refactor, I scoped out Brian Lovin’s website and just fell in love with the feed/timeline style he uses to organize his bookmarks. So, naturally, I took great inspiration from him to build something similar:

    I’m pretty pleased with the results. I’ve wanted something like this for a very long time. So, I want to take a moment to thank Brian for open sourcing his site (although his site is powered with next.js, it was an inspiration none the less). It paved the road for me to investigate a path forward building a similar suite of components using Apollo Client and wp-graphql.

    Ultimately, the list of changes and new features led to this nightmare diff (mostly configuration and newlines to get typescript working):


    But it built successfully, and I was happy to finally have Typescript at the ready.

    Up next, I will be finally adding back the commenting ability to my blog posts (and maybe setup a proper site search?). After that, I’ll probably add some portfolio and personal work. But, until then I will be resting comfortably sipping IPAs and mulled wine during this holiday, playing video games and pampering my two buds, Frank and Kevin:

    Merry Christmas!

  • New site, who dis

  • I haven’t developed a new WordPress theme for this website (that I really loved) in a long time. So naturally, it was time to re-examine my personal tech stack. If you personally know me, you know I’m a WordPress advocate. Big fan of ol trusty.

    There’s a few problems with WordPress I’ve been hung up on for a while now. It can be a bit slow (sometimes). Media managing can be painful on the front-end. Deployment processes are… all over the place. Historically, I’ve been a huge fan of Trellis from Roots. While we’re at it, I was a big fan of Bedrock too. Finally, I work with React and JavaScript all day long — why can’y my personal website run on a modern tech stack too?

    Well, I came across this post from Chris Coyier, and I was pretty much sold on the JAMStack concept immediately. At work, and at other organizations like Twitter, server-rendered pages are stupid fastest. Heck, they’re typically standard these days for most web apps. Enter the Gatsby + WordPress stack. Why abandon years of publishing paradigms when you can keep them?

    There’s no shortage of tutorials on the Gatsby + WordPress setup. Initially I played around with some starter projects, like this one. I personally started (and later forked) with egghead.io’s starter. It’s pretty spectacular out-of-the-box, and if you enjoy love @emotion theming or styled-components, I think you’ll dig too. But, really Gatsby itself has a lot to love. For example, Gatsby has drop-in support for server-rendering. Super cool stuff. I really enjoyed Juliano Rafael’s (@frontendwizard) notes on this subject:

    Progressive image loading? Inlining of critical CSS? Painless PWA configuration? You name it, Gatsby got you. It is really impressive. Don’t believe me? I encourage you to try it out.

    The solution for images is so good, that is constantly referred as a strong point of Gatsby, even thought it actually is a plugin. All you gotta do is add a couple of plugins into your Gatsby config file and you’re good to go. Your images will be available on the GraphQL API at build time and the image component will handle all the resize, picking the best file format and everything else. You even get a blur up effect to improve the user experience for free.

    That’s just the tip of the iceberg. Seriously. These web apps freakin purr. I’m overdue for writing a full tutorial on this subject, so stay tuned. But, for now I want to share a high-level overview of the technical lift I undertook for my site migration.

    My order of operations (yours might be slightly different):

    Now that we have our API-site prepped for deploy-hooks, exposed the frontpage and menus endpoints — we’re ready to consume the API with our server-rendered React app powered by Gatsby. Here’s a sample of my gatsby-config.js file:

    resolve: `gatsby-source-wordpress`,
    options: {
      // Your API WordPress source goes here.
      baseUrl: `example-api.stephen.news`,
      protocol: `https`,
      // Fetches posts, tags, categories, etc from the baseUrl.
      includedRoutes: [
      useACF: false,

    I absolutely love this setup.

    All of the un-fun configuration stuff is already handled by the WordPress/WP REST API side, and all of the actually fun conventional interface building is done on the… well, the interface side. A complete separation of church and state, if you will. It’s a thing of beauty. Absolute zen.

  • 2019

  • Verizon Sells Tumblr to Automattic, the Home of WordPress

  • The WSJ reports:

    Verizon Communications Inc. has agreed to sell its blogging website Tumblr to the owner of popular online-publishing tool WordPress, unloading for a nominal amount a site that once fetched a purchase price of more than $1 billion.

    Automattic Inc. will buy Tumblr for an undisclosed sum and take on about 200 staffers, the companies said. Tumblr is a free service that hosts millions of blogs where users can upload photos, music and art, but it has been dwarfed by Facebook, Reddit and other services.

    This is a shocking acquisition. No doubt, this a good move for the preservation of blogs. I firmly believe Automattic will be a better steward of creators than Verizon (or was it called Oath?) would’ve been. I have long awaited the day where Tumblr and WordPress have publication parity. This has to be really excited for everyone at Tumblr. I mean this even has Tumblr veteran Marco Arment pumped:

    Edit: it appears that Marco Arment deleted this tweet:

    This is pretty cool. Can’t think of a better owner today than Automattic for Tumblr’s huge creative publishing community.

    Marco Arment

    Now the hard question — what about the adult content ban? For now, it seems the ban stays in place. But, it’s really unclear if Matt will ever changed the policy. Regardless, the blogs at Tumblr will live on under the safe and profitable umbrella of Automattic.

  • How to Disable WordPress Emojis

  • First off, I didn’t write this. Ryan Hellyer did. I only came across this fix from a post written by Justin Downey. The short-end of it is, drop this block of code into the bottom of your functions.php file. I made some tiny tweaks:

    if (!function_exists('disable_emojis')) {
      function disable_emojis() {
        remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
        remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
        remove_action( 'wp_print_styles', 'print_emoji_styles' );
        remove_action( 'admin_print_styles', 'print_emoji_styles' );
        remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
        remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
        remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
      add_action( 'init', 'disable_emojis' );

    I really needed this personally, because MailChimp was parsing my RSS feed verbatim, and for whatever reason, when WordPress added Twemoji to core in 4.2 it forces the emoji markup to be an inline-image — which, I get it. It’s useful for older devices who may not support emoji. But it was completely wrecking my email campaigns styles, yuck! 🤢

    Thankfully, this function works in disabling the Twemoji images altogether! Tested up to: 5.2.2 👍

  • Revisiting Blogging in 2019

  • Jason frequently hits the nail on the head. It’s pretty rare that predictions of his don’t hold water. He’s the proprietor of the kottke.org. One of those great places your can find on the web where name is the address.

    2019 is surely going to be a golden year for bloggers, web journals, and anyone who has “exited social media”. The perfect storm has arrived. The Facebook exodus. The Snapchat well has dried up. The paywalls are going up left and right. Hell, even George R. R. Martin is migrating his old LiveJournal posts to a personal author blog he can manage on his own. My personal favorite — people and organizations are leaving Medium (and going back to WordPress), and honestly it’s been a long-time coming. Medium’s open paywall system hasn’t fared very well. Apparently the revenue falls short, and the traffic is low-quality.

    What’s old is new again. 2019 is the year we take back blogging ✌️

  • 2018

  • A screen full oh beautiful PHP

    PHP Namespaces

  • If you build or customize WordPress themes, coming across Namespaces are going to become more and more commonplace. I think they’re pretty cool. I have never enjoyed having to write (or parse for that matter), custom_functions_with_a_weird_long_name.

    Steve Grunwell has a fantastic primer on Namespaces, and how to use them in WordPress. I also learned about Singletons (and their problems in a OOP environment) and why Namespaces are effective.

    In short, functions are typically defined in plugins a variety of ways. Not long ago, developers might have written a function like this:

    function get_recent_posts( $limit = 5 ) {
      return new WP_Query( array(
        'post_type'      => 'post',
        'post_status'    => 'publish',
        'orderby'        => 'post_date',
        'order'          => 'desc',
        'posts_per_page' => $limit,
      ) );

    Innocent, naive function writing.

    Function name collisions are were pretty likely to occur, so we got longer and longer functions which led to Class encapsulations, so these functions began to make a mess of your codebase with global variable declarations. Steve goes on:

    From here, developers seem to have split into two factions: the first, dead-set on removing global variables, moved toward the Singleton pattern:

    class MyPlugin() {
      protected static $instance;
      protected function __construct() {
        // Made protected to prevent `new MyPlugin()` calls.
      public function get_instance() {
        if ( ! self::$instance ) {
          self::$instance = new MyPlugin();
        return self::$instance;
      public function get_recent_posts( $limit = 5 ) {
        return new WP_Query( /* ... */ );
    # Usage:
    $plugin       = MyPlugin::get_instance();
    $recent_posts = $plugin->get_recent_posts();

    Yikes, what a pickle… But, behold! The proper (and more maintainable effort) with Namespaces:

    namespace SteveGrunwell\MyPlugin;
    use WP_Query;
    function get_recent_posts( $limit = 5 ) {
      return new WP_Query( array(
        'post_type'      => 'post',
        'post_status'    => 'publish',
        'orderby'        => 'post_date',
        'order'          => 'desc',
        'posts_per_page' => $limit,
      ) );

    What a wild and crazy ride! Seriously, give Steve’s post a full read.

  • An Inside Look at The New York Times Publishing Tech Stack

  • As a WordPress Guy, and advocate for online publishing — I’ve always been curious about the New York Times digital publishing “stack.” A lot has changed since the printed word.

    Text editors, paragraph blocks, header arrangements, interactive graphs, revision history, and data structures — journalists these days have a lot more responsibilities to handle than just a story. They’re more akin to data scientists or librarians handling meta-data with caution and organizational finesse than the classical depiction of journalists.

    I was surprised to read that the New York Times employs a CMS called Oak, which was built on the backbone of a simple text editor called ProseMirror. An un-opinionated open-source text editor. I was taken back by this line: 

    ProseMirror structures its main elements — paragraphs, headings, lists, images, etc. — as nodes. Many nodes can have child nodes — e.g., a heading_basic node can have child nodes including a heading1 node, a byline node, a timestamp node and image nodes. This leads to the tree-like structure I mentioned above.

    It’s neat to see their CMS take on a node-tree structure (which is ultimately a appropriation other have taken as well such as HTML/XML/JSON). It’s smart. I would have loved to see what their process and CMS looked like before.

    Did you notice the parent-nodes have a similarity to another data structure paradigm, say blocks? In WordPress-land, blocks are coming soon, and once that happens, the data-structure pandora’s box will have been opened. In the simplest, most modest comparison, Gutenberg will only have parent blocks. No children. But that could all change later down the road. 

    Food for thought: if the New York Times can transition to a successful implementation of complex data structures, modern CMS composer capabilities — surely WordPress/Gutenberg could employ similar techniques and follow in that same spirit.


  • A collection of notebooks on shelves

    Links: March 2018

  • This month I collected some interesting links worth sharing. Some notables include: an iOS AirPod concept design, Starbucks bathroom reviews website, a post from Scott Galloway on marriage, and the technology behind my website page transitions and more.

    Starbucks Bathroom Reviews

    Living in NYC, finding a clean public bathroom can be difficult. Finding one without a security code can be even more rare. Let this incredible website be your guide. 😎

    Reading Time WP

    This plugin looks cool. It provides a reading time estimate for your posts in WordPress. Gonna check it out. Stay tuned.


    If you’re wondering how I setup page transitions on my website — barba.js is how. I fully plan on writing a installation/customization tutorial at some point. It’s weighs in at 4.4 kb JS file which isn’t shabby. And has all sorts of neat bells and whistles.

    iOS Airpods Sharing Concept

    Uhm. This is seriously so fucking cool. What a great idea. This whole concept stems from such unique problem that requires a unique solution:

    The AirPods are arguable one of Apple’s best products, their simplicity, and intuitiveness make them an essential daily device. However one disadvantage is the lack of ability to share music with friends. Back in the 3.5mm analog days, this was accomplished with a headphone splitter […]

    […] this is easily achievable with an iOS software update that could enable users to share their iPhone or iPad’s audio with multiple friends who also have AirPods:

    Knockoff iPhone X Notches

    Imitation is the sincerest form of flattery I suppose.

    I Do

    I follow Scott Galloway pretty religiously. Back in 2015, my friend Tim Kosters (@sretsok) introduced me to his You’veTube channel. Scott is an accomplished professor at NYU, and previously been a board member at Eddie Bauer, The New York Times Company, and a few others. From time to time Scott pens personal advice and this one hit home for me.

    In my experience, the most rewarding things in life are family and professional achievement. Without someone to share these things with, you’ve seen a ghost — it sort of happened, but not really. However, with the right partner, these things feel real, you feel more connected to the species, and all “this” begins to register meaning.

    Not quite a blogroll, but maybe a start

    Chris (@chofficehours) of iwantmyname.com runs a fantastic blog worth subscribing. Lately, he’s been writing a lot about remote worklife, blogrolls, and blogs (with a little b like this one). There’s a growing problem with the Twitter echo chamber, I don’t feel like I’m part of any community on Medium, and I go to Hacker News a little too often — what’s the fix? Blogrolls? More blog aggregators? Interesting words from an interesting guy.

  • 2017

  • How to Automatically Generate Alt Text in WordPress

  • Accessibility is important. But it’s also difficult to manage. The a11y checklist is a great resource to ensure your web projects are up to snuff. WordPress has some accessibility baked in, but it doesn’t check all the boxes.

    Wether you’re on a small team, or managing a WordPress install by yourself — ensuring all your images have alt text can be a real pain in the rear.

    Enter Automatic Alternative Text. This plugin is fantastic! It uses Microsofts Computer Vision API which is pretty nifty. You can play around with the tool here.

    From Microsoft Azure. Good stuff.

    Upon activating the plugin, you head over to the Microsoft Computer Vision API Center and grab an API Key for the plugin. Enter the key in Settings > Media and you’re off. Here’s an example of the plugin in action.

    I bumped the confidence threshold down to 5% just to see what it would pump out. So far, it’s been pretty accurate. Personally, screenshots and PNGs tend to throw off the generator in my tests. Quick sidenote: the plugin doesn’t seem to work with WordPress 4.9 at the moment, but it will work up to WordPress 4.8.3

    That being said, the plugin will only generate alt text for images uploaded after activation. It won’t add alt text to images previously uploaded. Not a huge ordeal, but moving forward the computer vision generated alt text will help you keep you site’s images a11y compliant without breaking a sweat.

    If you liked this post, make sure to Like it below! Subscribe to my monthly newsletter Hypertext Digest, and never miss a post 😀

  • How to Install the Latest Beta of WordPress with Composer

  • Want to checkout the latest version of WordPress? Or Gutenberg? Just swap your WordPress version in your composer file with the dev-master. Caveat: it requires the wordpress-core dev-master. Could prove useful for testing.

    ...shortened for brevity...
    "johnpbloch/wordpress-core": "dev-master",
    "johnpbloch/wordpress": "dev-master",
  • Remote Server Setup with Trellis

  • We’re going to setup a remote server (say hosted on DigitalOcean), with Trellis’ automated deployments. It’s magical. Oddly enough, as of 2018, this guide is still pretty much up date and relatively unchanged.

    We started by getting our local environment setup but now we need to get our production environment setup so we can get deploys going from our local machine. With Trellis, provisioning and deploying occurs from the trellis directory.

    Remember our structure?

    example.com/      # → Root folder for your project
    ├── trellis/      # → You'ver clone of Trellis
    └── site/         # → A Bedrock-based WordPress site
        └── web/
            ├── app/  # → WordPress content directory (themes, plugins, etc.)
            └── wp/   # → WordPress core (don't touch!)

    1. Copy group_vars

    Copy your wordpress_sites from your working development site in group_vars/development/wordpress_sites.yml to the production environment group_vars/production/wordpress_sites.yml.

    Modify your site and add the necessary settings for remote servers:

    • repo – URL of the Git repo of your Bedrock project (required)
    • repo_subtree_path – relative path to your Bedrock/WP directory in your repo (above) if its not the root (like site/ in roots-example-project)
    • branch – the branch name, tag name, or commit SHA1 you want to deploy (default: master)
    • env – environment variables (very important)
      • auth_key – Generate (required in vault.yml)
      • secure_auth_key – Generate (required in vault.yml)
      • logged_in_key – Generate (required in vault.yml)
      • nonce_key – Generate (required in vault.yml)
      • auth_salt – Generate (required in vault.yml)
      • secure_auth_salt – Generate (required in vault.yml)
      • logged_in_salt – Generate (required in vault.yml)
      • nonce_salt – Generate (required in vault.yml)

    You’ve’ll want to make sure that you change all instances of example.local (in your production vars) and switch it to example.com Otherwise you may get some errors.

    2. Setup Hosts and Users

    Create a Digitalocean droplet configured for Ubuntu 16.04.1 in whatever size you may need.


    Once you get your droplet setup, you’ll need to SSH into the server to change the default password DigitalOcean emails you.

    Next, add your server IP (from the droplet) to your hosts/<environment>. Specify public SSH keys for users in group_vars/all/users.yml so Trellis can access your droplet. See the Trellis’ SSH Keys docs for more information.

    3. Provision

    Run ansible-playbook server.yml -e env=<environment>. and Hopefully you get a green light and no errors. If you do get errors, troubleshoot and try again. Trellis has verbose warnings and errors so they don’t leave you high and dry.

    4. Deploying

    To deploy, ensure that your site folder is in a public repository on Github.

    Next, run ssh-add -K, this will add you SSH key to your shh-agent (if you’ve created one, also it’s good practice to add your keys to your Github account). Then, from the Trellis directory, run ./bin/deploy.sh production example.com and watch as your terminal deploys your most recent commit.

    Previous: The WordPress Roots Stack
  • Working with WordPress Locally with the Roots Stack

  • Okay, so it’s been a while since last time I wrote about Roots (shoutout to Rob for pointing it out). The last time I wrote about the Roots Stack, things were more or less the same. But I felt compelled to write this guide ever since Sage got a version bump.

    Secondly, no one enjoys spending precious hours configuring their local setup using a MAMP GUI or fucking up your machine’s hosts file or deliberating on how to fix the dreaded error establishing a database connection. What I love most about Roots is the priority on convention over configuration. 

    It’s literally a playbook taken from Rails. The net gain of this philosophy is twofold:

    1. Lowered barrier to entry for beginners
    2. Productivity bonus, spend more time hacking and less time crying

    The Roots framework has huge benefits for beginners, and even bigger returns for WordPress veterans. Mainly because you’ll no longer have to FTP your deployments like a goddamn barbarian. But I’ll go into more detail on remote server setup in my next post.

    Before I get side-tracked, let’s just dive right in shall we?


    Let’s talk about requirements and assumptions. I’m assuming you’re using Sublime Text 3 (all other versions make sure you have this enabled) and you understand how to use your terminal. Follow the links below and install the following software.


    Below is what we want our project too roughly look like:

    example.com/      # → Root folder for the project
    ├── trellis/      # → You'ver clone of Trellis
    └── site/         # → A Bedrock-based WordPress site (cloned as well)
        └── web/
            ├── app/  # → WordPress content directory (themes, plugins, etc.)
            └── wp/   # → WordPress core (don't touch!)

    Create a Sites folder if you haven’t already. I typically put all of my web projects in a Sites folder in my home directory on my Mac. To get started, we’re going to make a directory called example.com — you can name yours whatever project name you’d like:

    cd ~/Sites && mkdir example.com && cd example.com

    You should be inside the example.com directory at this point. Next we’re going to clone Trellis:

    git clone --depth=1 git@github.com:roots/trellis.git && rm -rf trellis/.git

    Then we’re going to clone Bedrock (which contains WP Core and our web app, etc.):

    git clone --depth=1 git@github.com:roots/bedrock.git site && rm -rf site/.git

    At this point, our example.com directory should look like this:

    example.com/      # → Root folder for the project
    ├── trellis/      # → You'ver clone of Trellis
    └── site/         # → A Bedrock-based WordPress site (cloned as well)

    You’ve’re doing great!

    Trellis vars

    Now let’s get into something a bit more challenging. We’re going into the Trellis directory and need to edit some variables to bring this local WordPress installation to life. I’m assuming you have Sublime Text 3 installed and you’re still in the root of example.com

    Edit the wordpress_sites.yml file first:

    subl trellis/group_vars/development/wordpress_sites.yml

    Then just make sure it looks like this. Replace example.com with the name of the project you have chosen. This is pretty important at this point, as we start getting into the nitty-gritty.

    # Make sure your file looks like this
          - canonical: example.test
              - www.example.test
        local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root)
        admin_email: admin@example.test
          enabled: false
          enabled: false
          provider: self-signed
          enabled: false

    Now go ahead and edit the vault.yml file as well:

    subl trellis/group_vars/development/vault.yml
    # Make sure the site name (`example.com`) must match up with the site name
    # in the file we previously edited: `group_vars/development/wordpress_sites.yml`
    # By default Trellis will provision a user `admin` too, below you can set a different
    # password if you'd like
        admin_password: admin
          db_password: example_dbpassword

    Don’t worry about the other .yml files in the development folder for now.


    Vagrant is our savior. It sits between us and the VirtualBox, and does most of the talking. To get our machine running our new local setup we configured (in the wordpress_sites.yml and vault.yml files), we have to go down into our trellis directory and get vagrant running:

    cd trellis && vagrant up

    By now you should see this a few commands trickling in. At some point vagrant will ask for your sudo password because it needs root permissions.

    Enter your password and vagrant will begin installing, provisioning and start running tasks your virtual-machine.

    At this point, take a break and chill. The total installation/provision time varies from machine to machine, but it takes around 10-15 minutes on a mid-2015 13″ MacBook Pro. Once the provisioner is done (provided that no errors stopped the installation), you can open your browser and navigate to example.test and you should see this:

    Getting started with theme development with Sage

    Now that we have a local development environment setup and running, we can move onto creating our first theme! The Roots team put together an awesome starter-theme to speed up development.

    Remember our project structure? We’re going to cd our way into the themes directory:

    example.com/      # → Root folder for the project
    ├── trellis/      # → Trellis
    └── site/         # → A Bedrock-based WordPress site
        └── web/
            ├── app/  # → WordPress content directory (themes, plugins, etc.)
            |   ├── mu-plugins/  # Must-use plugins live here
            |   ├── plugins/     # Plugins live here, managed by composer.json in the `site` folder
            |   ├── themes/      # We're going to create our theme HERE.
            |   └── uploads/     # WordPress will put uploads here (I wouldn't mess with these)
            └── wp/   # → WordPress core (don't touch! Like ever!)

    Once we’re in the themes directory, we’re going to run a composer command:

    composer create-project roots/sage example-theme 8.5.3

    Basically, the previous command says:

    1. create a composer project with the the roots/sage package
    2. name the theme, “example-theme”
    3. choose the 8.5.3 version of the roots/sage package

    As of October 17, 2017 the LTS Sage version is at 8.5.3 which is why I chose that version. You should see composer downloading the packages (be patient, sometimes composer can be slow)

    Sage requires Node.js to function. Make sure you are up-to-date.

    npm install -g npm@latest

    We’re going to use Gulp to build assets (styles, scripts, etc) and Bower will handle any front-end packages such as Bootstrap or Flickity. To get started, cd inside our new theme example-theme. We’re going to install Gulp and Bower globally (if you haven’t already)

    npm install -g gulp bower

    Now, were’ going to install all the dependencies listed in the package.json

    npm install && bower install

    Next, let’s open our manifest.json in Sublime Text and configure BrowserSync to proxy our dev URL

    subl assets/manifest.json

    Make sure the devURL reflects the canonical hostname we wrote in wordpress_sites.yml earlier since our vagrant machine will be listening for traffic at that URL.

      "config": {
        "devUrl": "http://example.test"

    At this point, we can go to example.test/wp-admin, and login. From the dashboard, choose Appearance > Themes. Pick your Sage Theme and Visit Site. Everything will look broken now, as no styles exist 🙁

    But don’t fret! Next, we go back to the terminal (make sure you’re still in the new theme directory), and run gulp watch. A new tab should open on your browser. That’s BrowserSync loading style changes in real-time (no more ⌘R in Chrome). Save any of the SCSS files in the assets/styles directory and your webpage should reload with the changes. Also watch your terminal 👀

    Gulp literally watches all PHP, SCSS and JS files changes. And BrowserSync will reload your browser window. Pretty nifty. Assuming you didn’t get any errors, your homepage should load with basic styles thanks to Normalizer and Bootstrap.

    From here on out, the world is your oyster!

    Sage doesn’t give you everything you need for WordPress development but it’s speedy, it’s git-friendly, and it’s modern. I’m not kidding when I say this… the Roots Stack is literally my favorite way to interface with WordPress. Any other way just feels primitive and old.

    It gets even better with deployments and remote provisioning which I will cover in my next post. 

    Further Reading:

    Next: Remote Server Setup with Trellis
  • How to Embed Your Unsplash Photos in WordPress

  • Wouldn’t it be neat to embed your Unsplash photography on your WordPress site?  Unfortunately, there’s no WordPress plugin available. If you’re not familiar, Unsplash is a huge library of photographs for commercial and noncommercial purposes. It’s an awesome resource for designers, prototypers, and developers alike. 

    If you have a profile with Unsplash, you should check out their API docs. The devs provide some wrappers too. There’s a PHP, Ruby and JS wrapper. Which all-in-all, are pretty fucking dope.

    So yeah, I figured it would be cool to add a showcase of my Unsplash photos on my website. Upon closer inspection of the Composer wrapper, it looked like it was overkill for my use. But if you’re gonna make a WordPress plugin using Unsplash, you’ll want to use it.

    So I began here — first I made a quick blueprint on how I wanted this to go down:

    Just a simple page, and a two column grid layout of my photos.

    Next, I took a looked through the Unsplash API docs again. Created an application, and received my Application ID. Very important step. The docs even had an example for listing a user’s photos. Hell yeah 😎

    From Unsplash’s Documention

    Seems easy yeah? Next, I went to check out what kind of HTTP Response I get from the API with hurl.it and everything is looking hunky-dory so far.

    So at this point I realized a few things:

    1. I need to make a simple HTTP request
    2. Decode the JSON
    3. Loop through the array and echo the goodies

    I decided on making a simple snippet below:

    If you leave line 19 uncommented, you can see the full array and figure out what you want to use from the HTTP request. You can copy/paste that snippet in any template or PHP file and it should work. NOTE: make sure to replace my username and swap YOUR_APPLICATION_ID for your actual App ID from Unsplash.

    After a few layers of paint my Photography page now looks like this:

    It’s quick and dirty, but at least I won’t have to upload my Unsplash photos to my website manually anymore. It’s all programmatic baby! I’m really happy how this turned out. I tried to make this as simple as possible and under 15 lines of code I think this rocks. But I hope someone else finds this useful!

    Let me know how if you have any feedback, improvement or philosophy on this implementation in the comments below. 🙂

  • How to Add Advanced Custom Fields to AMP by Automattic

  • Want to add your ACF’s to your AMP’s?

    If you’re unfamiliar with AMP (Accelerated Mobile Pages) by Google. It’s basically a new way to serve super fast, lightweight compiled versions of a webpage to mobile users. It has its own validation spec. Really really huge honking websites like Polygon, Washington Post and The Verge all use AMP on their news articles.

    Which is super awesome, because nothing sucks more than an article taking more than 15 seconds to load over “Transit Wi-fi” on the G Train. These super powered articles are literally instant, taking no more than 1 second to load — even over 4G speeds. Even after all of those pro’s Google takes it one step higher by giving search result priority to AMP powered-articles and pages for mobile searches.

    So the first thing you need to do is declare in your functions.php file that you want to use a custom AMP template. Mine looks like this:

      add_filter( 'amp_post_template_file', 'xyz_amp_set_custom_template', 10, 3 );
      function xyz_amp_set_custom_template( $file, $type, $post ) {
        if ( 'single' === $type ) {
          $file = dirname( __FILE__ ) . '/templates/template-amp.php';
        return $file;

    Then create a template file in your theme called template-amp.php. Mine is in a directory named templates.

    Copy the amp-wp single.php template from the Github master for the latest codebase.

    Once you’ve done that, you can add your ACF field in the template as usual! The caveat are ACF image fields which need some AMP-validating like so:

      <div class="amp-wp-article-content">
        <?php echo $this->get( 'post_amp_content' ); // amphtml content;?>
        <?php if( have_rows('editorial_flexible_components') ): ?>
          <?php while ( have_rows('editorial_flexible_components') ) : the_row(); ?>
            <?php if( get_row_layout() == 'paragraph' ): ?>
              <?php $paragraph_text = get_sub_field('paragraph_text'); ?>
            <?php elseif( get_row_layout() == 'image' ): ?>
              <?php // Grab the Image ID from the ACF ?>
              <?php $the_image = get_sub_field('the_image'); ?>
              <?php $image_url_array = wp_get_attachment_image_src($the_image, 'full', true); ?>
              <?php $image_url = $image_url_array[0]; ?>
              <?php // Displaying a ACF Image field that will validate ?>
              <img class="i-amphtml-fill-content i-amphtml-replaced-content" src="<?= $image_url; ?>" alt="" />
            <?php endif; ?>
          <?php endwhile; ?>
        <?php endif; ?>

    And now, the AMP will deliver your ACF content! Obviously I used Flexible Content fields in my example, but you can use any ACF field in the template-amp.php file, just make sure it validates.

    Before (left), and after (right):

  • 2016

  • WordPress Local Development on OS X with Trellis and Bedrock

  • I wrote a new tutorial since this one is a bit out of date.



    Okay so a few things have changed since my April post. I figured I’d write an update on how to get started with Trellis and Bedrock. There has been a ton of commits since then.

    Again, I’m assuming you’re on a Mac and have node, npm, bower and homebrew installed. If not start here. Things are a little different if you’re a Windows user.

    1. Install Git, Ansible and Composer

    brew install git ansible composer

    2. Install VirtualBox

    Install your VirtualBox software and follow the prompts.

    3. Install Vagrant

    Download and install the latest Vagrant.

    4. Starting a Trellis Project, Installing Bedrock and Sage

    By the time we’re done here, you should have a folder structure that looks like the following (taken from the Trellis Repo):

    example.com/      # → Root folder for your project
    ├── trellis/      # → You'ver clone of Trellis
    └── site/         # → A Bedrock-based WordPress site
        └── web/
            ├── app/  # → WordPress content directory (themes, plugins, etc.)
            └── wp/   # → WordPress core (don't touch!)


    1. Creat a Sites directory if you haven’t already and cd ~/SitesFirst, create a new project directory and cd to that directory: mkdir example.com && cd example.comClone Trellis and remove the gitfiles: git clone --depth=1 git@github.com:roots/trellis.git && rm -rf trellis/.gitClone Bedrock and remove gitfiles: git clone --depth=1 git@github.com:roots/bedrock.git site && rm -rf site/.gitInstall the Ansible Galaxy roles: cd trellis && ansible-galaxy install -r requirements.ymlInstall Sage in your themes directory, and install npm dependencies: cd ../site/web/app/themes composer create-project roots/sage example-theme dev-master && cd example-theme && npm install
      Now, we head back up the tree to Trellis. Here’s the current folder structure there. We need to configure the WordPress sites in group_vars/development/wordpress_sites.yml and in group_vars/development/vault.yml
    ├── trellis/ # → You'ver clone of Trellis
        └── deploy-hooks
        └── group_vars
        └── hosts
        └── lib/trellis
        └── roles

    Here’s an example configuration for group_vars/development/wordpress_sites.yml

          - canonical: example.dev
              - www.example.dev # optional redirect
        local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root)
        admin_email: admin@example.dev
          enabled: false
          enabled: false
          provider: self-signed
          enabled: false

    Here’s an example configuration for group_vars/development/vault.yml

    # Generate a secure root password
    vault_mysql_root_password: B3LkKUpcZVx4bpLXKXpiez%R
    # Variables to accompany `group_vars/development/wordpress_sites.yml`
    # Note: the site name (`example.com`) must match up with the site name in the above file.
        admin_password: admin
          db_password: example_dbpassword


    5. Let’s Take a Break

    Now that the configuration is done, we can begin the real magic. From the trellis directory, we run: vagrant up. Vagrant will ask for your password, and upon pressing enter it will provision a server and get you all setup. It should take about 10–15 minutes to install. So take a breather if you can.

    Luckily Trellis has some pretty verbose error messages and make sure to double check your group_vars if you have any issues. When the dust settles, head to example.dev and you should see this:


    Now that we got local development out of the way we can move onto your production setup. It’s all about dev/prod parity nowadays and honestly, it rocks.

    [button type=”success” size=”lg” link=”/blog/remote-server-setup-with-trellis”] Next: Remote Server Setup [/button]

  • Intro to Modern WordPress with Trellis, Sage and Bedrock

  • UPDATE: I updated this post here 👀

    WordPress is a beast. Setting up your local machine, mysql, keeping track of users, managing dependancies and plugins, setting up your vhosts—jeez, all of that can be a daunting task. Let’s automate some things.

    I’m making a few assumptions here. Such as you probably have Node, npm and Bower installed on your Mac. We’ll begin with Homebrew. Full disclosure, I forked Dave Kiss’ take on his champion WordPress workflow with some of my own tweaks. Feel free to check out his setup too, it’s really great.

    The whole walkthrough should take about 15-20 minutes of your time on a modern machine. Let’s get started.


    Curl Homebrew:

    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    Make sure to run brew doctor afterward to make sure you have everything installed all nice and neat.

    Git, Ansible and Composer

    Now that we have Homebrew installed, let’s go ahead and install some goodies for our workflow and get started. Composer will manage our plugins. It’s awesome.

    brew install git ansible composer

    Install VirtualBox

    Go ahead and install your VirtualBox software and follow the prompts.

    Install Vagrant

    Next, download and install Vagrant. Pretty painless.

    Beginning a Project with Trellis

    Next, mkdir -p ~/Sites/example.com where example.com is the name of the project.

    Then, go ahead and cd ~/Sites/example.comWe should be inside the example.com directory. Now we’re going to clone Trellis into a new directory called ansible with this command:

    git clone git@github.com:roots/trellis.git ansible

    Provision the Ansible roles and packages.

    cd ~/Sites/example.com/ansible && ansible-galaxy install -r requirements.yml


    Finally onto Bedrock. A simple WordPress boilerplate and configuration and improved folder structure for your project. Clone it into a new folder called site

    cd ~/Sites/example.com && git clone git@github.com:roots/bedrock.git site

    Installing the WordPress Core and any Other Composer Dependancies

    cd into the new folder called site and let’s get this party started.

    cd ~/Sites/example.com/site && composer install

    More Ansible Stuff

    If you’re like me, you probably have Sublime Text aliased. But click here if you haven’t aliased Sublime. Or if you’re into Vim and all that, that’s cool too.


    Go ahead and configure your db_namedb_user and db_password and everything else to your liking.

    Now the real fun stuff begins. We’re going to cd to the ansible sub-directory we previously created and start up vagrant and the virtual machine voodoo. It will prompt you for your system password because it’s going to add example.com to your /etc/hosts file. No biggie, but you should know.

    Also, this will take a while. Probably 10+ minutes or so.

    cd ~/Sites/example.com/ansible && vagrant up

    If you got any errors, don’t worry. Vagrant might ask you to install a few additional pieces of software for Vagrant. Go ahead and heed Vagrant’s advice. If it appears that your install falls into a timeout loop, run vagrant reload and hopefully that should resolve any timeout errors in the setup.

    You should be able to navigate to http://example.dev in your browser and see the fruits of your labor!


    Now that we have WordPress installed, let’s get a starter theme going with Sage. Let’s cd to the theme-root and clone the Sage repo over:

    cd ~/Sites/example.com/site/web/app/themes && git clone https://github.com/roots/sage.git

    Now let’s rename the Sage directory to our new-theme-name:

    mv sage new-theme-namecd to the new-theme-name and npm install && bower installNow you can run gulp in that directory and build your theme! http://example.dev should now have some styles. Sage comes with Bootstrap by default, but you can swap it out for whatever framework you want. Or no framework at all! Whew—that was a lot of steps but way less painful than it could’ve been! More about Sage here.