A good rule-of-thumb is that anything a user would expect to keep after changing themes belongs in a plugin. This includes custom post types, taxonomies, metaboxes, and shortcodes.
For specific functionality that’s reusable and/or the client might want to selectively disable, it’s best to build as its own plugin. Many times I’ll turn these into public plugins.
For everything else that’s project-specific but should be theme-independent, I put in a plugin called Core Functionality. That’s the base plugin I now use. Of course you’ll want to delete everything that’s not relevant – I have the code for post types, taxonomies, and metaboxes in there because they are used most often.
My base child theme used to have all these in the theme. Over the past few months I’ve been moving them to a core functionality plugin on each project, but it’s about time I finally update the base child theme and pull the functionality out into its own plugin.
For added safety, I use a “must use” plugin rather than a standard plugin. Must Use plugins (mu-plugins), are plugins that cannot be disabled. I like to put my core functionality in these so that a client can’t accidentally disable it and break their site. Must Use plugins go in /wp-content/mu-plugins (you’ll need to create that directory if it doesn’t already exist).
Update: I no longer use must use plugins for core functionality. I just create them as normal plugins now. Here’s why:
- Managed hosts like WPEngine and Synthesis use the mu-plugins directory for their host-specific plugins. This makes migration a pain since you need to merge your mu-plugins with their mu-plugins.
- Likewise, I now use mu-plugins for code/plugins I only want in development, not in production.I have Registered Users Only in mu-plugins along with some code specific to my dev environment.
- Clients and other developers don’t know much about mu-plugins. I’ve had many past clients contact me saying they hired someone but he couldn’t find where we registered post types.