How I Built It

How I built it: Plugin Notes Plus

I released Plugin Notes Plus on the plugin repo in February, and it recently reached 200+ active installations. Although I built the plugin to “scratch my own itch,” it’s gratifying to know that others are finding it useful as well. I’m a big fan of the “How I Built It” podcast, so I thought it would be fun to write a post explaining how I built Plugin Notes Plus.

The Problem

The team I work with has notes in Evernote and Google Sheets with information about the plugins installed on the various sites we maintain. Crucially, some of those notes contain important information that one should know when updating the plugin. But those notes can be hard to find. What would be ideal would be to have a note right on the WordPress Admin Plugins page that would contain or link to important information about that plugin.

A survey of the existing offerings

I wasn’t the first one to have the idea to make a plugin notes plugin. Chris Coyier proposed the idea in 2009, and it was implemented by Mohammad Jangda with his plugin called Plugin Notes. I tested the plugin, and it worked as advertised except for one crucial flaw. When you click ‘delete’ to remove a note, it triggers the process of deleting the entire plugin. I found this support thread where the issue was identified, but the plugin doesn’t appear to be actively maintained.

Listening to an interview with Russell Aaron on How I Built It, I learned that the Maintainn Tools plugin offers a similar plugin notes functionality, among other things. While I liked how Maintainn Tools keeps plugin notes in a separate column rather than below the plugin description like Plugin Notes, I found it limiting that I couldn’t edit or delete the plugin notes that I had created.

Considering the limitations of the available offerings for plugin notes, I decided that there was an opportunity to build something that would better serve my team’s needs, and hopefully be of use to others as well.

How I built it

The basic idea behind the plugin is simple. When a user goes to their WordPress admin dashboard and views their installed plugins, Plugin Notes Plus provides an additional column to the right of the plugin descriptions for plugin notes. The user can add, edit, or delete notes for any installed plugin.

The plugin adds a column on the admin plugins page called “Plugin Notes” and provides functionality to add, edit, or delete notes about a plugin.

In the following sections, I describe the various considerations behind how I built the plugin in more detail.

The foundation: WordPress Plugin Boilerplate

Object-oriented programming (OOP) is a good approach for building plugins since it allows you to keep your code well organized and minimizes the chances that the code will conflict with other plugins or themes. I had learned the concepts behind OOP in introductory programming classes years ago, but I found Know the Code tutorials very helpful in getting me started with OOP PHP.

I decided to use the WordPress Plugin Boilerplate as the foundation for my plugin because it was well organized and helped to ensure that I was following best practices for plugin development. The boilerplate is organized into three main folders: ‘admin’ (for functionality in the admin), ‘public’ (for functionality on the front end), and ‘includes’ (for functionality that is shared between between the admin and front end). Because my plugin only deals with the WordPress admin, I removed the ‘public’ folder as well as any references to its files.


The user interface

Although the plugin seems simple, I spent a fair amount of time thinking through exactly how I wanted it to work. I liked how Maintainn Tools put the plugin notes in their own column and allowed for multiple notes per plugin, but I also wanted to make it possible to edit and delete notes. Thus, I used jQuery to build an interface that enables users to easily add, edit, and delete notes, and I used Ajax to handle database updates so that users can manipulate their notes without requiring a page refresh.

Note icons

I had the idea of including an icon next to each note to quickly convey what type of content it contains (e.g., info, warning, link, etc.). For that, I used Dashicons, which is the default icon font of the WordPress admin. I selected six icons that are available by default, but I also included a filter to enable developers to modify which icons are available.

Hyperlinks in notes

A feature that I liked in the original Plugin Notes plugin was that it automatically converted links to target="_blank". Presumably, if someone is looking at the Plugins page and clicks on a link in a note, the don’t want to actually leave the Plugins page.

Additionally, I converted any urls without <a> tags into links. Below is a snippet that uses a regular expression to identify any urls that aren’t links and converts them to links. I then used jQuery to add target="_blank" to all links.

