Instructions for Claude Code working on the YOURLS codebase.
YOURLS (Your Own URL Shortener) is a self-hosted, open-source PHP URL shortener. Primary target: shared hosting (no root/sudo), MySQL 5.5+, PHP 8.1+. WordPress-inspired architecture: hook/filter system, options table, plugin API.
- Never commit.
- All functions are prefixed
yourls_+ snake_case description, e.g.yourls_do_this_task. - Use docblocks (
@param/@return) for functions, classes, and complex logic. Comment only when not self-explanatory. - Edit only code relevant to the task. No formatting/whitespace-only changes.
- Don't break shared-hosting compatibility (no root, MySQL 5.5+).
- Full function/hook/filter reference: see
database-and-function-reference.md.
yourls-loader.php- front-facing redirect engine (resolves short URLs)yourls-api.php- REST API endpointadmin/- admin interface (requires auth);admin/admin-ajax.php- AJAX handleryourls-go.php- redirect handler (GO mode)yourls-infos.php- stats page for a short URL (append+)
yourls-loader.php -> includes/load-yourls.php -> YOURLS\Config\Config (finds/loads config) -> YOURLS\Config\Init (orchestrates startup) -> procedural core.
YOURLS\Config\InitDefaults defines a boolean flag per startup step, so tests can skip steps (DB connect, plugin load, ...). See tests/bootstrap.php.
Thin OOP layer under includes/ (PSR-4 as YOURLS\) + large procedural core:
includes/functions-*.php- procedural functions, allyourls_-prefixedincludes/Config/- Config, Init, InitDefaults (bootstrap)includes/Database/- YDB, Options, Logger, Profilerincludes/Views/AdminParams.php- admin view helpersadmin/- admin UI pagesuser/- user config + pluginsincludes/vendor/- vendored deps (Aura\Sql, etc.)
YDB extends Aura\Sql ExtendedPdo: the global $ydb god-object (DB, options, context, installed state).
WordPress-style, central to everything. Full hook list: database-and-function-reference.md.
- Filters (modify + return a value):
yourls_add_filter,yourls_apply_filter,yourls_has_filter,yourls_remove_filter. - Actions (side effects):
yourls_add_action,yourls_do_action,yourls_has_action,yourls_did_action(returns a count). - Signature:
yourls_add_filter('hook', 'callback', $priority = 10, $accepted_args = 1); apply withyourls_apply_filter('hook', $value, $extra_arg1, ...).
Many core functions check a shunt_<functionname> filter first, letting plugins bypass core logic and return their own value. Sentinel is yourls_shunt_default(); if the filter returns anything else, the function short-circuits.
$pre = yourls_apply_filter('shunt_add_new_link', false, $url, $keyword, $title);
if (false !== $pre) {
return $pre;
}
// ... normal logicPlugins live in user/plugins/. The plugins_loaded action fires once all plugins are loaded.
YOURLS\Database\YDB extends Aura.SQL ExtendedPdo; all queries go through it.
yourls_get_db()returns the YDB singleton.- Query methods: standard PDO + Aura.SQL
fetchAll(),fetchOne(),fetchCol(),fetchPairs(),fetchValue(),fetchAffected().
Three tables (accessed via constants):
YOURLS_DB_TABLE_URL - keyword->URL mappings
- PK
keywordvarchar(100);urltext,titletext,timestamptimestamp,ipvarchar(41),clicksint(10) - Indexes:
ip,timestamp,url_idx(url prefix 30)
YOURLS_DB_TABLE_OPTIONS - key/value settings
- PK
option_idbigint auto_increment;option_namevarchar(64),option_valuelongtext - Index:
option_name
YOURLS_DB_TABLE_LOG - clicks + referrers
- PK
click_idint auto_increment;click_timedatetime,shorturlvarchar(100),referrervarchar(200),user_agentvarchar(255),ip_addressvarchar(41),country_codechar(2) - Index:
shorturl
User-defined in user/config.php: YOURLS_DB_USER/PASS/NAME/HOST/PREFIX, YOURLS_SITE (base URL), YOURLS_COOKIEKEY (cookie hashing secret), YOURLS_URL_CONVERT (36 or 62), YOURLS_PRIVATE, YOURLS_UNIQUE_URLS, YOURLS_DEBUG.
Core: YOURLS_ABSPATH, YOURLS_INC, YOURLS_USERDIR/YOURLS_USERURL, YOURLS_PLUGINDIR/YOURLS_PLUGINURL, YOURLS_VERSION, YOURLS_DB_VERSION.
Tests run in Docker against a real DB (no mocking):
docker exec -it dev-web bash --login
cd /var/www/html/ozh.in
phpunit # all
phpunit tests/tests/format/FormatTest.php # one file
phpunit --group formatting # one group- Opening brace on same line:
function foo(string $x, int $y) : bool { - Comments in English; docblocks (
@param/@return) for classes/functions - End multi-line scripts with a blank line