When it comes to my online lifestyle, I’m quite stodgy and old-fashioned. I like having a domain that is the canonical digital representation of me. I like running software on my own server, so that I’m in control of my own content. I prefer writing here, and having the words copied to Twitter rather than the other way around. Over the years I’ve been guilty of tweaking the URL structure here on sunpig.com (cool URIs don’t change), but I have tried to keep redirects in place for old content. The last change was four years ago when I moved from Movable Type to WordPress and settled on the sunpig.com/martin/year/month/day/slug/ format, dropping the “.html” suffix. I’m quite particular about my URLs.

And I really like WordPress! But compared to the ease of putting up a Tweet, it has always felt overcomplicated. Now that I have a lovely new (actually, not that new any more, but it still feels new) iPhone X that is capable of great feats of computing, I’ve been wanting to post to my blog more often, and more casually. And while there is a WordPress app for iOS, the image upload workflow had me stymied for a long time.

See, I don’t use WordPress’s built-in image uploading capabilities, because it puts uploaded images in the wrong place. By default, WordPress puts them under /.../wordpress/wp-content/uploads/sites/$SITE_ID/$YEAR/$MONTH/. I don’t like storing CMS content in the directory structure of the application itself. It feels like I’m tying myself to the application, and I hate that. (I also still write my HTML by hand, rather than using WordPress’s visual editor. Like I said, stodgy and old-fashioned. It’s also a position of privilege, and I’m aware of that.) Uploaded images should be served from https://sunpig.com/martin/images/$YEAR/, and I want them stored on disk in a location that maps closely to that same structure. So until today, my image workflow had been:

  • Take picture.
  • Manipulate picture, usually in Acorn.
  • Save suitably compressed picture to a folder on disk that has an OSX Automator folder action on it that does an rsync upload of any new files to the matching folder on my server. (Or, if I’m not on my home computer, use Transmit to do the upload manually.) Because I’ve got nginx serving images direct from disk, that image is now immediately available over https.
  • In WordPress, use the “Add Media” option to “insert from URL”, manually typing out the URL where I know the image lives.
  • Save the blog entry.

If this is tedious on a desktop machine, it’s ten times worse on a phone. On iOS there are plenty of really nice image editors; fewer options for scaling and optimizing images for the web; and only a few apps I’d feel happy entrusting with my ssh keys for connecting to remote servers. With Panic Software sunsetting Transmit for iOS I didn’t see the workflow getting any better. I briefly contemplated writing my own app that would do all of this in a single gulp, but…I’ve got better things to do with my time.

Today I was messing around with micro.blog, whose concept I love, and which I backed on Kickstarter. I installed the iOS app because it supports posting directly to a WordPress blog. But then I ran into the upload problem again: if I upload an image from any third-party application, WordPress would just put it in its own stupid location, and I wouldn’t be any better off.


But I dug a bit deeper and found that there’s actually a pretty simple answer to this. One of the great things about WordPress is how madly customizable it is. Pretty much every function in the application can be tweaked or completely overridden through the use of filter hooks, and this includes the location of file uploads. In the end, I created a child theme to hold my customizations, so that they won’t get overwritten when the parent theme is updated. In the child theme, I add a filter to the built-in upload_dir function that will replace the default upload locations with my preferred locations:

// Customize the uploads directory:
// By default, WP uploads to $WORDPRESS_DIR/wp-content/uploads/sites/$SITE_ID/$YEAR/$MONTH/
// I want to upload to $BLOG_ROOT/images/$YEAR/
// The $uploads parameter contains details of the uploads directory and the
// path on which uploaded files will be served. Default:
// {
//     "path":"/var/www/sunpig.com/wordpress/wp-content/uploads/sites/2/2018/05",
//     "url":"https://sunpig.com/martin/wp-content/uploads/sites/2/2018/05",
//     "subdir":"/2018/05",
//     "basedir":"/var/www/sunpig.com/wordpress/wp-content/uploads/sites/2",
//     "baseurl":"https://sunpig.com/martin/wp-content/uploads/sites/2",
//     "error":false
// }
function sunpig_martin_upload_dir_filter( $uploads ) {
    $year = date("Y");
    $uploads['basedir'] = "/var/www/sunpig.com/martin/images";
    $uploads['baseurl'] = "https://sunpig.com/martin/images";
    $uploads['path'] = "/var/www/sunpig.com/martin/images/$year";
    $uploads['url'] = "https://sunpig.com/martin/images/$year";
    $uploads['subdir'] = "/$year";

    return $uploads;

add_filter( 'upload_dir', 'sunpig_martin_upload_dir_filter' );

And boom ? WordPress now puts my uploaded images exactly where I want. I could have done this more elegantly, with less hard-coding, but LOL no. This is perfectly fine.

UPDATE 5 June 2018: Putting this functionality in a child theme is the wrong place, because it means that I have to make a new child theme whenever I want to change the theme of my blog. The right place for this code to go is in a functionality plugin that can operate no matter what theme is active.

The WordPress app and the Micro.blog app now sit on the home screen of my phone. I like the “messing around in photo apps” part of my blogging workflow – that bit was never the problem. Now that I’ve fixed the image uploading step, I hope that this means I’ll post more often, and in smaller chunks. We’ll see!