function convert_urls_to_links( $input ) {
    $url_without_tags_regex = "/<a.*?<\/a>(*SKIP)(*F)|https?:\/\/\S*[^\s`!()\[\]{};:'\".,<>?«»“”‘’]/";
    $replacement_pattern = '<a href="$0">$0</a>';
    return preg_replace( $url_without_tags_regex, $replacement_pattern, $input );

Data storage

We all make mistakes, right? Well, I made a mistake when I decided where to store the plugin notes. I was familiar with the options API, so I decided to use it to store plugin notes in the options table. For each plugin with notes, I set up an array containing each plugin’s notes and note metadata and used add_option(), delete_option(), update_option(), and get_option() to handle adding, deleting, editing, and retrieving plugin notes, respectively.

The problem is that the options table is only meant to hold small amounts of information that don’t get updated often – typically setup and configuration information. Storing large amounts of data in the options table can impact performance. In fact, WP Engine will warn customers and even delete entries in the options table if they are too large.

Granted, I wouldn’t expect anyone to use Plugin Notes Plus to write an epic novel 😉 , but it’s still not a best practice to store plugin note data in the options table, as I learned after releasing the plugin and getting feedback from some early users. Returning to the codex, I reviewed my options for storing plugin notes in the database and decided that it would make the most sense to store plugin notes in their own custom database table.

In my first update (v1.1.0), I set up a new custom database table for plugin notes and added a migration routine to move any existing plugin notes out of the options table and into the new table. It felt a little like doing maintenance on a plane while flying it because I had to ensure that the update didn’t affect anyone’s existing plugin notes. However, the migration was successful, and I’m happy with how the plugin stores its data now.


Because the plugin collects and stores data in the database, security considerations were a must. Below is an overview of the security best practices that I followed while building the plugin. (I used the WordPress Plugin Handbook as a reference.)

Checking user capabilities

Any plugin that allows users to submit data should check that the user has the correct level of permissions. In the case of Plugin Notes Plus, I checked to ensure that the user had the activate-plugins capability before rendering the plugin notes and form.


After checking user capabilities, the next level of security is ensuring that the user actually intends to perform a given action. Nonces are unique, generated numbers that verify the origin and intent of requests.

Because I was using Ajax to update plugin notes, I generated a nonce using wp_create_nonce() and then made it available in the JavaScript file that handles plugin note updates using wp_localize_script(). I then checked that the nonce from the Ajax request matched the original nonce using check_ajax_referer().

Data validation and sanitization

Whenever notes are saved to the database or retrieved for rendering, they need to be validated and sanitized. For that, I wrote a process_plugin_note() function that uses wp_kses(), which filters a string of HTML to ensure that it includes only an allowed set HTML tags and attributes. For rendering translatable strings, I relied on the esc_html__() and esc_html_e() functions.

Safe database operations

Having set up a custom database table for plugin notes in the v1.1.0 release, I needed to ensure that I was following best practices when dealing with the table. I took advantage of WordPress’s built-in helper methods, including insert, update, and get_row. I also used the prepare method where appropriate to ensure that I generated SQL that was safe from SQL injection.

As an example, to retrieve a note with a particular ID, I used $wpdb->prepare() as follows:

 * Get a specific plugin note by id.
 * @since    1.1.0
public function get_plugin_note_by_id( $note_id ) {

    global $wpdb;
    $table_name = $wpdb->prefix . Plugin_Notes_Plus::get_table_name();

    $result = $wpdb->get_row( $wpdb->prepare(
        "SELECT * FROM $table_name WHERE ID = %d;",
    ) );

    $note_array = array();
    $note_array['note'] = $this->process_plugin_note( $result->note_content );
    $note_array['icon'] = $result->note_icon;
    $note_array['user'] = $result->user_name;
    $note_array['time'] = $result->time;

    return $note_array;


There were a number of ways that I could have designed the plugin to work on multisite installations. One approach would be to have the plugin notes synced across all sites. However, I opted to have each site maintain its own plugin notes. I figured that the super admin might want to keep their own private plugin notes, and then each site could add their own notes as it made sense.

One interesting issue that I encountered was that, in a multisite install, the plugin notes column didn’t display for the super admin. I eventually discovered that the manage_plugins_columns and manage_plugins_custom_columns hooks that I was using to create the new column didn’t work for the super admin. To display the plugin notes column on the network admin (super admin) plugins page, I had to additionally use these hooks: manage_plugins-network_columns and manage_plugins-network_custom_column.


I set the language for one of the sites in my local multisite install to Spanish so that I could verify that all user-facing strings could be translated. Following the instructions in this article, I used Poedit to create a Spanish translation of my plugin. I was able to confirm that all of the translated strings displayed correctly in my Spanish install.

In conclusion…

I really enjoyed building Plugin Notes Plus, and I learned a lot about plugin development. Feel free to check out the project on GitHub, or you can find it in the plugin repo if you’d like to write some plugin notes of your own.


WordPress Workflow Series: Part I

Raise your hand if you’ve ever used an FTP client to make a file update directly to a live site. Now keep your hand up if you’ve ever made a file update that broke a live site. If we’re being honest, there are probably a lot of hands up, including mine.

The practice of “cowboy coding” (i.e., editing files on a live server) is how many of us got started. Often, we recognize that it’s not a good practice, but the process of moving to a better workflow can seem daunting. This series is intended to help you understand and implement workflow best practices when developing WordPress sites. Maybe you’ll even decide to retire that cowboy hat. 😉

Understanding the principles behind a solid development workflow

When you start reading about workflow, it’s easy to feel overwhelmed by the various tools and techniques. However, it’s important to remember that a solid workflow is based on a few basic principles. This article is meant to be a high-level overview of the concepts behind workflow best practices.

My goal is to explain how everything fits together so that you can follow best practices in your workflow, regardless of the tools you’re using. In future articles, I’ll talk about the specific tools I use in my own workflow and provide more specific guidance about how to get started.

1. Use a local development environment.

If you do nothing else to improve your workflow, at least consider setting up a local development environment. This means that you’ll have a copy of the website you’re developing running on your own computer.

Working locally means that you’re free to make mistakes and test things out without the fear of breaking a live website. It also makes it easier to edit files since you don’t have to worry about transferring the file up to the host server every time you want to test an update to your code.

2. Set up automated backups of your live site.

While this might not be strictly considered part of your development workflow, it’s absolutely essential to maintain regular backups of your live site. There are many ways to do this, but whatever solution you’re using should meet the following basic requirements:

  • Backups of your entire site (including files and database) should run automatically and reliably at regular intervals.
  • You should be able to set the backup interval and the number of recent backups to be stored based on what makes sense for the particular site you’re maintaining.
  • Backups should be stored somewhere that is NOT your host server.
  • You should be able to restore your site using one of the saved backups. (Test this periodically in your local development environment.)
  • You should be able to easily roll your live site back to a previous backup point.

3. Staying in sync: Code moves up, database moves down

Once you’re comfortable developing locally, you’ll probably start to wonder how you can keep the local copy of your site in sync with the live site. What makes this a little tricky is that WordPress sites consist of both files (including your theme, plugins, and WordPress core files) and a database (which stores your content and any configuration settings).

To follow best practices in modern software development, remember this general principle:

Code moves up, database moves down.

How does this work in practice? Consider the following examples.

Case 1: Updating child theme files (code moves up)

Let’s say that you want to update some files in your site’s child theme. In that case, it’s best to set up a local copy of your site, make and test the code updates, and then “deploy” those updates to the live server. By adhering to the “code moves up” practice, you will ensure that you won’t break the live site by introducing PHP or CSS errors.

Case 2: Updating content and settings (database moves down)

Now let’s say that you want to write a blog post or change a setting in the WordPress dashboard. Those are updates that affect the database, and therefore they should be done directly on the live site. When you want to sync your live and local copies, you should bring down a copy of the live site’s database to your local copy.

What’s important to remember is that your live site’s database is in constant flux. For example, somebody might be writing a comment on your blog post or placing an order in your eCommerce store. Those are both examples of transactions that are stored in the database. So if you were to bring a copy of your local site’s database into the live site (i.e., moving the database up), you would risk losing any new comments, orders, etc., that were added after you set up your local copy.

What about plugins and uploads?

You may be wondering how plugins and uploads fit into the “code moves up, database moves down” scheme. While uploads are technically files, they should be treated as content because uploading a file to the media gallery actually creates a new post in the database. So uploads should be made on the live site and then brought into the local copy.

Plugins are interesting because they are files, but they sometimes have settings that you can configure via the WordPress dashboard after installing them, and those settings are stored in the database. What I would recommend is to always install and test plugins locally first. Once you’ve confirmed that you want to use the plugin, you can push its files upstream from local to live and then apply any configuration settings on the live site.

Similarly for plugin updates, I’d recommend to run those on the local copy to ensure that they don’t break anything and then push the updated files up to the live site.

In a future post, I’ll describe some ways to sync uploads and plugins between your live and local copies.

4. Use version control.

In the previous section, I mentioned making file changes on your local copy and then “deploying” them to your live server. Using a version control system makes the deployment process easy by tracking which files you’ve updated and enabling you to “push” those file updates to the live site.

An effective version control system should do the following:

  • Keep a running history of all file updates, showing specifically which files where changed and what those changes were.
  • Deploy file updates made locally to the live site.
  • Enable you to revert to an earlier version of your code.
  • Enable you to work in parallel with other developers.

In future posts, I’ll go into detail about how to set up and use a version control system with WordPress.

5. Maintain staging installs on the host server.

For the sake of simplicity, I’ve only mentioned working with local and live copies of a site so far. However, it’s a common practice to additionally maintain one or more copies of the site on the host server that are designated as “staging” copies. (These might also be labeled “dev” and/or “test” installs.)

Because they’re hosted on the exact same server as your live site, staging copies can be used as intermediate environments where you can test your updates before moving them live. This is especially important because your local server may not always match the exact configuration of the host server. Staging copies can also be useful to allow clients to preview and approve updates before they are deployed to the live site.

In the diagram below, you can see how the staging copy fits into the development workflow. The general principles still apply, but we’re just adding an intermediate environment. So you would move code from local -> staging and then from staging -> live. And you would copy the database downstream, from live -> staging and from staging -> local.

6. Document your workflow and communicate with other developers.

There are many great tools that will help you streamline and automate your workflow. However, there’s nothing that will replace good old fashioned communication. It’s important to communicate with other developers working on the site to ensure that everyone is on the same page about the development workflow and also to coordinate who’s working on what.

It’s also a good idea to maintain documentation for the sites you build. Especially as workflows become more complicated, it will be helpful to anyone working on the project to understand the specific workflow that you’ve set up. Even if you’re not working with others, the documentation will help you get back into development if you haven’t worked on the project in a while.

How to get started

If you’re a cowboy or cowgirl coder who is curious about improving your workflow but not sure where to start, my advice would be to move into a better workflow one step at a time. In future posts, I’ll go into more detail about how you can start improving your WordPress workflow by implementing the concepts described in this article.

How I Built It

How I built it: A WordPress plugin to calculate user meeting times that uses the WP REST API and React.js

This post is based on a talk that I gave at the East Bay WordPress Meetup on December 10, 2017. The complete code for the plugin can be found on GitHub.

The problem

I work with a team of six that is distributed across the globe. In order to work effectively as a team, we hold regular meetings over video. Shortly after I joined the team, I noticed that there was sometimes confusion over when the meetings would begin. With team members in six different timezones, the confusion was understandable.

Our team lives in six different timezones across the globe. (Image adapted from Free Vectors by

The initial solution

Initially, I wrote a simple Python app on Google App Engine, using the pytz library to calculate user meeting times. The app listed each team member and their timezone. To calculate meeting times, a user would enter the meeting time and date and select a reference timezone. The app would then generate a list of team members and their personalized meeting times, which we could paste into a task in our project management system.

The Google App Engine solution served its purpose, basically eliminating any confusion about when our team meetings would start. However, it was somewhat hard to find and hard to use. For example, if a team member moved to a new timezone, they would have to look up the correct name of their new timezone in this database. If they didn't enter it correctly, it would throw an error. I could have spent time improving the interface, but I was focusing on improving my skills as a WordPress developer, and it worked well enough.

An opportunity to play with shiny, new things

With the introduction of the WP REST API and WordPress's adoption of React.js as the JavaScript framework behind the Gutenberg editor, I was looking for an excuse to build something with these new tools. A meeting timezone calculator seemed like a good first project. Additionally, building it as a WordPress plugin meant that it would be easier for our team to find – because it could be installed on our professional development blog, a site that we are already logged into on a daily basis.

How I built it: The WordPress part

The WordPress part of the plugin is quite simple. First, it creates a custom user meta field (called user_timezone) that can be set via a User Timezone dropdown field when editing a user's profile in the WordPress admin. The timezone options are borrowed from the "Timezone" dropdown menu that appears under Settings > General in a regular WordPress install.

The plugin creates a custom user meta field for the user's timezone.

In order for user timezones to be accessible via the REST API, I had to add them to the users WP API endpoint. During my testing, I discovered that only users who have published content are included in the users WP API endpoint by default. Since I wanted all registered users to be available to my plugin, regardless of whether they had published content, I first added a filter to remove 'has_published_posts' from the WP API user query.

// Includes all users in API result even if they haven't published posts
add_filter( 'rest_user_query', 'red_remove_has_published_posts_from_wp_api_user_query', 10, 2 );
 * Removes `has_published_posts` from the query args so even users who have not
 * published content are returned by the request.
 * @see
 * @param array           $prepared_args Array of arguments for WP_User_Query.
 * @param WP_REST_Request $request       The current request.
 * @return array
function red_remove_has_published_posts_from_wp_api_user_query( $prepared_args, $request ) {
    unset( $prepared_args['has_published_posts'] );
    return $prepared_args;

Then I made the user_timezone user meta value available to rest API by modifying the default response. I found it helpful to use Postman to confirm that I had successfully modified the default WP API users endpoint. The URL where you would find data for the users endpoint is:

// Make user_timezone user meta value available to rest API
add_action( 'rest_api_init', 'create_api_user_meta_field' );
function create_api_user_meta_field() {
    register_rest_field( 'user', 'user_timezone', array(
            'get_callback'    => 'get_user_meta_for_api',
            'schema'          => null,
function get_user_meta_for_api( $object ) {
    //get the id of the user object array
    $user_id = $object['id'];
    //return the user meta
    return get_user_meta( $user_id, 'user_timezone', 1 );
Users API output for Jamie, including user_timezone. Viewed using Postman.

Finally, I created a page under Admin > Tools called "Meeting Timezones" to display the interface where users can calculate meeting times. The PHP part of the plugin simply inserts an empty div with id="mtg-tz-container and then enqueues the React-generated styles and scripts.

A new page created under Tools called "Meeting Timezones."

How I built it: The React part

The React part is where the magic happens, transforming the empty div shown above into this:

The user interface for the meeting timezone calculator, generated by React.

The beauty of a front-end JavaScript library, like React.js, is that it enables you to have an app that responds to user input without having to reload the page. That's a huge advantage for a tool that runs in the WordPress admin dashboard, where page loads are notoriously slow.

React is a modular framework, and each component can handle two types of data: props and state. Props are values that don't change, while the state contains values that can be updated. In my meeting timezone calculator app, there is an outer component called <MtgTzContainer />, and it makes a call to the WP REST API to retrieve the user data, including user timezones. It stores that data in its state and then renders two components: <UserTimezoneList /> and <MeetingTimeForm />. It passes the user data to those components as props.

The <UserTimezoneList /> component renders the top section of the user interface that shows each user's name, avatar, and timezone. I also included a checkbox next to each user to indicate whether that individual would be included in the final meeting times list.

The <MeetingTimeForm /> component provides a datepicker and timezone dropdown menu. The datepicker is a component borrowed from an external library called react-datepicker, while the timezone dropdown menu is simply a select element whose options are a list of the unique timezones represented by the list of users. This component is where a lot of the work happens. It relies on the libraries moment.js and moment-timezone.js to calculate meeting times in various timezones. Finally, it passes the results to the <UserMeetingTimes /> component, which displays the output.

While building the React app and WordPress plugin, I ran into a few interesting issues:

  • The names of the .js files that React generates are modified with cache-breaking hashes. For example, main.js might become main.a8429063.js. Since WordPress requires you to enqueue scripts by name, and since that name will change every time the file is generated, I used the strategy explained here to look for and enqueue any file in the targeted folder that ends in .js.
  • I found that I needed to enqueue the script generated by React late so that the empty div that React looks for exists when the script is loaded.
  • While working on the React app locally (and before integrating it into the WordPress plugin), I needed to provide it some test user data from a WordPress API. I accomplished that by using a hardcoded URL to my personal site, where I had set up some dummy user data. However, when the app is integrated into a WordPress plugin, I want it to look for the users WP API endpoint for the site on which it's installed. To account for both situations, my script first looks at the current URL to see whether it contains /wp-admin. If so, it loads data from the current site's WP API. Otherwise, it catches the error and loads data from the dummy API. 

And you can, too!

This was my first foray into React.js. I got started with some free tutorials on YouTube. Fortunately, there are so many learning resources for React these days that you can easily find one that fits your learning style. I was also lucky to have create-react-app at my disposal, which made it very easy to get up and running with a React app on my local machine.

Figuring out how to write a WordPress plugin that uses React was a bit of a challenge. However, I found this post that walks you through a very simple way to get a WordPress plugin up and running with React. Instead of using a package manager and doing things the "right" way, it shows you how to load React and its dependencies from external URLs.

The author of that post acknowledges that it's not the "correct" way to do it. However, that simple approach helped me get started and, from there, I was able to figure out how to build my React app in the proper way, using node package manager and generating the final script that I can then package with the plugin. A future improvement would be to write a script that packages the React-generated scripts with the final WordPress plugin automatically. Additionally, I'd like to learn more about Redux, which is a tool for organizing state within an app.

The completed project can be viewed on GitHub.


What I learned at WordCamp Phoenix

WordCamp Phoenix 2018, which took place last weekend, was my second WordCamp. It was absolutely worth the flight to Phoenix, as the event was well organized and fun. I had a great time hanging out with some of the Red Earth Design, Inc. team members, meeting people in the WordPress community, and attending talks. Here are a few of my takeaways from the event.

Three of the RED team members at WCPHX 2018

WordPress is at the cutting edge

Since WordPress is an open source project, it can rapidly interface with new technologies. I had been hearing about how great GraphQL was as an alternative to REST APIs, so I was excited to hear Jason Bahl’s talk about WPGraphQL. I was encouraged to see that there is already a WordPress plugin that provides a GraphQL API for WordPress. I really enjoyed Jason's talk because he spent a lot of the time demonstrating the basic usage of the plugin and how it can be extended.

GraphQL logo

Having just released Plugin Notes Plus in the WordPress plugin repo, I'm interested in having an API available that provides data about the plugins installed on a site, including any plugin notes. I learned that the WPGraphQL plugin already provides a way to query plugins, so I could simply extend that endpoint to include plugin notes. During my free time at the WordCamp, I worked out the basics for how to accomplish that, so hopefully I can work that into a future release of Plugin Notes Plus.

Don't set too many goals

I attended Nathan Ingram’s workshop called "Taming the Whirlwind," which was about strategies to help a business succeed. Although I don’t run a business, I found some of the advice about setting goals interesting and useful.

One slide in particular stood out to me, which was data collected by the Franklin-Covey Institute about the number of goals a team set vs. achieved. According to the study, the more goals you have, the fewer you're likely to achieve. I'm sure there are nuances to this finding, but I have found it to be true in my experience that, the more goals or items on my to-do list, the less I'm able to focus on any one item.

Number of Goals 2-3 4-10 11-20
Goals Achieved 2-3 1-2 0

Working with a team is like improv comedy

Amber Pechin gave a funny and entertaining talk about how teams can be more creative and successful by following the basic rules of improv comedy. For example, follow the "Yes, and …" rule when brainstorming with a team. And think of ways to set up your fellow team members to succeed. It wasn't anything I hadn't heard before, but it was motivating. Ideally, a team that works well together can produce something better than any individual member could have, and everybody can enjoy the team's successes, no matter what individual role they played.

My efforts at improving my workflow are paying off

I've spent a lot of time in the past couple of years refining my WordPress development workflow. Carl Alexander gave a well structured talk about the concepts behind automated WordPress deployments. I liked how most of this talk kept to the high-level concepts behind workflow, but he stopped at various intervals to give specific recommendations for tools and services. His talk helped solidify the way that I think about workflow best practices and also validated that I'm finally doing it the right way.

I'm too old

Possibly the most important takeaway from the event was having an elderly woman tell me that I’m too old. Here's what happened: I was sitting and waiting for the talk on WPGraphQL to begin when an elderly women wearing a sequined outfit entered the room and sat next to me. While WordCamps attract a refreshing diversity of demographics in their attendees, I remember being slightly surprised to see someone like her at a talk about WPGraphQL.

The woman turned to me and said, “I’m trying to sit next to the youngest person in the room. How old are you, young lady?” I'm pretty sure she expected me to tell her that I was 25 or so. However, I decided to tell her the truth. “I’m 36,” I said. Her reply was priceless: “Oh dear, you’re too old.” Shortly after, she got up and left. I'm still not quite sure what to make of that encounter.

Year In Review

2017: Year in Review

As the year comes to an end, I tend to reflect on how the year went and to consider my aspirations for the coming year. Especially since having Sylvie, I’ve wanted to be better about recording my memories. Having started this blog recently, I figured it would be the perfect place for a “year in review” post.

Balancing parenting and career

Sylvie was born in March of 2016, and I spent the first few months at home with her, fumbling around in a sleep-deprived state and figuring out all of the things you’re supposed to do for a newborn. After five months, I realized that I really needed to get my head back into my other work.

We set Sylvie up with a nanny in the mornings so that I could have some structured time to work. It was absolutely the right decision for us, and I was happy and relieved to get back to more adult-focused activities. However, my productivity was subject to the vagaries of Sylvie’s sleep schedule.

In 2017, Sylvie started to sleep through the night more consistently, and I started to feel my career ambition return. We extended our nanny’s hours slightly, and I started taking on more work, including some new site build projects. 

It’s a continuous struggle for me to balance parenting with web development work, but I’m grateful for the flexibility that my job allows so that I can spend time with Sylvie every day while also keeping up with web work. And I’m totally guilty of mixing the two, as I often listen to work-related podcasts and audiobooks while spending time with Sylvie in the afternoons.

A momentous meetup

In August, Red Earth Design, Inc., the company that I work for, held its first in-person retreat. It was a landmark occasion for us since most of us had never met in person before. You can read my post about the retreat here. It was also the first time that I was away from Sylvie for longer than a few hours. I missed her, of course, but it was refreshing for me to step away from my parenting role for a few days.

The RED team enjoys a productive work session during our retreat.

My first GitHub project

As a web developer, it’s important to stay on top of the latest technologies and to continually learn and grow. One of my goals was to improve my JavaScript skills and learn React, a popular JavaScript framework.

I decided that I would put these skills into practice by building a WordPress plugin that calculates meeting times for users in various timezones. (I work with a team from across the entire world, so this was something that would be useful to us.) The problem was that I was having trouble finding time for my side projects.

Listening to a podcast around that time, I heard an interesting piece of advice:

“If it’s worth doing, it’s worth doing badly.”

As a perfectionist who often starts projects with way too much ambition, I took the advice to heart. The idea isn’t to try to do something badly, but rather to set aside unreasonable expectations and focus on just getting it done. I set a deadline for myself by which I wanted to complete a minimum viable product. The deadline helped me focus on what was most essential to get the plugin working, and the result was a functioning plugin and my first real GitHub project.

The user interface of my meeting time calculator tool. All of the headshots are stock photography models except for one. 😉

An exercise in empathy

One of my interests in web development is building accessible websites. Among the groups of people who should be considered when building accessible sites are screen reader users. A screen reader is software, typically used by visually impaired individuals, that reads aloud the content on the screen. 

One of the problems that web developers face when trying to build accessible sites is that many have never used a screen reader, and there’s a bit of a learning curve to become comfortable using them. With that in mind, I thought it would be a fun and useful project to force myself to use a screen reader for a week.

Since learning to use a screen reader requires a focused effort, I scheduled the project for the week that we were visiting Bill’s parents at Lake Michigan in September. I can’t say that I was totally successful in using a screen reader exclusively for the entire week, but I did make an honest effort to learn to use VoiceOver, the screen reader that is built into Macs.

Importantly, I reached a level of comfort using a screen reader that I feel capable of performing a cursory test of a site to see whether it’s generally accessible to screen reader users. I’m hoping to improve my screen reader skills in the coming year and possibly even build an accessible WordPress theme.

Getting involved and giving back

This year, I pushed myself to become involved with my local web development and WordPress communities. This had been a goal of mine for some time, but I found it difficult to fit meetups into my already busy schedule. 

I found a meetup group in Oakland that meets once a month on Sundays and started attending in August. I was particularly excited to attend their WordPress contributor day, which is an event geared towards helping people get started contributing to the WordPress project. You can read more about my experience at the contributor day here.

Our “WordPress Contributor Day” selfie

A related goal I had was to give a presentation at a meetup. It turns out that the East Bay WordPress meetup group holds a “Show Off Your WordPress Site” session at the end of each year, where participants are encouraged to share a project they worked on that year.

I was initially reluctant to volunteer since I was new to the group. However, with some encouragement from Bill, I decided to take the plunge. I presented my timezone calculator plugin (described above). My presentation was a little rough, but I’m glad I did it, and I’m eager to find new opportunities to speak in the coming year.


An unexpected development of 2017 was my effort to simplify. As mentioned above, I’m an avid podcast and audiobook listener, and I came across a book called “The Life-Changing Magic of Tidying Up” by Marie Kondo. The basic idea behind Kondo’s organizational strategy is that you should go through all of your possessions by category and keep only those items that “spark joy.” 

While I was initially a little turned off by her intensity and all-or-nothing approach, I found myself inspired to purge my closets of things that I realistically was never going to use again. Bill willingly participated as well, which was a pleasant surprise.

I’m not sure that tidying has changed my life. (Granted, I didn’t follow her prescribed methods that closely.) But I do feel slightly lighter and happier. There’s something about holding onto things out of a feeling of guilt or obligation that kind of drags me down.

P.S. It is an unavoidable fact that nature abhors a vacuum, and Sylvie’s stuff rapidly filled the void that was left by Bill’s and my purging.

Keeping a new years resolution that wasn’t my own

Bill decided this year to reduce his consumption of refined sugar. Part of that was that we stopped buying ice cream. I was surprised at how easy it was to cut back on my consumption of sweets by simply not having them in the house.

This seems to relate to the theory that willpower is a limited resource, and the best way to conserve that resource is to avoid temptation when possible. I was by no means strict about avoiding sweets, but the simple change of not keeping ice cream and sweets in the house seemed to have a positive effect on my diet.

Visiting grandparents

Sylvie is the only grandchild on both sides, so Sylvie visits were a hot commodity among her grandparents. In August, Sylvie and I flew to Oregon to visit my parents and to participate in the yearly ritual of consuming copious amounts of blueberries. In the photos below, Sylvie demonstrates her 3-step blueberry eating technique. She found it easier to pick blueberries out of the bucket than to find them on the bushes.

In September, our whole family (minus Calico) flew to Chicago to see Sylvie’s other grandparents and to spend some time with them at Lake Michigan. She had a great time walking along the beach and playing in the sand.

An unexpected road trip

One of my fondest memories from 2017 was an impromptu trip up to Oregon in October. With fires raging to the north of us, the smoke became very noticeable in our neighborhood. We spent a miserable evening trying to entertain Sylvie and Calico while minimizing our exposure to the smoke. The next morning, we packed the whole family in the car and headed to my parents’ house in Oregon. 

Bill and I felt a little silly taking such a drastic measure, but we were also relieved to get out of the unhealthy air. Sylvie and Calico did much better than we expected on such a long car ride. We spent a few days in Oregon with my parents, and it was a really fun time – despite both Bill and Sylvie catching a cold. We took Sylvie to a pumpkin patch and a carousel, and my mom and I took her shopping at the factory outlets in Woodburn. It was her first shopping experience, and I think she enjoyed it a little too much. 🙂

It was also the first time that our whole family – including Calico – took a big trip together. We were encouraged by how well it went, and we felt emboldened by the success of this trip to plan some family road trips in the coming year.

A family photo in my parents’ backyard

Looking ahead to 2018

So what’s in store for 2018? Sylvie will turn two in March, and I’m looking forward to the new experiences she’ll have as she becomes more aware of the world around her. As I mentioned above, our family is eager to start taking some road trips. Who knows? Maybe she’ll see snow for the first time in 2018.

As Sylvie continues to gain independence and move into a more consistent sleep schedule, I’m feeling ready to lean into my career more this year. I’m currently working on a “plugin notes” plugin, which will be my first submission to the WordPress plugin repo. Additionally, there are a number of other projects in the queue that I hope will see the light of day in 2018. I’m also excited to see how Red Earth Design, Inc. evolves and grows in the coming year.


Reflections from my remote team’s first in-person retreat

I’ve worked as a developer for Red Earth Design, Inc. since October of 2014 and, until recently, I had met only one of my fellow team members in person. Even though we have regular team meetings over video, and we chat on Slack, we were starting to feel like we could really benefit from an in-person retreat. Early in the summer, we decided that it was time.

Getting six remote team members together – who live in four different states and three different countries – was no small feat. It took a lot of planning and preparation, but on the day of the solar eclipse, the moon, the sun, and the Earth aligned, and our company converged on Chicago for the very first time. We spent two full days together, focusing on work activities in the morning and recreational or volunteer activities in the afternoon. You can read the official RED blog post about the retreat here.  Below are my personal reflections from the event.


Meeting everyone and hanging out together. It was fun to meet everyone after only interacting via computer or phone for almost three years. Our regular team video meetings gave me a good sense of what everybody was like, but there’s a certain je ne sais quoi that comes from actually spending time together in person. I think all of our activities – from exploring Millenium Park to eating deep-dish pizza to playing Pictionary – really helped us bond as a team and connect personally.

Working together. We tried to be strategic in planning our work activities during the retreat, focusing on things that would be a lot easier to accomplish in person than over a computer. One of our main goals was to get everyone up to speed on an improved workflow. I think it was a good choice of activities, as it was the type of thing that requires frequently looking over each other’s shoulders and troubleshooting. We also had a number of business-related discussions, which felt a bit more comfortable in person.

Staying together in a big house. In the early stages of planning the retreat, we considered the option of staying in a hotel, but it seemed too impersonal for a team retreat with only six people. So we followed the lead of other small remote teams and rented a big house through Airbnb. And I’m really glad that we did. The house was in Oak Park, a suburb of Chicago, and it was absolutely perfect. It was large, well equipped, and just a little bit quirky – with a full-sized mannequin in my bedroom. I especially liked the large dining table that we could all sit around while we worked. Oh, and the giant trampoline in the back yard was a nice perk.

Volunteering. Our company serves a number of nonprofits, and one of our core values is “making a difference.” So it made sense for us to spend some time volunteering as a team. I wasn’t sure how easy it would be to arrange a volunteer activity because many organizations require an initial orientation session. Because we were all coming from out of town, and we only had an afternoon available to volunteer, we needed something where we could hit the ground running.

Luckily, I contacted Ricardo at the Oak Park River Forest Food Pantry, and he was more than willing to accommodate us. He signed us up for a Wednesday afternoon food distribution shift, where we helped distribute food and carry out groceries for what seemed like an endless stream of food pantry clients. It was hard work, but I found it to be an extremely rewarding experience. And it was fun to see our team, for whom work is typically done sitting in front of a computer, perform the hard physical labor of schlepping heavy bags of groceries out to people’s cars.

Taking updated headshots. An underappreciated aspect of working remotely is that we’re constantly looking at team members’ headshots – whether in Slack or in Intervals, our project management system. Those headshots are often several years old, taken under poor lighting, and/or cropped out of a vacation or wedding photo. We decided that the retreat would be a perfect time to update our headshots.

Instead of hiring a professional photographer, I decided to borrow my dad’s fancy telephoto lens and pretend to be a photographer for a day. I took my photographer role very seriously, watching several YouTube videos on how to take a good headshot and reading several articles. This slideshow in particular gave some very helpful pointers. We took a lot of photos – both in our company t-shirts and in nicer shirts we’d brought, and we ended up with some decent headshots. Since the retreat, it’s been nice to see everyone’s updated headshot in Slack and Intervals.


The retreat was exhausting since we tried to pack so much into two days, but I returned home energized, excited about our team, and optimistic about the direction our company is headed. I like the freedom and flexibility of working remotely, but I think that periodic in-person retreats can be hugely beneficial for morale and productivity.


How a WordPress contributor day changed my mind about the Gutenberg editor

I’ve been trying to become more involved with the WordPress community lately, so I was excited to see that the East Bay WordPress Meetup group was hosting a contributor day, where volunteers can come and learn how to contribute to the WordPress project.

Before the contributor day, I was well aware of Gutenberg, a block-based content editor slated to be merged into WordPress core in its 5.0 release, and had done a little testing with the plugin. However, I had read some critical blog posts about the new editor, so I wasn’t that enthusiastic about getting involved.

At the contributor day, it became clear pretty quickly that Gutenberg was the thing to work on. The meetup group set up a test site, and everyone was setting up accounts on the site and playing with the new editor. I grudgingly went along and quickly started to find bugs. I worked with one of the meetup organizers on filing our first GitHub issue (related to the inability to add or edit tags as an Author). Then I continued testing and submitted a few more of my own issues. By the end of the contributor day, I had submitted 3 issues on the Gutenberg GitHub repo, and our meetup group had submitted about a dozen in total.

When I got home, I noticed that someone had already confirmed one of the bugs I had reported. By the next morning, another issue I had submitted already had a pull request. I went to the contributor day with the goal of contributing to WordPress, and I ended up feeling like I had actually made a contribution, however small. And, surprisingly, my attitude toward Gutenberg went from apathetic to sympathetic. Sure, Gutenberg is a controversial project among the WordPress community, and I’m not sure what I’ll think of it once it’s merged into WP core. But I do appreciate that it’s an active project, and the more people who get involved in its development – even if you just submit a GitHub issue or two – the better it will be.