A WordPress plugin for automatic updates from Forgejo. A lightweight alternative to Git Updater, with no banners and no external dependencies.
Find a file
emanuelegori f0567ac5f7 Admin table: Source shows the update channel, Status never empty
Source column shows Forgejo or WordPress.org (registration type moves to the tooltip); Repository links to the WordPress.org page for directory plugins; channel button on its own line; Status shows Up to date and, when forced to Forgejo, the version on WordPress.org.
2026-09-26 17:01:31 +02:00
.forgejo/workflows ci: workflow auto-release su bump versione (tag + note CHANGELOG + zip) 2026-06-15 22:34:04 +02:00
includes Admin table: Source shows the update channel, Status never empty 2026-09-26 17:01:31 +02:00
languages Admin table: Source shows the update channel, Status never empty 2026-09-26 17:01:31 +02:00
.distignore Fix phpcs:ignore echo $ok/$warn e nonce notice, aggiungi .distignore 2026-06-02 13:44:18 +02:00
.gitattributes build: add .gitattributes to exclude dev files from release archives 2026-08-09 16:43:27 +02:00
.gitignore feat: sicurezza, file standard WP e miglioramenti architetturali 2026-04-05 20:15:53 +02:00
CHANGELOG.md Admin table: Source shows the update channel, Status never empty 2026-09-26 17:01:31 +02:00
eg-forgejo-updater.php v2.12.0 — Hand directory plugins back to WordPress.org 2026-09-26 16:28:21 +02:00
index.php feat: sicurezza, file standard WP e miglioramenti architetturali 2026-04-05 20:15:53 +02:00
LICENSE feat: sicurezza, file standard WP e miglioramenti architetturali 2026-04-05 20:15:53 +02:00
README.it-IT.md Admin table: Source shows the update channel, Status never empty 2026-09-26 17:01:31 +02:00
README.md Admin table: Source shows the update channel, Status never empty 2026-09-26 17:01:31 +02:00
readme.txt Admin table: Source shows the update channel, Status never empty 2026-09-26 17:01:31 +02:00
uninstall.php v2.12.0 — Hand directory plugins back to WordPress.org 2026-09-26 16:28:21 +02:00

EG Forgejo Updater

🇬🇧 English · 🇮🇹 Italiano

Minimal WordPress plugin that automatically updates plugins hosted on a self-hosted Forgejo instance.

A lightweight replacement for Git Updater: no ad banners, no Redis compatibility issues, token saved in wp-config.php once and for all.


Project structure

eg-forgejo-updater/
├── eg-forgejo-updater.php            # Bootstrap, plugin header, class includes
├── uninstall.php                     # Data cleanup on uninstall
├── index.php                         # Directory listing protection
├── readme.txt                        # Standard WordPress readme
├── CHANGELOG.md                      # Changelog
├── LICENSE                           # GPL-2.0-or-later
└── includes/
    ├── class-eg-forgejo-api.php      # Forgejo REST API client
    ├── class-eg-forgejo-updater.php  # WordPress hooks for update detection and install
    ├── class-eg-forgejo-admin.php    # Admin page (Plugins submenu)
    └── index.php                     # Directory listing protection

Requirements

  • WordPress 6.0 or later
  • PHP 8.0 or later (uses typed properties and named arguments)
  • Forgejo instance reachable over HTTPS from the WordPress host
  • For each managed plugin: at least one tag or release in the corresponding Forgejo repo

Installation

  1. Upload the eg-forgejo-updater/ folder to wp-content/plugins/
  2. Add FORGEJO_ACCESS_TOKEN to wp-config.php (required for private repos)
  3. Activate the plugin from Dashboard > Plugins
  4. Plugins with a Forgejo Plugin URI or Gitea Plugin URI header are detected automatically
  5. To install additional plugins from Forgejo, use the built-in feature described below

Configuration in wp-config.php

Add these lines before the line /* That's all, stop editing! */:

// Personal access token (required for private repos)
define( 'FORGEJO_ACCESS_TOKEN', 'your_token_here' );

