Skip to content

Lando and WordPress Plugin Development

Dear Reader,Lando logo

So I’m working on my next book “Extending the WordPress REST API”. It’s the companion volume to “Using the WordPress REST API”. In this book, I actually have a working WordPress REST API controller and series of endpoints that I am showcasing. This means I’ve got development to do.

One of the hardest things I do in software development is WordPress plugin development. It takes so much to get things stood up and ready that it’s a pain just to start a project. My last few, I would spin up a site on an unused domain I own – don’t look at me funny…you’ve got them too – and then use VS Code to edit the files remotely. Not exactly the best solution but it’s a great excuse for not working when your not connected. (Scuba boat, airplane in times past, etc.)

Now though, that excuse has been taken away from me thanks to Lando. :)

Installing Lando

Installing Lando is not difficult. One of the great things about Lando it’s copious documentation. Anyone familiar with the basics of software development should be able to install Lando in no time regardless of their operating system. Thanks to Microsoft’s new WSL2 and some huge improvements in the file system, Lando now works better under WSL2 than it does on macOS. (I’ve done both)

I wrote up my thoughts on how to get Lando installed, up and running on WSL2 in this post, Making Lando work inside WSL2.

Developing a Plugin

Once you have Lando installed, the next thing we need is a plugin. You can use any plugin you are wanting to do development on. I’m using my wp_podcast_api plugin that I wrote for Voices of the ElePHPant, and that is the sample code for my book.

Once you are in your plugin’s directory, you have to create a .lando.yml file. Lando makes this part easy. Straight from the Lando Docs,

lando init \
--source remote \
--remote-url \
--recipe wordpress \
--webroot wordpress \
--name my-first-wordpress-app

Of course you want something more clever than “my-first-wordpress-app” but the rest is exactly what you need. This will create you a basic .lando.yml file. This also downloads and uncompresses the latest version of WordPress. This is necessary for when we fire things up.

Now we start tinkering. You can find the finished product here, in case you are too impatient to read this.

The Basics

The basic .lando.yml that the init process created for us is just that, very basic.

name: my-first-wordpress-app
  recipe: wordpress
  webroot: wordpress

That’s it. It just defines our webroot. By the way, you CAN change that if you want but there’s very little value to be gained unless you’ve got something hard-coded to public.


For our working environment, we want a little more than just the basics. We want ti decked out exactly the way we like to develop, otherwise, whats the point in using Lando to begin with? So I’m going to walk through each part of the finished .lando.yml file explaining why the lines exist and why I set them this way.

  via: apache
  php: 7.4
  webroot: wordpress
  ssl: true
  xdebug: false
  database: mariadb

I am now and have always been an Apache fan. So I build with Apache. If you like nginx, that’s cool you can specify that. I’m developing using PHP 7.4 like every good PHP developer should. Also, I like MariaDB over MySQL. I don’t currently use xDebug on this project but I can easily turn it on and then lando rebuild to use it. Of course, everyone should use ssl. It’s not that important for development so it’s fine to leave that off if you like.

Services – Database

Services one gets a bit long so I will break it up into smaller chunks.

  portforward: true

I want to be able to access my database from outside of the Lando container, so I tell it to port forward. Here’s the thing about Lando and port forwarding you can turn it on or off, but it’s best if you let Lando decide the port to forward. Let me quote the Lando manual for you.

portforward will allow you to access this service externally by assigning a port directly on your host’s localhost. Note that portforward can be set to either true or a specific port but we highly recommend you set it to true unless you have pretty good knowledge of how port assignment works or you have a very compelling reason for needing a locked down port.

portforward: true will prevent inevitable port collisions and provide greater reliability and stability across Lando apps. That said, one downside of portforward: true is that Docker will assign a different port every time you restart your application. You can read more about accessing services externally over here.

I mean seriously, I could expound on it, but why. They said it best.

Services – Appserver (Apache)

This is the big section so let’s break it up into even smaller chunks.

The first is the overrides section. This is where the magic happens for WordPress plugin developers happens. That volume mapping below where we map . to /app/wordpress/wp-content/plugins/wp_podcast_api, that is the secret sauce. See, the .lando.ymlgoes in the root of your plugin directory. This way everything is kept together.

You cd in to your plugin’s working directory (on my laptop I have a directory named Projectsand all of my projects, including all of my plugins, have their own dir there.) and then you lando start. Everything stays together, everything in a single git repo, all nice and tidy. That mapping makes this possible.

      - '.:/app/wordpress/wp-content/plugins/wp_podcast_api'

Now when lando starts up, you get a directory in /app/wordpress/wp-content/plugins/wp_podcast_apithat is your plugin, just like you need it. Since it’s a mapping, you can still edit the files in the root of your project and your webserver sees those changes.

Literally, after lando startmy next command is code . and I have my editor up and running ready to start building some awesome.

If this were a simple plugin, that would be all I would need. However, this particular example depends on other plugins. I don’t want to have to re-install them every time I lando destroy -y && lando start so let’s use a combination of composer and wp-cli to put them in place and configure them. Of course we don’t do this manually, let let Lando deal with it. :)

Services – Appserver Build and Run

