{"id":661,"date":"2020-04-04T23:00:38","date_gmt":"2020-04-04T23:00:38","guid":{"rendered":"https:\/\/www.antpace.com\/blog\/?p=661"},"modified":"2025-08-25T14:03:45","modified_gmt":"2025-08-25T14:03:45","slug":"how-to-create-a-wordpress-plugin","status":"publish","type":"post","link":"https:\/\/www.antpace.com\/blog\/how-to-create-a-wordpress-plugin\/","title":{"rendered":"Create a WordPress plugin &#8211; How to"},"content":{"rendered":"<p>Distributing software to app and plugin markets is a great way to gain organic traffic. <a href=\"https:\/\/www.antpace.com\/blog\/develop-apps-explore-the-world\/\">Last year I submitted BJJ Tracker<\/a> to the <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=xyz.appmaker.lbpvgo\" target=\"_blank\" rel=\"noopener\">Google Play store<\/a> as a Progressive Web App. Since then, I get signups every few days &#8211; with zero marketing effort.<\/p>\n<p>I created a <a href=\"https:\/\/wordpress.org\/plugins\/splitwit\/\" target=\"_blank\" rel=\"noopener\">WordPress plugin<\/a> for <a href=\"https:\/\/www.splitwit.com\" target=\"_blank\" rel=\"noopener\">SplitWit<\/a>, to grow its reach in a similar way. SplitWit helps run A\/B experiments on the web. A JavaScript snippet needs to\u00a0 be added to your code for it to work. This plugin injects the code snippet automatically.<\/p>\n<p>Here is the process I took to develop and submit it to the WordPress plugin directory.<\/p>\n<h2>Plugin code<\/h2>\n<p>Since this is such a simple plugin, all I needed was one PHP file, and a readme.txt file. &#8220;<a href=\"https:\/\/developer.wordpress.org\/plugins\/plugin-basics\/\" target=\"_blank\" rel=\"noopener\">At its simplest, a WordPress plugin is a PHP file with a WordPress plugin header comment.<\/a>&#8221;<\/p>\n<p>The header comment defines meta-data:<\/p>\n<pre>\/*\n\nPlugin Name: SplitWit\nPlugin URI: https:\/\/www.splitwit.com\/\nDescription: This plugin automatically adds the SplitWit code snippet to your WordPress site. SplitWit lets you create a variation of your web page using our visual editor. It splits traffic between the original version and your variation. You can track key metrics, and let SplitWit determine a winner. No code needed. Go to <a href=\"https:\/\/www.splitwit.com\/\">SplitWit<\/a> to register for free.\nAuthor: SplitWit\nVersion: 1.0\nLicense: GPLv2+\nLicense URI: http:\/\/www.gnu.org\/licenses\/gpl-2.0.txt\n\n*\/\n<\/pre>\n<p>My PHP code defines six functions, uses four action hooks, and one filter hook.<\/p>\n<p>The first function injects the SplitWit snippet code into the WordPress website&#8217;s header:<\/p>\n<pre>function splitwit_header_code(){\n\t\/\/inject SplitWit code snippet\n\n\t$splitwit_project_id = get_option('splitwit_project_id');\n\n\tif($splitwit_project_id){\n\t\twp_enqueue_script( 'splitwit-snippet', 'https:\/\/www.splitwit.com\/snippet\/'.$splitwit_project_id.'.js' );\n\t}\n}\nadd_action( 'wp_head', 'splitwit_header_code', 1 );\n<\/pre>\n<p>Another defines the WordPress plugin&#8217;s menu page:<\/p>\n<pre>function splitwit_plugin_menu_page() { ?&gt;\n\n\t&lt;div&gt;\n\t\t&lt;h1&gt;SplitWit Experiments&lt;\/h1&gt;\n\t\t&lt;p&gt;This plugin automatically adds the &lt;a href=\"https:\/\/www.splitwit.com\" target=\"_blank\"&gt;SplitWit&lt;\/a&gt; code snippet to your WordPress site. &lt;a href=\"https:\/\/www.splitwit.com\" target=\"_blank\"&gt;SplitWit&lt;\/a&gt; lets you create a variation of your web page using our visual editor. It splits traffic between the original version and your variation. You can track key metrics, and let SplitWit determine a winner. No code needed.\n\t\t&lt;\/p&gt;\n\t\t&lt;p&gt;You'll need to create an account at SplitWit.com - it's free. After signing up, you can create a new SplitWit project for your website. Find that project's ID code, and copy\/paste it into this page.&lt;\/p&gt;\n\n\t\t&lt;form method=\"post\" action=\"options.php\"&gt;\n\t\t\t&lt;?php settings_fields( 'splitwit_settings_group' ); ?&gt;\n\t\t\t&lt;input style=\"width: 340px;display: block; margin-bottom: 10px;\" type=\"text\" name=\"splitwit_project_id\" value=\"&lt;?php echo get_option('splitwit_project_id'); ?&gt;\" \/&gt;\n\t\t\t&lt;input type=\"submit\" class=\"button-primary\" value=\"Save\" \/&gt;\n\t\t&lt;\/form&gt;\n\t&lt;\/div&gt;\n\n&lt;?php }\n<\/pre>\n<p>I add that menu page to the dashboard:<\/p>\n<pre>function splitwit_plugin_menu() {\n\tadd_options_page('SplitWit Experiments', 'SplitWit Experiments', 'publish_posts', 'splitwit_settings', 'splitwit_plugin_menu_page');\n}\nadd_action( 'admin_menu', 'splitwit_plugin_menu' );\n<\/pre>\n<p>And link to it in the Settings section of the dashboard:<\/p>\n<pre>function splitwit_link( $links ) {\n     $links[] ='&lt;a href=\"' . admin_url( 'options-general.php?page=splitwit_settings' ) .'\"&gt;Settings&lt;\/a&gt;';\n    return $links;\n}\nadd_filter('plugin_action_links_'.plugin_basename(__FILE__), 'splitwit_link');\n<\/pre>\n<p>When the SplitWit code snippet is injected into the website&#8217;s header, it needs to reference a project ID. I register that value from the menu page:<\/p>\n<pre>function splitwit_settings(){\n\tregister_setting('splitwit_settings_group','splitwit_project_id','string');\n}\nadd_action( 'admin_init', 'splitwit_settings' );\n<\/pre>\n<p>If the project ID value has not been defined, I show a warning message at the top of the dashboard:<\/p>\n<pre>function splitwit_warning(){\n  if (!is_admin()){\n     return;\n  }\n\n  $splitwit_project_id = get_option(\"splitwit_project_id\");\n  if (!$splitwit_project_id || $splitwit_project_id &lt; 1){\n    echo \"&lt;div class='notice notice-error'&gt;&lt;p&gt;&lt;strong&gt;SplitWit is missing a project ID code.&lt;\/strong&gt; You need to enter &lt;a href='options-general.php?page=splitwit_settings'&gt;a SplitWit project ID code&lt;\/a&gt; for the plugin to work.&lt;\/p&gt;&lt;\/div&gt;\";\n  }\n}\nadd_action( 'admin_notices','splitwit_warning');\n<\/pre>\n<p><a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/how-your-readme-txt-works\/\" target=\"_blank\" rel=\"noopener\">The readme.txt defines additional meta-data<\/a>. Each section corresponds to parts of the WordPress plugin directory page. The header section is required, and includes some basic fields that are parsed to the plugin page UI.<\/p>\n<pre>=== SplitWit ===\nContributors: SplitWit\nPlugin Name: SplitWit\nPlugin URI: https:\/\/www.splitwit.com\nTags: split test, split testing, ab testing, conversions\nRequires at least: 2.8\nTested up to: 5.3.2\nStable tag: 1.0\n\nOptimize your website for maximum convertibility. This plugin lets you use SplitWit to run experiments on your WordPress website.\n<\/pre>\n<p>I also added sections for a long description and installation instructions. Later, I included a screenshots section (see <a href=\"#subversion-repo\">Subversion repo<\/a>).<\/p>\n<h2>Submit for review<\/h2>\n<p>Plugin zip files can be uploaded to <a href=\"https:\/\/wordpress.org\/plugins\/developers\/add\/\" target=\"_blank\" rel=\"noopener\">WordPress.org<\/a>.\u00a0 Plugins <a href=\"https:\/\/www.splitwit.com\/wordpress-plugin\" target=\"_blank\" rel=\"noopener\">can also be distributed<\/a> to WordPress users without this step &#8211; but having it listed in the WordPress directory lends credibility and visibility. After my initial submission, I received an email indicating issues with my code and requesting changes. The changes were simple: &#8220;use wp_enqueue commands&#8221; and &#8220;document use of an external service&#8221;.<\/p>\n<p>Originally, my &#8220;splitwit_header_code()&#8221; function include the SplitWit JS snippet directly as plain text. I changed it to use the built-in function &#8220;wp_enqueue_script()&#8221;.<\/p>\n<pre>\/\/wrong:\necho '&lt;script type=\"text\/javascript\" async src=\"https:\/\/www.splitwit.com\/snippet\/'.$splitwit_project_id.'.js\"&gt; &lt;\/script&gt;';\n\n\/\/correct:\nwp_enqueue_script( 'splitwit-snippet', 'https:\/\/www.splitwit.com\/snippet\/'.$splitwit_project_id.'.js' );\n\n<\/pre>\n<p>Next, they wanted me to disclose the use of SplitWit, the service that powers the plugin. I added this to my readme.txt:<\/p>\n<blockquote><p><em>This plugin relies on SplitWit, a third-party service. The SplitWit service adds code to your website to run A\/B experiments. It also collects data about how users interact with your site, based on the metrics you configure.<\/em><\/p><\/blockquote>\n<p>After making these changes, I replied back with an updated .zip. A few days later I received approval. But, that wasn&#8217;t the end\u00a0 &#8211; I still needed to upload my code to a <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/how-to-use-subversion\/\" target=\"_blank\" rel=\"noopener\">WordPress.org hosted SVN repository<\/a>.<\/p>\n<h2 id=\"subversion-repo\">Subversion Repo<\/h2>\n<p>I&#8217;ve used Git for versioning my entire career. I had heard of SVN, but never used it. What a great opportunity to learn!<\/p>\n<p>The approval email provided me with a <a href=\"https:\/\/plugins.svn.wordpress.org\/splitwit\" target=\"_blank\" rel=\"noopener\">SVN URL<\/a>. On my local machine, I created a new folder, &#8220;svn-wp-splitwit&#8221;. From a terminal, I navigated to this directory and checked out the pre-built repo:<\/p>\n<pre>svn co https:\/\/plugins.svn.wordpress.org\/splitwit<\/pre>\n<p>I added my plugin files (readme.txt and splitwit.php) to the &#8220;trunk&#8221; folder. This is where the most up-to-date, ready-to-distribute, version of code belongs.<\/p>\n<p>In the &#8220;tags&#8221; folder, I created a new directory called &#8220;1.0&#8221; and put a copy of my files there too &#8211; for the sake of version control. This step is completely optional and is how SVN handles revisions.<\/p>\n<p>In the assets folder I included my banner, icon, and screenshot files. The filenames follow <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/plugin-assets\/\" target=\"_blank\" rel=\"noopener\">as prescribed by WordPress.org<\/a>. I made sure to reference the screenshot files in my readme.txt file, under a new &#8220;Screenshots&#8221; section.<\/p>\n<p>Finally, I pushed my code back up to the remote:<\/p>\n<pre> svn ci -m \"Initial commit of my plugin.\"<\/pre>\n<p>You can now find my plugin in the WordPress.org plugin directory. SplitWit is available for a free trial. Give it a try, and let me know what you think.<\/p>\n<p>&nbsp;<\/p>\n<hr \/>\n<p>&nbsp;<\/p>\n<p>Pro-tip: Some WordPress setups won&#8217;t let you to install plugins from the dashboard with out providing FTP credentials, including a password. If you use a key file, instead of a password, this is a roadblock.<\/p>\n<figure id=\"attachment_662\" aria-describedby=\"caption-attachment-662\" style=\"width: 1340px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-662 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/04\/install-plugin-roadblock.png\" alt=\"Install WordPress plugin roadblock\" width=\"1340\" height=\"778\" \/><figcaption id=\"caption-attachment-662\" class=\"wp-caption-text\">Not everyone uses a password to connect to their server.<\/figcaption><\/figure>\n<p>You can remedy this by defining the file system connection method in your functions.php file:<\/p>\n<pre>define( 'FS_METHOD', 'direct' );<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Distributing software to app and plugin markets is a great way to gain organic traffic. Last year I submitted BJJ Tracker to the Google Play store as a Progressive Web App. Since then, I get signups every few days &#8211; with zero marketing effort. I created a WordPress plugin for SplitWit, to grow its reach &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.antpace.com\/blog\/how-to-create-a-wordpress-plugin\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Create a WordPress plugin &#8211; How to&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3169,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[90,94,100,101,137,143],"class_list":["post-661","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-organic-traffic","tag-plugin","tag-progressive-web-app","tag-pwa","tag-web-app","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/661","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/comments?post=661"}],"version-history":[{"count":1,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/661\/revisions"}],"predecessor-version":[{"id":3170,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/661\/revisions\/3170"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media\/3169"}],"wp:attachment":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media?parent=661"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/categories?post=661"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/tags?post=661"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}