PHPackages                             apermo/wp-update-server - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. apermo/wp-update-server

ActiveLibrary

apermo/wp-update-server
=======================

Self-hosted update API for WordPress plugins and themes, compatible with Plugin Update Checker and Composer.

v3.1.0(1mo ago)01↑2900%[4 issues](https://github.com/apermo/wp-update-server/issues)MITPHPPHP &gt;=8.0CI failing

Since Mar 26Pushed 1mo agoCompare

[ Source](https://github.com/apermo/wp-update-server)[ Packagist](https://packagist.org/packages/apermo/wp-update-server)[ Docs](https://github.com/apermo/wp-update-server/)[ RSS](/packages/apermo-wp-update-server/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (1)Dependencies (6)Versions (4)Used By (0)

WP Update Server
================

[](#wp-update-server)

[![Packagist Version](https://camo.githubusercontent.com/e477e30a125ecd07d93afc5a8bbe371b8da126a242407388cbc10798a6380aab/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f617065726d6f2f77702d7570646174652d736572766572)](https://packagist.org/packages/apermo/wp-update-server)[![PHP Version](https://camo.githubusercontent.com/7796792725fa3962fda686370c29bf15c517107592d3684347802eeb54bf0207/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f617065726d6f2f77702d7570646174652d736572766572)](https://packagist.org/packages/apermo/wp-update-server)[![License](https://camo.githubusercontent.com/e4a30ab6e9204b8579cb3335ddbcae51cb2750624311a044f830d18e6856066e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f617065726d6f2f77702d7570646174652d736572766572)](https://packagist.org/packages/apermo/wp-update-server)[![codecov](https://camo.githubusercontent.com/697af169c7701d9658d1d4c183ae65595331297e3771f974ce88ab953097e230/68747470733a2f2f636f6465636f762e696f2f67682f617065726d6f2f77702d7570646174652d7365727665722f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/apermo/wp-update-server)

A self-hosted update API for WordPress plugins and themes, compatible with the [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker) library and Composer.

Originally forked from [YahnisElsts/wp-update-server](https://github.com/YahnisElsts/wp-update-server), now independently maintained with a modernized codebase.

Features
--------

[](#features)

- **Plugin and theme updates** — works like WordPress.org from the user's perspective
- **Multiple versions per package** — versioned directory layout with `?version=` parameter
- **Pre-release channels** — distribute alpha/beta/RC builds via `?channel=` parameter
- **Composer repository** — `?action=composer_packages` endpoint for `composer require`
- **Upload API** — deploy new versions via `POST ?action=upload` with Bearer token auth
- **License key authentication** — pluggable provider with file-based default
- **Configuration file** — `config.php` for settings without subclassing
- **Extensible by design** — override `filterMetadata()`, `checkAuthorization()`, or any method

Requirements
------------

[](#requirements)

- PHP 8.0+
- `ext-zip`
- `ext-json`

Quick Start
-----------

[](#quick-start)

### 1. Install

[](#1-install)

```
git clone https://github.com/apermo/wp-update-server.git
cd wp-update-server
composer install
```

Or download the [latest release](https://github.com/apermo/wp-update-server/releases) and upload to your server. Composer is optional — a built-in PSR-4 autoloader handles class loading without it.

### 2. Configure

[](#2-configure)

```
cp config.sample.php config.php
```

Edit `config.php` to your needs. All settings are optional — the server works with sensible defaults.

### 3. Add packages

[](#3-add-packages)

Create the versioned directory structure and drop your ZIP files:

```
packages/
  my-plugin/
    1.0.0/
      my-plugin.zip
    1.1.0/
      my-plugin.zip
  my-theme/
    2.0.0/
      my-theme.zip

```

The ZIP must contain a single top-level directory matching the slug, with a valid `Plugin Name:` or `Theme Name:` header inside.

### 4. Verify

[](#4-verify)

```
https://your-server.com/wp-update-server/?action=get_metadata&slug=my-plugin

```

You should see a JSON response with the plugin metadata.

Integrating with Plugins
------------------------

[](#integrating-with-plugins)

Use the [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker) library:

```
require 'path/to/plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;

$updateChecker = PucFactory::buildUpdateChecker(
    'https://your-server.com/wp-update-server/?action=get_metadata&slug=my-plugin',
    __FILE__,
    'my-plugin'
);
```

Updates will appear in the WordPress Dashboard just like plugins from WordPress.org.

**Tip:** Create a `readme.txt` following the [WordPress.org standard](https://developer.wordpress.org/plugins/wordpress-org/how-your-readme-txt-works/)to populate the "View details" modal.

Integrating with Composer
-------------------------

[](#integrating-with-composer)

Point Composer at your server as a repository:

```
{
    "repositories": [
        {
            "type": "composer",
            "url": "https://your-server.com/wp-update-server"
        }
    ],
    "require": {
        "your-vendor/my-plugin": "^1.0"
    }
}
```

Composer requests `/packages.json` on the repository URL. This requires a web server rewrite rule to route the request through `index.php` — see [Web Server Configuration](#web-server-configuration)below.

The vendor prefix is configurable in `config.php` (default: `wpup`).

### Authenticated Composer access

[](#authenticated-composer-access)

For packages that require a license key, Composer authenticates via its native `auth.json`mechanism. The server accepts Bearer tokens from the `Authorization` header, which Composer sends automatically when configured:

```
composer config bearer.your-server.com your-license-key
```

This stores the token in `auth.json` (not `composer.json`, so it stays out of version control):

```
{
    "bearer": {
        "your-server.com": "your-license-key"
    }
}
```

Enable license authentication on the server side in `config.php`:

```
return [
    'auth' => [
        'require_license' => true,
        'public_packages' => ['free-plugin'],  // these don't need a key
        'licenses_file'   => 'licenses.json',
    ],
];
```

API Reference
-------------

[](#api-reference)

EndpointMethodDescription`?action=get_metadata&slug=X`GETPackage metadata (JSON)`?action=get_metadata&slug=X&version=1.0.0`GETMetadata for a specific version`?action=get_metadata&slug=X&channel=beta`GETLatest version for a stability channel`?action=download&slug=X`GETDownload the latest stable ZIP`?action=download&slug=X&version=1.0.0`GETDownload a specific version`?action=composer_packages`GETComposer `packages.json``?action=upload`POSTUpload a new package version (requires API key)Configuration
-------------

[](#configuration)

Copy `config.sample.php` to `config.php`. Key options:

```
return [
    'vendor_prefix'        => 'wpup',     // Composer vendor prefix
    'legacy_flat_packages' => false,       // Enable packages/{slug}.zip fallback
    'logging' => [
        'anonymize_ip' => false,
        'rotation'     => ['enabled' => false, 'period' => 'Y-m', 'keep' => 10],
    ],
    'auth' => [
        'require_license'  => false,
        'public_packages'  => [],
        'licenses_file'    => 'licenses.json',
    ],
    'upload' => [
        'api_keys' => [],
        'max_size' => 50 * 1024 * 1024,
    ],
];
```

See [`config.sample.php`](config.sample.php) for the full reference.

Web Server Configuration
------------------------

[](#web-server-configuration)

The Composer integration requires `/packages.json` to be routed through `index.php`. A matching `.htaccess` is included for Apache. For other web servers, add the equivalent rewrite rule.

### Apache / LiteSpeed

[](#apache--litespeed)

The included `.htaccess` handles this automatically. Ensure `mod_rewrite` is enabled:

```

    RewriteEngine On
    RewriteRule ^packages\.json$ index.php [L,QSA]

```

LiteSpeed is fully compatible with Apache `.htaccess` rewrite rules — no additional configuration needed.

### nginx

[](#nginx)

Add a location block to your server configuration:

```
server {
    # ... existing config ...

    location = /packages.json {
        rewrite ^ /index.php last;
    }

    location ~ \.php$ {
        # ... your existing PHP-FPM config ...
    }
}
```

Extending the Server
--------------------

[](#extending-the-server)

Create a subclass and override any method:

```
require __DIR__ . '/loader.php';

use Apermo\WpUpdateServer\UpdateServer;
use Apermo\WpUpdateServer\Request;

class MyServer extends UpdateServer {

    protected function filterMetadata( array $meta, Request $request ): array {
        $meta = parent::filterMetadata( $meta, $request );
        unset( $meta['download_url'] );
        return $meta;
    }
}

$server = new MyServer();
$server->handleRequest();
```

Common extension points:

- `filterMetadata()` — modify the JSON response
- `checkAuthorization()` — custom auth logic
- `RequestLogger::filterLogInfo()` — customize log entries (subclass `RequestLogger`)
- `dispatch()` — add custom actions

Logging
-------

[](#logging)

All requests are logged to `logs/request.log`:

```
[2026-03-26 14:00:00 +0000] 192.168.1.xxx  GET  get_metadata  my-plugin  1.0.0  6.4  https://example.com  action=get_metadata&slug=my-plugin

```

Enable IP anonymization and log rotation in `config.php`.

Development
-----------

[](#development)

```
# Install dependencies
composer install

# Run tests
composer test

# Run code style checks
composer cs

# Run static analysis
composer analyse

# Start local DDEV environment
ddev start

# Run smoke tests against DDEV
tests/smoke-test.sh
```

Migrating from v2.x
-------------------

[](#migrating-from-v2x)

See [`docs/migration.md`](docs/migration.md) for a step-by-step upgrade guide including a shell script to migrate packages from the flat layout to the versioned directory structure.

Credits
-------

[](#credits)

Originally created by [Yahnis Elsts](https://w-shadow.com/). Now independently maintained by [Christoph Daum](https://christoph-daum.de/).

License
-------

[](#license)

[MIT](license.txt)

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/910b8010a35a86821d0b90d645374f5ae484513f2c195818e4c54bc0175d12e1?d=identicon)[apermo](/maintainers/apermo)

---

Top Contributors

[![YahnisElsts](https://avatars.githubusercontent.com/u/2527434?v=4)](https://github.com/YahnisElsts "YahnisElsts (118 commits)")[![apermo](https://avatars.githubusercontent.com/u/4695889?v=4)](https://github.com/apermo "apermo (58 commits)")[![jrfnl](https://avatars.githubusercontent.com/u/663378?v=4)](https://github.com/jrfnl "jrfnl (55 commits)")[![relic-se](https://avatars.githubusercontent.com/u/21224161?v=4)](https://github.com/relic-se "relic-se (3 commits)")[![HungNth](https://avatars.githubusercontent.com/u/63095696?v=4)](https://github.com/HungNth "HungNth (1 commits)")[![mweimerskirch](https://avatars.githubusercontent.com/u/362092?v=4)](https://github.com/mweimerskirch "mweimerskirch (1 commits)")[![dangoodman](https://avatars.githubusercontent.com/u/345718?v=4)](https://github.com/dangoodman "dangoodman (1 commits)")

---

Tags

wordpressautomatic updatesplugin updatestheme updatesself hostedcomposer repositoryupdate serverplugin-update-checkerwp-update-serverpackage server

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/apermo-wp-update-server/health.svg)

```
[![Health](https://phpackages.com/badges/apermo-wp-update-server/health.svg)](https://phpackages.com/packages/apermo-wp-update-server)
```

###  Alternatives

[yahnis-elsts/plugin-update-checker

A custom update checker for WordPress plugins and themes. Useful if you can't host your plugin in the official WP repository but still want it to support automatic updates.

2.5k1.5M79](/packages/yahnis-elsts-plugin-update-checker)[yahnis-elsts/wp-update-server

Custom update API server for WordPress plugins and themes.

8741.3k](/packages/yahnis-elsts-wp-update-server)[roots/wordpress

WordPress is open source software you can use to create a beautiful website, blog, or app.

19116.9M258](/packages/roots-wordpress)[aristath/kirki

Extending the WordPress customizer

1.3k73.0k4](/packages/aristath-kirki)[wpreadme2markdown/wpreadme2markdown

Convert WordPress Plugin readme.txt to Markdown

9564.6k4](/packages/wpreadme2markdown-wpreadme2markdown)[wpstarter/framework

The WpStarter Framework - Laravel Framework for WordPress

1810.1k4](/packages/wpstarter-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