Lando’s .lando.yml has 4 sections under each service where you can define things that are to be executed inside the container.

  • build_as_root
  • build
  • run_as_root
  • run

The *_as root should be obvious from the names, in the case of this sample plugin, we don’t use them, just build and root. These execute every time you lando start from scratch, you lando rebuild, or you lando destroy -y and then lando start. If you just lando stop and then lando start again (or lando restart) these steps do not execute.

Build executes before the services have been started. If you need to download stuff, tinker w/config files, etc, build is your friend. (same for build_as_root)

  - wp core download --force --skip-content

Now I know what you are thinking…WE JUST DOWNLOAD CORE! Yes, we did, for the init. However, if you want to start totally from scratch. (commit this to a repo, delete the dir, clone the rep anew and rebuild it with Lando) then you need this command.


run executes after the services have started. If you needs the services up and running to do a task, you want those tasks in run. In our case, we do several tasks here in run.

  - composer install
  - wp config create --dbname=wordpress --dbuser=wordpress --dbpass=wordpress --dbhost=database --skip-check --force
  - wp core install --title=Podcast --admin_user=admin --admin_password=admin
  - cp assets/.htaccess wordpress
  - perl -p -i -e "s/\/\* That's all, stop editing! Happy publishing. \*\//\ndefine('JWT_AUTH_SECRET_KEY', 'bite-me');\n\/\* That's all, stop editing! Happy publishing. \*\//" wordpress/wp-config.php
  - wp theme install twentytwenty --activate
  - wp post delete 1
  - wp rewrite structure '/%postname%/' --hard
  - wp plugin install classic-editor --activate
  - wp plugin install jwt-auth --activate
  - wp plugin install wordpress-importer --activate
  - wp plugin install wordpress-seo --activate
  - wp plugin install powerpress --activate
  - wp plugin activate wp_podcast_api
  - wp import assets/voicesoftheelephpant.wordpress.2020-07-23.000.xml --authors=create
  - wp user update calevans --rich_editing=false
  - wp user update admin --rich_editing=false

Everything we do falls into two buckets.

  1. composer tasks (exactly 1 of these)
  2. wp-cli tasks

Lando was built for devs. It knows we love our tools. So when you told it you wanted to start with a WordPress recipe, it figured out that you need composer, wp-cli and a few other must-have tools ready to go. So by the time you get to run, they are there and waiting on you.

In out run section we make use of both composer and wp-cli these tools. This plugin requires a couple of other plugins to be installed. We use composer and wpackagist to get those installed properly. After they are installed, we use wp-cli to activate them as well the TwentyTwenty theme.

I’ve also got a few config options in there and yes, I install classic editor and turn off rich-text editing, I’m old skool.

Side note, while I use composer because it’s the tool I am comfortable with, I could have just as easily used wp-cli to download, install, and activate the plugins I a using because they all come from the main WordPress plugin repository. If you are using custom plugins from a private, composer is your best route. If you know how to use both then you can figure out the best tool for the job each time.

Services – mailhog

Finally, one of my favorite things about Lando, I tell it to setup mailhog. This is a real simple email testing service. When you spin up Lando with mailhog it gives you a URL to go to that shows you any email that was sent to anyone. The mails are not sent out over the net but just captured for you to examine.

  type: mailhog
  portforward: false
    - appserver

Since WordPress can send a lot of emails and we want to make sure they are correct, mailhog is a great way to monitor them.

Bring it all together

Ok, if you’ve followed along so far and you’ve tweaked your .lando.yml file, you can now spin it up.

$ lando start

That’s all it takes.

If you look at the final lando.yml file, you’ll see that I use the wp importer tool to import a set of posts. You can also use lando db-importto populate your database from a .sql file. If you have awesome friends like Kim Cottrell then you’ve probably got a good companion tool like lando db-download that exports and downloads your production database for you.  Or you can do like I did for this one and just use XML. It doesn’t suck that bad. :)

When you are done, you can stop it with a simple

$ lando stop

Don’t destroy it with lando destroy -y unless you aren’t going to be back for a while. stopping and restarting is a lot faster and preserves you database.

As you can see from my sample plugin’s repo, I keep all of this, .lando.yml, composer.json, and the plugin code, in the same repo. This way, no mater what machine I am on, if Lando is installed, I can git pull and then lando start to be up and running with a full WordPress development site faster than light-speed. (Sorry, couldn’t resit) :)


Until next time,
I <3 |<

4 thoughts on “Lando and WordPress Plugin Development

  1. Thanks for your post. Do you know ddev?
    We use it for all our Magento projects. It supports also WordPress out of the box and can manage multiple projects.
    Would be interesting where the main differences between both tools are.

  2. Hello,
    I am an happy Lando’s user since ~2 years, thank you for your post.

    Were you able to configure a Lando NGINX recipe for a WordPress Multisite installation (with subfolder schema, not subdomains)?
    If yes, may you share your code or leave me a breadcrumb? I am not able to accomplish this task.

    My best

  3. Hi David!

    No, I do not use Nginx so I never tried that configuration. That having been said, if you are having issues, ask in the Lando slack channel. They are very helpful in there.

    Cheers! :)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.