// Base URL of the Forgejo instance (optional since v2.0 — derived from the plugin header)
define( 'FORGEJO_BASE_URL', 'https://git.example.com' );

How to create FORGEJO_ACCESS_TOKEN

  1. Log in to your Forgejo instance with your account
  2. Go to Settings → Applications → Manage Access Tokens
  3. Create a new token with the following permissions:
    • Repository → Read (to read raw files and download ZIPs)
    • Everything else: No access
  4. Copy the generated token and paste it into wp-config.php

An administrator account is not required: the account that owns the repositories you need to manage is enough.

Notes on the FORGEJO_ACCESS_TOKEN constant

Required for private repos. The token is used in two places:

  1. In the Authorization: token xxx header of Forgejo REST API calls
  2. In the Authorization: token xxx header of the ZIP download request during the update

The token is added only to requests directed to known Forgejo URLs (from the FORGEJO_BASE_URL constant and/or from plugin headers). It is never sent to third-party domains.

Notes on the FORGEJO_BASE_URL constant

Deprecated. The Forgejo Plugin URI header should always contain the full repository URL (e.g. https://git.example.com/owner/repo) — the base URL is automatically derived from there. Defining FORGEJO_BASE_URL in wp-config.php is not needed and ties the plugin to a specific URL, hurting portability if the Forgejo instance changes.

Plugin auto-discovery

Since v2.0 the plugin automatically detects the plugins to manage by reading the header of the main PHP file. Two headers are supported, compatible with Git Updater:

/**
 * Plugin Name: My Plugin
 * Forgejo Plugin URI: https://git.example.com/owner/my-plugin
 */

or:

/**
 * Plugin Name: My Plugin
 * Gitea Plugin URI: https://git.example.com/owner/my-plugin
 */

If FORGEJO_BASE_URL is defined, the short form can also be used:

 * Forgejo Plugin URI: owner/my-plugin

Plugins detected automatically cannot be removed from the list (just remove the header to exclude them); the tooltip on the Source column says whether a plugin was detected from its header or installed through the form.

Plugins also listed on WordPress.org

When a managed plugin is published in the WordPress.org directory, the updater steps aside: the "View details" popup and the updates come from WordPress.org. In the admin table the Source column shows WordPress.org and the Repository column links to the plugin's WordPress.org page.

  • Detection reads the update_plugins transient filled by WordPress's own update check: an entry with id = w.org/plugins/<slug> means the plugin is in the directory. No extra request for plugins that are not.
  • Slug collisions: WordPress.org matches plugins by folder slug only. The updater steps aside only if the Contributors: line of the installed readme.txt shares at least one name with the plugin's contributors on WordPress.org (plugins/info/1.2, cached for one week). Otherwise the plugin stays on Forgejo and the Status column shows "Slug taken on WordPress.org".
  • Manual switch: the "Use Forgejo" button forces a directory plugin back to Forgejo (stored in eg_forgejo_updater_channels); "Automatic" undoes it.

Why in wp-config.php and not in the database

Saving credentials in the WordPress database (even encrypted) potentially exposes them to malicious plugins or database dumps. wp-config.php lies outside the webroot in standard setups and is never served by NGINX/Apache. This approach is compatible with Redis Object Cache because it does not rely on transients for credentials.


How update detection works

Full flow

WordPress scheduler (every 12h) or manual action
  └── Filter site_transient_update_plugins
        └── For each plugin with a Forgejo/Gitea Plugin URI header:
              └── get_transient(eg_fu_release_{md5(plugin_file)})
                    ├── Cache hit  → use cached data
                    └── Cache miss → call Forgejo API (1 single call)
                          └── GET /api/v1/repos/{owner}/{repo}/raw/{file}.php?ref=main
                                └── Parse "Version:" from the remote source
                                      ├── Remote version > installed → inject into $transient->response
                                      └── Equal or lower version       → remove from $transient->response

Check frequency

The version-data cache is saved as a WordPress transient with a TTL of 12 hours (CACHE_TTL = 43200). This means:

  • The automatic check happens every 12 hours, in sync with the native WordPress cycle
  • Going to Dashboard > Updates and clicking "Check again" forces an immediate check
  • The "Check for updates now" button on the plugin page explicitly flushes all transients

Version comparison

WordPress internally uses version_compare() (PHP native). The remote version is read directly from the Version: header of the main PHP file on the repo branch.


ZIP file handling during the update

Archive download

The plugin downloads the ZIP archive of the primary branch from the Forgejo repo:

https://git.example.com/owner/repo/archive/main.zip

The folder-name problem

Forgejo generates archives with a folder name like repo-main/ that does not match the plugin slug (e.g. eg-fediverso-box/). WordPress expects the extracted folder to have the same name as the slug.

The plugin handles this via the upgrader_source_selection filter, which renames the extracted folder to the correct slug before WordPress finishes the installation.


Installing a plugin from Forgejo

EG Forgejo Updater lets you install any public plugin hosted on Forgejo directly from WordPress, without downloading ZIPs, without FTP, without manual upload.

How to do it

  1. Go to Plugins → Forgejo Updater
  2. In the "Install plugin from Forgejo" section, paste the repository URL
  3. Click "Verify and continue"
  4. The plugin runs an automatic checklist:
    • Valid URL format
    • Recognized Forgejo/Gitea instance
    • Reachable repository
    • Main PHP file found
    • Plugin Name and Version headers present
    • Forgejo Plugin URI header present (required for automatic updates)
  5. If all checks pass, a preview with name, version and author is shown
  6. Click "Install plugin" to proceed
  7. Activate the plugin from the plugin list

Once installed, the plugin is automatically detected by the auto-discovery feature and will receive updates like the others.

Adding via header (auto-discovery)

For plugins already installed with the header in the main PHP file:

 * Forgejo Plugin URI: https://git.emanuelegori.uno/emanuelegori/repotest
 * Primary Branch: main

The plugin is detected automatically when the page is loaded, with no additional configuration.


Forgejo icon in the plugin list

In Plugins > Installed Plugins, every plugin managed by this updater shows an extra link in the meta info row (below the name and description), with the Forgejo logo and a direct link to the repository.

The SVG logo is embedded inline in PHP (get_forgejo_svg()), with no separate asset files and no additional HTTP requests.


WordPress hooks used

Hook Class Purpose
plugins_loaded bootstrap Class initialization
site_transient_update_plugins Updater Inject update data into the native WP cycle
plugins_api Updater "Version details" popup in the dashboard (left to WordPress for directory plugins)
plugin_row_meta Updater Forgejo icon and repo link in the plugin list
http_request_args Updater Add Authorization token for private repo downloads
upgrader_source_selection Updater Fix the ZIP folder name from zipball
upgrader_process_complete Updater Cache flush after a completed update
admin_menu Admin Register submenu under Plugins
admin_post_eg_fu_preview Admin Verify repo URL and compatibility checklist
admin_post_eg_fu_install Admin Install plugin from Forgejo zip
admin_post_eg_fu_delete Admin Remove plugin from the list
admin_post_eg_fu_check Admin Force an immediate update check
admin_post_eg_fu_channel Admin Per-plugin channel: automatic or forced to Forgejo
admin_notices Admin Feedback notices

Redis Object Cache compatibility

Transients are saved through native WordPress set_transient() / get_transient() / delete_transient() functions. With Redis Object Cache active they are automatically stored in Redis without extra configuration. No direct SQL queries or database bypass accesses are used, eliminating the incompatibility issue that affected Git Updater.


Publishing a new version

For every new version of a managed plugin:

  1. Update Version: in the header of the plugin's main PHP file
  2. Commit and push to the primary branch

Within 12 hours (or immediately by clicking "Check for updates now") WordPress will show the available-update notice. There is no need to create releases or tags.


Updating EG Forgejo Updater itself

This plugin manages itself thanks to the Forgejo Plugin URI header in its own main file. It updates exactly like the other plugins.


License

GPL-2.0-or-later