Wordpress - Objective Best Practices for Plugin Development?

Use Actions and Filters

If you think people would like to add or alter some data: provide apply_filters() before returning.

P.S. One thing I find a bit disappointing and that your question addresses is the percentage of plugins that are designed only for end-users, i.e. that have no hooks of their own. Imagine if WordPress were designed like most plugins? It would be inflexible and a very niche solution.

Maybe things would be different if WordPress were to have the ability to auto-install plugins on which other plugins depended? As it is I typically have to write a lot of the functionality I need from scratch because clients want things a certain way and the available plugins, while 90% there, don't allow me the flexibility to update the remaining 10%.

I really do wish those leading the WordPress community would identify a way to ensure that plugins are rewarded for following best practices (such as adding in hooks for other developers) much like good answers are rewarded on a StackExchange site.

Let's take an example from another question:

Example: I want to do something in my plugin when someone retweets an article. If there was a custom hook in whatever the popular retweet plugin is that I could hook in to and fire off of, that would be great. There isn't, so I can modify their plugin to include it, but that only works for my copy, and I don't want to try to redistribute that.

Related

  • Offer Extensible Forms

Load Scripts/CSS with wp_enqueue_script and wp_enqueue_style

Plugins should not load / attempt to load duplicate versions of JS / CSS files, especially jQuery and other JS files included in WP Core.

Plugins should always use wp_enqueue_script and wp_enqueue_style when linking JS and CSS files and never directly via <script> tags.

Related

  • Re-Use existing functions

I18n support

All output strings should be linked to an appropriate text domain to allow for internationalization by interested parties, even if the developer has no interest in translating their own plug-in.

Note that it is very important to load the language files during the init action so the user can hook into the action.

See the Codex: I18n for WordPress Developers

And also this article: Loading WP language files the correctly.

Since WordPress 4.6+

WP 4.6 changed the load order and the locations checked, it has made it a lot easier for developers and users.

Considering a plugin with a textdomain 'my-plugin', WordPress will now FIRST look for a translation file in:
/wp-content/languages/plugins/my-plugin-en_US.mo

If it fails to find one there it will then look for one where the plugin tells it to look (usualy in the pluigns 'language' folder if following the codex):
/wp-content/plugins/my-plugin/languages/my-plugin-en_US.mo

Lastly if no language file is found it will check the default location of:
/wp-content/languages/my-plugin-en_US.mo

The first check was added in 4.6 and gives users a defined place to add a language file, as before they would need to know where the developer added the language file, now the user just needs to know the plugin's textdomain: /wp-content/languages/plugins/TEXTDOMAIN-LOCAL.mo


Below is the old way (Not relevant since WP 4.6+)

[...]
Finally, I would like to point out that is important to load custom user language files from WP_LANG_DIR before you load the language files that ship with the plugin. When multiple mo-files are loaded for the same domain, the first found translation will be used. This way the language files provided by the plugin will serve as a fallback for strings not translated by the user.

public function load_plugin_textdomain()
{
    $domain = 'my-plugin';
    // The "plugin_locale" filter is also used in load_plugin_textdomain()
    $locale = apply_filters( 'plugin_locale', get_locale(), $domain );

    load_textdomain( 
            $domain, 
            WP_LANG_DIR . '/my-plugin/' . $domain . '-' . $locale . '.mo' 
    );
    load_plugin_textdomain( 
            $domain, 
            FALSE, 
            dirname( plugin_basename(__FILE__) ) . '/languages/' 
    );
}