Developer Integration Guide
Follow these steps to enable automatic updates for your projects using this server.
This server provides a generic API. You can use it for WordPress plugins (Steps 1-3) or for any custom PHP project (Step 4).
Step 1: Add Your Plugin to the Server
First, you need to prepare your plugin's files on this update server.
1. Create the Plugin Directory
Inside the /plugins/ directory, create a new folder. The folder name must be the **exact slug** of your plugin (e.g., sample-super-plugin).
/plugins/your-plugin-slug/
2. Create the `info.json` File
Inside your new plugin directory, create a file named info.json. This file contains all the metadata for your plugin. Copy the template below and customize it.
{
"name": "Your Plugin Name",
"version": "1.0.0",
"author": "Refat Rahman",
"author_profile": "https://profiles.wordpress.org/refatbd/",
"requires": "5.8",
"tested": "6.1",
"homepage": "https://wordpress.refat.ovh/plugin/your-plugin-slug",
"sections": {
"description": "A short, clear description of what your plugin does.",
"changelog": "<h4>Version 1.0.0 - October 15, 2025</h4><ul><li>Initial release of the plugin.</li></ul>"
},
"banners": {
"low": "https://example.com/banner-772x250.png",
"high": "https://example.com/banner-1544x500.png"
},
"screenshots": [
{
"src": "https://example.com/screenshot-1.png",
"caption": "This is the first screenshot"
},
{
"src": "https://example.com/screenshot-2.png",
"caption": "This is the second screenshot"
}
],
"icons": {
"1x": "https://example.com/icon-128x128.png",
"2x": "https://example.com/icon-256x256.png"
}
}
2a. Formatting Guide for `info.json`
To ensure your plugin details display correctly on the repository site, follow these formatting rules. The Description and Changelog sections work differently.
| Field | Format | HTML Tags Allowed? | Line Breaks |
|---|---|---|---|
sections.description |
Plain Text | ❌ NO | Automatic (Single Paragraph) |
sections.changelog |
HTML | ✅ YES | Use HTML tags (<ul>, <br>) |
The Description Section
- Plain Text Only: Do NOT use HTML tags here. The server automatically cleans this text for security.
- No Custom Styling: Tags like
<b>or<br>will be displayed as literal text. - Structure: Write this as a single, continuous paragraph.
The Changelog Section
- HTML Required: This section is output directly, so you must use HTML to format your content.
- Recommended Tags:
- Lists: Use
<ul>and<li>for version notes. - Headings: Use
<h4>for version numbers. - Emphasis: Use
<strong>for bold and<em>for italics.
- Lists: Use
- JSON Syntax Warning: Since this is a JSON file, you cannot use actual line breaks in the code. You must write the HTML as a single long string.
Correct Example:
"sections": {
"description": "This is a plain text description. It does not support bold or italics.",
"changelog": "<h4>Version 1.0</h4><ul><li>Added a <strong>bold</strong> feature.</li><li>Fixed a bug.</li></ul>"
}
Incorrect Example (Do not do this):
"sections": {
"description": "<b>This will break</b> because HTML is escaped.",
"changelog": "Version 1.0 \n - This plain text list won't render correctly."
}
3. Create the `tags` and `source` Directories
Inside your plugin directory (e.g., /plugins/your-plugin-slug/), create a folder named tags. Inside tags, create a folder for the current version (e.g., 1.0.0). Finally, inside the version folder, create one more folder named source.
Upload your raw plugin files (e.g., your-plugin-slug.php, class-plugin-updater.php, etc.) directly into the source directory. The server will handle zipping them automatically.
The final path to your plugin files should look like this:
/plugins/your-plugin-slug/tags/1.0.0/source/your-plugin-slug.php
/plugins/your-plugin-slug/tags/1.0.0/source/another-file.php
Step 2: Add the Updater Class to Your (WordPress) Plugin
This step is specific to **WordPress plugins**. If you are integrating with a custom PHP project, skip to Step 4.
1. Create the Updater Class File
Copy the code below and save it as a new file inside your plugin's main folder. A good name is class-plugin-updater.php.
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class My_Plugin_Updater {
private $api_url = '';
private $plugin_file = '';
private $plugin_slug = '';
private $current_version = '';
public function __construct( $_api_url, $_plugin_file ) {
$this->api_url = trailingslashit( $_api_url );
$this->plugin_file = $_plugin_file;
$this->plugin_slug = basename( dirname( $_plugin_file ) );
$plugin_data = get_plugin_data( $_plugin_file );
$this->current_version = $plugin_data['Version'];
add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_update' ] );
add_filter( 'plugins_api', [ $this, 'plugin_api_call' ], 10, 3 );
add_action( 'upgrader_process_complete', [ $this, 'clear_update_transient' ], 10, 2 );
}
public function check_for_update( $transient ) {
if ( empty( $transient->checked ) ) {
return $transient;
}
$plugin_basename = plugin_basename( $this->plugin_file );
// Make the API request
$response = $this->api_request( 'update_check', [ 'slug' => $this->plugin_slug, 'version' => $this->current_version ] );
// Check if we received a valid response
if ( $response ) {
// Compare the server version with the currently installed version
if ( version_compare( $response->new_version, $this->current_version, '>' ) ) {
// An update is available, so add it to the 'response' array
$transient->response[ $plugin_basename ] = $response;
} else {
// No update is available, but we add the info to 'no_update'
// This ensures "View details" and other links are still available
$transient->no_update[ $plugin_basename ] = $response;
}
}
return $transient;
}
public function plugin_api_call( $result, $action, $args ) {
if ( 'plugin_information' !== $action || !isset( $args->slug ) || $args->slug !== $this->plugin_slug ) {
return $result;
}
$response = $this->api_request( 'plugin_information', ['slug' => $this->plugin_slug] );
if ( $response ) return $response;
return $result;
}
private function api_request( $action, $args ) {
$response = wp_remote_post( $this->api_url, [
'body' => ['action' => $action, 'request' => json_encode($args)],
'timeout' => 15
]);
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) return false;
$result = json_decode( wp_remote_retrieve_body( $response ), true );
if ( $result && !empty($result['success']) && !empty($result['data']) ) {
return (object) $result['data'];
}
return false;
}
public function clear_update_transient( $upgrader_object, $options ) {
if (
isset( $options['action'] ) && $options['action'] === 'update' &&
isset( $options['type'] ) && $options['type'] === 'plugin' &&
isset( $options['plugins'] )
) {
$plugin_basename = plugin_basename( $this->plugin_file );
if ( in_array( $plugin_basename, $options['plugins'] ) ) {
delete_site_transient( 'update_plugins' );
}
}
}
}
2. Initialize the Updater
Open your main plugin file (the one with the plugin headers like `Plugin Name:`). Add the following code right after the header comments.
/**
* Plugin Name: Your Super Plugin
* Description: A description of your plugin.
* Version: 1.0.0
* Author: Refat Rahman
*/
// Include and initialize the updater.
require_once __DIR__ . '/class-plugin-updater.php';
function my_plugin_start_updater() {
$api_url = 'https://wordpress.refat.ovh/api/update.php';
new My_Plugin_Updater( $api_url, __FILE__ );
}
add_action( 'admin_init', 'my_plugin_start_updater' );
// Your plugin's regular code starts here...
Step 3: Releasing a New Version
When you have a new version of your plugin ready, follow this checklist:
- Update the
Version:number in your main plugin file's header (e.g., from1.0.0to1.0.1). - On the server, create a new version folder inside
/plugins/your-plugin-slug/tags/(e.g.,1.0.1). - Inside the new version folder, create a
sourcedirectory. - Upload the updated raw plugin files into that new
sourcefolder. - Most importantly: Edit the
info.jsonfile and update the"version"field to the new version number (e.g.,"version": "1.0.1").
How to Test (WordPress): WordPress only checks for updates twice a day. To force an immediate check, go to your WordPress admin, navigate to Dashboard -> Updates, and click the "Check Again" button.
Step 4: Integrating with a Custom PHP Project
You can use this server to provide updates for any PHP project, not just WordPress plugins. The server API is generic.
Your custom project will need its own simple "check for update" script. Here is a basic example of how you could build one.
Example `check_update.php` script for your project:
You can include this code in your project's admin area, or run it as part of a scheduled task.
<?php
// --- Configuration for Your Custom Project ---
// The unique slug for your project, matching the folder name in /plugins/
define('MY_PROJECT_SLUG', 'my-custom-app');
// The current version of your project. You should store this in a file or database.
define('MY_PROJECT_VERSION', '1.0.0');
// The URL to your update server's API
$api_url = 'https://wordpress.refat.ovh/api/update.php';
// --- End Configuration ---
/**
* Makes a POST request to the update server.
*
* @param string $url The API endpoint URL.
* @param string $action The API action to perform (e.g., 'update_check').
* @param array $request_data The data to send in the 'request' parameter.
* @return object|null The 'data' part of the JSON response, or null on failure.
*/
function my_custom_api_request( $url, $action, $request_data ) {
$payload = [
'action' => $action,
'request' => json_encode($request_data)
];
// Use cURL if available
if ( function_exists('curl_init') ) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$result = curl_exec($ch);
curl_close($ch);
} else {
// Fallback to file_get_contents
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($payload),
'timeout' => 15
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
}
if ( $result === false ) {
return null;
}
$response = json_decode($result);
if ( $response && isset($response->success) && $response->success && isset($response->data) ) {
return $response->data;
}
return null;
}
// --- Check for an update ---
echo "<h3>Update Check</h3>";
echo "<p>Checking for updates to '" . MY_PROJECT_SLUG . "' (Current Version: " . MY_PROJECT_VERSION . ")</p>";
$update_data = my_custom_api_request( $api_url, 'update_check', [
'slug' => MY_PROJECT_SLUG,
'version' => MY_PROJECT_VERSION
]);
if ( $update_data === null ) {
echo "<p style='color: red;'>Error: Could not connect to update server.</p>";
} elseif ( $update_data && version_compare( $update_data->new_version, MY_PROJECT_VERSION, '>' ) ) {
echo "<p style='color: green; font-weight: bold;'>A new version is available!</p>";
echo "<ul>";
echo "<li><strong>New Version:</strong> " . htmlspecialchars($update_data->new_version) . "</li>";
echo "<li><strong>Download URL:</strong> " . htmlspecialchars($update_data->package) . "</li>";
echo "</ul>";
echo '<a href="' . htmlspecialchars($update_data->package) . '" class="btn">Download Update</a>';
echo "<p><em>Note: You are responsible for writing the PHP to download this zip, extract it, and replace the old project files.</em></p>";
} else {
echo "<p style='color: blue;'>Your project is up to date.</p>";
}
?>
How to Implement the Update
The example above only *checks* for the update. To complete the process, you would need to:
- Provide a "Download & Install" button.
- When clicked, your script should download the
.zipfile from thepackageURL. - Use PHP's
ZipArchiveclass to extract the files from the downloaded zip. - Carefully overwrite the old project files with the new ones from the
sourcedirectory in the zip.