shlinkio/shlink
58.4
Adequate · 19 September 2026
17.6k
lines of production code
PHP
primary language
1
measurement over time
What this system is
This system is a self-hosted URL shortening service that manages short links, domains, and visit analytics through a REST API and a command-line interface. It supports dynamic, condition-based redirects based on visitor attributes like device type or geolocation, and integrates with external analytics platforms such as Matomo. The application is designed for high performance and scalability, utilizing persistent workers via RoadRunner or FrankenPHP, and provides granular access control through role-based API keys.
How it got here
2016–2019 — Laminas migration and modular architecture
60 changes.
The project underwent a comprehensive architectural overhaul, migrating from Zend to Laminas and restructuring the codebase into distinct Core, REST, and CLI modules. This period established a modernized configuration system, persistent worker runtimes via RoadRunner and FrankenPHP, and robust API key management with granular role-based permissions. Extensive test coverage was added to validate the new middleware, actions, and CLI commands, ensuring stability for the refactored core.
2020–2021 — Domain-specific redirects and API key scoping
49 changes.
This period focused on implementing granular, per-domain 404 redirect configurations and introducing role-based access control for API keys. The work involved restructuring core modules like Visits, Tags, and Domains to support these new features, alongside migrating the pagination system to Pagerfanta and enhancing event dispatching reliability.
2022–2023 — Core architecture modernization and real-time updates
50 changes.
This period focused on refactoring the Core module's data models and repositories to support database-level pagination, granular filtering, and efficient visit counting. It also introduced a unified real-time event system supporting Mercure, RabbitMQ, and Redis Pub/Sub, alongside new integrations for Matomo analytics and configurable domain redirects.
2024–2026 — Dynamic redirects and configuration modernization
32 changes.
This period focused on implementing a comprehensive rule-based redirection system, allowing short URLs to dynamically route visitors based on attributes like device, location, and query parameters. Concurrently, the project modernized its core infrastructure by migrating configuration to environment variables, adopting Valinor for object mapping, and optimizing visit counting and geolocation tracking mechanisms.
Features
API key management repository implementation
The ApiKeyRepository now provides specific methods to manage API keys by name, including creating an initial key idempotently (only if none exist), checking if a key name exists, and deleting keys by name. The implementation uses pessimistic locking to prevent race conditions during creation and existence checks, ensuring data integrity when managing the initial admin API key.
module/Rest/src/ApiKey/Repository · high confidence
API keys now support granular role-based access control
API keys can now be created with specific permissions (roles) rather than having global access. This change introduces a new \ApiKeyMeta\ model and \RoleDefinition\ class to define key metadata, including auto-generated names and expiration dates. Users can now restrict keys to specific capabilities, such as preventing visits to orphaned short URLs (\NO\_ORPHAN\_VISITS\) or limiting access to authored short URLs, providing finer-grained security and control over API usage.
module/Rest/src/ApiKey/Model · high confidence
Added Apache and Nginx virtual host configuration examples
New example configuration files for Apache (apache-vhost.conf) and Nginx (nginx-vhost.conf) have been added to the data/infra/examples directory. These files provide ready-to-use server block templates for deploying the application, featuring updated server names (s.test) and Nginx settings pointing to a PHP 8.4 FPM socket.
data/infra/examples · high confidence
Added Mercure event listeners for short URLs and visits
New event listeners have been added to publish short URL creation and visit events to Mercure. The \NotifyNewShortUrlToMercure\ and \NotifyVisitToMercure\ classes extend abstract base listeners to handle these specific event types, enabling real-time updates via the Mercure protocol for these actions.
module/Core/src/EventDispatcher/Mercure · high confidence
Added Redis Pub/Sub listeners for short URL and visit events
New event listeners (NotifyNewShortUrlToRedis and NotifyVisitToRedis) have been added to the Core module to publish real-time updates via Redis Pub/Sub. These listeners extend existing abstract base classes and integrate with the unified publishing API, allowing real-time update topics to be enabled or disabled individually via configuration options.
module/Core/src/EventDispatcher/RedisPubSub · high confidence
Core module introduces glob-based configuration loading
The new Core module now loads its configuration using a glob pattern via the \loadConfigFromGlob\ function from the \shlink-config\ package, allowing multiple configuration files to be loaded automatically from the module's config directory.
module/Core/src · high confidence
Initial CLI module configuration and command registration
The CLI module is now fully configured with a comprehensive set of commands for managing short URLs, visits, API keys, tags, domains, and database operations. This includes commands for creating, editing, listing, and deleting short URLs; locating and managing visits (including orphan visits and GeoLite2 database updates); generating and managing API keys; listing and managing tags; listing domains and managing redirects; and initializing or migrating the database. The configuration also registers dependencies for these commands using Laminas ServiceManager factories and maps specific services (like ShortUrl services, Visit helpers, and ApiKeyService) to their respective commands, enabling the CLI interface to function.
module/CLI/config · high confidence
Initial Docker image support with RoadRunner runtime
Added a new Docker image for Shlink that serves the application using RoadRunner. The entrypoint script initializes the application by creating necessary data directories, handling GeoLite2 database downloads based on the provided license key, and optionally creating an initial API key. It supports configuration via environment variables such as DEFAULT\_DOMAIN, IS\_HTTPS\_ENABLED, and GEOLITE\_LICENSE\_KEY, and ensures proper signal handling by executing the RoadRunner server via exec.
docker · high confidence
Initial project scaffolding and configuration files
The repository is initialized with essential configuration and documentation files, including a Dockerfile for PHP 8.5 Alpine, a build script, and a comprehensive changelog documenting versions up to 5.1.6. Development environment setup is defined via .gitignore, .dockerignore, and .gitattributes to exclude test artifacts and local configs, while DEVELOPING.md provides instructions for local setup using Docker and RoadRunner. The project also includes a Code of Conduct, Security policy, and Upgrade guide detailing breaking changes from previous major versions.
(repo-wide) · high confidence
Initial public entry point and URL rewriting configuration
The application now includes a standard Apache entry point in the public directory. The new index.php script serves as the single entry point, delegating execution to the run.php configuration script. Additionally, a .htaccess file has been added to handle URL rewriting, ensuring that existing files and directories are served directly while all other requests are routed to index.php, supporting both aliased and non-aliased hosting environments.
public · high confidence
Interactive CLI tool for managing short URL redirect rules
Users can now interactively add, remove, re-arrange, and save redirect rules for short URLs directly from the command line. The new RedirectRuleHandler provides a guided workflow to define rule conditions, supporting device type, language, query parameters (including any-value and valueless variants), IP address, geolocation (country code and city name), browser, and date-based conditions (before/after date).
module/CLI/src/RedirectRule · high confidence
Introduce model layer for dynamic redirect rules
Added new data models (RedirectConditionData, RedirectConditionType, RedirectRuleData, RedirectRulesData) to support configuring short URLs with dynamic redirect rules. The RedirectConditionType enum defines supported conditions including device, language, query parameters, IP address, geolocation (country/city), date ranges, and browser, with built-in validation for values like ISO country codes and IP CIDR ranges.
module/Core/src/RedirectRule/Model · high confidence
Introduced API key roles and metadata model
Added the ApiKey and ApiKeyRole entities to support granular permissions for API keys. The ApiKey entity now stores hashed keys, optional expiration dates, and a collection of roles, while the ApiKeyRole entity links specific roles to their metadata. This change enables the system to enforce role-based restrictions on short URLs, domains, and tags, moving beyond simple admin/non-admin distinctions.
module/Rest/src/Entity · high confidence
Introduces dedicated listeners for tracking short URL and orphan visit counts
Added two new Doctrine event listeners, ShortUrlVisitsCountTracker and OrphanVisitsCountTracker, to the visit processing pipeline. These listeners hook into the ORM's onFlush and postFlush lifecycle events to atomically increment visit counts in the short\_url\_visits\_counts and orphan\_visits\_counts database tables respectively. The implementation uses database-specific upsert syntax (ON DUPLICATE KEY UPDATE for MySQL, ON CONFLICT for PostgreSQL) or a locked select-then-insert/update pattern for SQLite and SQL Server to ensure accurate counting of both regular and orphan visits, including bot traffic differentiation.
module/Core/src/Visit/Listener · high confidence
Introduction of new domain events for short URL creation and visits
The system now introduces specific event classes—ShortUrlCreated, UrlVisited, and GeoLiteDbCreated—to signal key actions within the core module. These events are designed to be serializable and deserializable, allowing them to be dispatched asynchronously (e.g., as RoadRunner jobs) or persisted for later processing. This change enables external listeners to react to short URL creation, URL visits (including original IP address data), and GeoLite database updates, facilitating more granular tracking and integration with services like Matomo.
module/Core/src/EventDispatcher/Event · high confidence
New CLI command to read environment variable values
A new hidden CLI command, \env-var:read\, has been added to the Config module, allowing users to display the current value of a specified Shlink environment variable. The command accepts an environment variable name as an argument and uses an interactive prompt to help users select from the list of valid Shlink environment variables if none is provided, ensuring the input is valid before displaying the formatted value.
module/CLI/src/Command/Config · high confidence
New CLI commands for managing API keys
The CLI now includes dedicated commands to manage API keys: \api-key:generate\ creates new keys with optional names, expiration dates, and roles; \api-key:list\ displays keys with status and role details; \api-key:disable\ and \api-key:delete\ allow disabling or removing keys by name; \api-key:rename\ changes a key's name; and \api-key:initial\ creates the first key idempotently. These commands replace previous mechanisms and provide a consistent interface for API key lifecycle management.
module/CLI/src/Command/Api · high confidence
New CLI commands for managing and exporting visit data
The CLI now includes dedicated commands to manage short URL visits: \visit:orphan-delete\ removes all orphan visits, \visit:non-orphan\ and \visit:orphan\ list visits with support for domain and type filtering, and \visit:locate\ resolves IP geolocation for visits. Additionally, visit listing commands now support CSV export and paginated output to prevent memory exhaustion when handling large datasets.
module/CLI/src/Command/Visit · high confidence
New CLI commands for managing domain-specific 404 redirects and listing domains
Users can now configure custom "not found" redirects for individual domains using the new \domain:redirects\ command, which allows setting separate redirect URLs for the base URL, regular 404 errors, and invalid short URLs. Additionally, the \domain:list\ command has been added to display all configured domains, with an optional \-r\ flag to show their specific redirect configurations, and the \domain:visits\ command provides a way to retrieve visit statistics for a specific domain.
module/CLI/src/Command/Domain · high confidence
New CLI commands for managing tags and their visits
The CLI now includes dedicated commands for tag management: 'tag:list' displays tags with their short URL and visit counts, 'tag:delete' removes one or more tags, 'tag:rename' changes a tag's name with error handling for conflicts or missing tags, and 'tag:visits' lists visits for a specific tag, supporting domain filtering and pagination via the VisitsListInput.
module/CLI/src/Command/Tag · high confidence
New CLI utility classes for process execution and table rendering
The CLI module introduces new utility classes to improve command execution and output formatting. A new \ProcessRunnerInterface\ and its implementations (\ProcessRunner\ and \PhpProcessRunner\) standardize how background processes are executed, with \ProcessRunner\ handling verbose output formatting and a default timeout of 20 minutes, while \PhpProcessRunner\ ensures commands are prefixed with the current PHP binary. Additionally, a new \ShlinkTable\ class wraps Symfony's console table helper, adding support for styled headers/footers and optional row separators to enhance the readability of multi-line table outputs in the CLI.
module/CLI/src/Util · high confidence
New Domain entity with configurable 404 redirects
A new Domain entity has been introduced to represent a specific domain authority, allowing users to configure distinct redirect behaviors for base URL redirects, regular 404 errors, and invalid short URL errors. This entity implements the NotFoundRedirectConfigInterface, enabling granular control over how different types of not-found scenarios are handled for each domain.
module/Core/src/Domain/Entity · high confidence
New Matomo integration for tracking visits
Added a new integration that allows sending visit data to a Matomo analytics instance. This feature introduces configuration options (enabled, base URL, site ID, API token) loaded from environment variables, a tracker builder that configures the Matomo client with a 10-second timeout and POST requests, and a visit sender that transmits visit details including URL, user agent, referrer, location (using country codes), and IP address. The implementation includes logic to handle orphan visits and provides a result model to track successful and failed transmissions.
module/Core/src/Matomo · high confidence
New REST actions for managing tags
The REST API now exposes dedicated action classes for tag operations, enabling users to list tags with pagination and optional statistics, delete multiple tags, and rename tags via PUT requests. These changes introduce new endpoints (/tags for listing, deleting, and renaming; /tags/stats for statistics) that rely on the TagServiceInterface, improving the structure and security of tag management through API key validation.
module/Rest/src/Action/Tag · high confidence
New REST endpoints for listing domains and configuring redirects
This change introduces two new REST API actions in the Domain module. The GET /domains endpoint now lists existing domains and includes the system's default 404 redirect configuration in the response. Additionally, a new PATCH /domains/redirects endpoint allows users to configure or update the 404 redirects for a specific domain, utilizing Valinor for request validation and mapping.
module/Rest/src/Action/Domain · high confidence
New REST endpoints to manage short URL redirect rules
Users can now retrieve and configure redirect rules for specific short URLs via new REST API endpoints. The GET /short-urls/{shortCode}/redirect-rules endpoint returns the default long URL and associated redirect rules, while the POST/PATCH /short-urls/{shortCode}/redirect-rules endpoint allows setting these rules using Valinor for validation and mapping. These changes introduce new action classes (ListRedirectRulesAction and SetRedirectRulesAction) that interact with the core ShortUrlRedirectRuleServiceInterface.
module/CLI/src/Command/RedirectRule, module/Rest/src/Action/RedirectRule · high confidence
New adapter for paginated short URL listing
A new ShortUrlRepositoryAdapter has been introduced to handle pagination for short URL lists. This adapter implements Pagerfanta's AdapterInterface and delegates data retrieval to the ShortUrlListRepositoryInterface, passing through filtering parameters, API key context, and the default domain to ensure accurate result counts and slices.
module/Core/src/ShortUrl/Paginator · high confidence
New command to send existing visits to Matomo
A new CLI command, \integration:matomo:send-visits\, has been added to allow users to backfill their Matomo analytics by sending existing visits from Shlink to a configured Matomo instance. The command supports filtering by date range using \--since\ and \--until\ options, enabling users to send all historical visits or specific subsets while providing progress feedback and warnings about potential duplicates.
module/CLI/src/Command/Integration · high confidence
New command utility helpers for warnings and locking
Added CommandUtils and LockConfig classes to the CLI module to standardize how commands handle user confirmations and distributed locking. CommandUtils provides static methods to display warnings before execution and to manage lock acquisition, ensuring that commands do not run concurrently and properly release resources. LockConfig offers a structured way to define lock parameters such as name, blocking behavior, and time-to-live, allowing commands to be decoupled from abstract base classes and implemented as invokable commands with consistent locking behavior.
module/CLI/src/Command/Util · high confidence
New crawling helper for listing crawlable short codes
A new CrawlingHelper class and its interface have been added to the Core/Crawling module to provide a dedicated service for listing crawlable short codes. This helper delegates to a CrawlableShortCodesQueryInterface, allowing the system to efficiently retrieve short codes that are safe for web crawlers to access.
module/Core/src/Crawling · high confidence
New development environment configuration via PHP array
The application now supports loading development environment settings from a PHP array file (shlink\_dev\_env.php.dist) instead of relying solely on environment files. This new configuration file provides pre-configured defaults for local development, including settings for MySQL, MariaDB, PostgreSQL, and MSSQL databases, as well as integrations for Matomo, Mercure, RabbitMQ, and Redis. It also introduces a new MERCURE\_ENABLED environment variable to control the Mercure hub functionality, allowing developers to easily spin up a complete local development stack with FrankenPHP support.
config/params · high confidence
New helper services for short URL resolution, redirection, and title processing
This change introduces several new helper classes in the ShortUrl module to improve how short URLs are managed and redirected. ShortUrlRedirectionBuilder now handles redirect logic, ensuring query parameters are preserved and supporting extra paths. ShortUrlStringifier extracts the logic for converting short URLs to strings, respecting base paths and custom domains. ShortUrlTitleResolutionHelper automates fetching and parsing page titles from long URLs, handling character encoding properly. ShortCodeUniquenessHelper manages the generation of unique short codes. These helpers centralize and simplify the core short URL functionality.
module/Core/src/ShortUrl/Helper · high confidence
New middleware handles extra path segments and trailing slashes for short URLs
Two new middleware classes have been added to the short URL redirection pipeline. ExtraPathRedirectMiddleware now processes requests containing extra path segments after the short code, resolving the target short URL and appending the extra path to the redirect destination when the extraPathMode is set to APPEND; it also supports multi-segment slugs by recursively attempting to match longer slug prefixes if the initial resolution fails. TrimTrailingSlashMiddleware ensures that trailing slashes are stripped from the short code attribute when both multi-segment slugs and trailing slash handling are enabled, preventing matching issues.
module/Core/src/ShortUrl/Middleware · high confidence
New repositories for visit counting, deletion, and iteration
The Visit repository layer has been expanded with new specialized components to improve performance and manageability. A new OrphanVisitsCountRepository and ShortUrlVisitsCountRepository provide efficient counting of orphan and short-URL visits respectively, with the orphan counter respecting API key roles to restrict access. A new VisitDeleterRepository introduces the capability to delete visits, supporting both deletion of all visits for a specific short URL and the removal of all orphan visits. Additionally, a VisitIterationRepository has been added to allow memory-efficient iteration over large sets of visits (such as unlocated visits or visits within a date range) using a block-based approach.
module/Core/src/Visit/Repository · high confidence
New request model for domain redirect configuration
A new DomainRedirectsRequest class has been introduced to handle the input data for setting redirects on a specific domain. This model accepts a domain authority and optional redirect URLs for base URLs, regular 404 errors, and invalid short URLs, allowing users to configure these specific redirect behaviors via the REST API.
module/Rest/src/Action/Domain/Request · high confidence
New service for managing API key lifecycle
The API key management logic has been extracted into a dedicated ApiKeyService (and its interface), which now handles creating, checking, listing, renaming, disabling, and deleting API keys. This service introduces support for auto-generated unique names when creating keys, enforces uniqueness constraints on custom names, and allows keys to be managed by their human-readable name rather than just the raw key string.
module/Rest/src/Service · high confidence
New utility classes for batch processing, IP matching, and redirect response handling
Added several new utility classes in the Core module: DoctrineBatchHelper for managing database batch operations, IpAddressUtils for matching IP addresses against CIDR blocks and wildcard patterns, NoValue as a sentinel type for optional parameters, and RedirectResponseHelper/RedirectStatus for building HTTP redirect responses with configurable status codes (301, 302, 307, 308) and cache headers.
module/Core/src/Util · high confidence
REST module initialization with centralized route configuration
The new Rest module introduces a ConfigProvider that centralizes route configuration by applying a versioned prefix (/rest/v{version:1\|2\|3}) to all API endpoints while ensuring the health check endpoint is also accessible via an unversioned path (/rest/health). This change establishes the foundational routing structure for the REST API, supporting multi-segment slugs and versioned access patterns.
module/Rest/src · high confidence
Short URLs now support conditional redirect rules
Users can now define specific redirect rules for a short URL that override the default destination based on request conditions. The system evaluates these rules in priority order, returning the long URL of the first matching rule; if no rules match, the short URL falls back to its original default long URL. This allows for dynamic routing, such as directing mobile users to a different page than desktop users, without creating separate short URLs.
module/Core/src/RedirectRule · high confidence
Support for dynamic, condition-based redirects on short URLs
Short URLs can now be configured with a set of redirect rules that determine the destination long URL based on specific visitor attributes. This change introduces new entity models (RedirectCondition and ShortUrlRedirectRule) that allow administrators to define conditions such as query parameters, device type, browser, IP address, geolocation (country or city), language, and date ranges. When a visitor accesses a short URL, the system evaluates these rules in priority order and redirects to the long URL associated with the first matching rule, enabling highly targeted and dynamic redirection behavior.
module/Core/src/RedirectRule/Entity · high confidence
Support for importing short URLs, visits, and redirect rules from other Shlink instances
The system now allows importing short URLs, their associated visits, and redirect rules from other Shlink instances. This feature handles short-code uniqueness conflicts by prompting the user to generate a new code or skip the URL, ensures that only new or pending visits are imported to prevent duplicates, and supports importing orphan visits. It also includes error handling so that individual import failures do not stop the entire process, and fixes a Postgres-specific error when importing short URLs.
module/Core/src/Importer · high confidence
Architecture
Core module configuration migrated to Laminas and split into dedicated config files
The Core module's dependency injection and event wiring has been restructured to use Laminas ServiceManager factories and a new ConfigAbstractFactory pattern, replacing the previous Zend/annotation-based setup. Configuration is now split into three distinct files: dependencies.config.php (registering services like NotFoundRedirectHandler, ShortUrl services, and Visit trackers), entity-manager.config.php (defining ORM entity mappings), and event\_dispatcher.config.php (mapping events like UrlVisited and ShortUrlCreated to their listeners). This change also introduces support for sending visit data to Matomo, integrating Redis Pub/Sub for real-time updates, and adding delegators to close database connections after async tasks.
module/Core/config · high confidence
REST module configuration restructured into dedicated config files
The REST module's configuration has been reorganized into specific files (access-logs.config.php, auth.config.php, dependencies.config.php, entity-manager.config.php) to improve modularity. This change centralizes dependency injection definitions for all REST actions (such as ShortUrl, Visit, Tag, and Domain actions) and middleware using Laminas' ConfigAbstractFactory. It also explicitly configures authentication rules, defining which routes bypass API key checks (like HealthAction) and which allow query-string API keys (like SingleStepCreateShortUrlAction), while setting up access log filtering to ignore health check paths.
module/Rest/config · high confidence
Refactored short URL management into dedicated service classes
The short URL creation, listing, resolution, updating, deletion, and visit deletion logic has been extracted from monolithic classes into dedicated, readonly service classes (UrlShortener, ShortUrlListService, ShortUrlResolver, ShortUrlService, DeleteShortUrlService, and ShortUrlVisitsDeleter). This change improves code organization and separation of concerns within the Core module, making the short URL workflow easier to maintain and extend.
module/Core/src/ShortUrl · high confidence
Behavioural changes
API key role resolution logic and validation
The CLI module now uses a dedicated RoleResolver to determine API key permissions based on input flags. This resolver enforces a validation rule that prevents generating keys with domain-only roles if the specified domain matches the system's default domain, throwing an InvalidRoleConfigException in that case. It also handles the creation of roles for authored short URLs, specific domains, and the exclusion of orphan visits.
module/CLI/src/ApiKey · high confidence
API key roles are deprecated
The Role enum in the API key module has been marked as deprecated, signaling that the existing API key role system (including authored short URLs, domain-specific access, and orphan visit filtering) is no longer supported. This change reflects the removal of deprecated features and the migration of role-related logic, indicating that users should avoid relying on these specific role-based restrictions in new integrations.
module/Rest/src/ApiKey · high confidence
Add paginated output format for visits commands to prevent memory exhaustion
Visits listing commands now support a new 'paginated' output format that processes visits in 1000-visit chunks instead of loading the entire dataset into memory at once. This change introduces the VisitsListFormat enum and VisitsListInput class to manage date range filtering and format selection, allowing users to specify the 'paginated' format to avoid out-of-memory errors when dealing with large volumes of visit data.
module/CLI/src/Input · high confidence
Adopt Valinor for object mapping and validation
The system now uses the Valinor library to validate and map input data, replacing previous mechanisms. This introduces a new MapValidationMiddleware that converts Valinor mapping errors into standard ValidationExceptions, ensuring consistent error handling. A MapperBuilderWithCacheDelegatorFactory is added to optimize performance by caching mapping definitions in production environments. Additionally, an OrderingConverter is implemented to validate and map ordering parameters (field and direction) for list endpoints, ensuring that only allowed fields and valid sort directions (ASC/DESC) are accepted.
module/Core/src/ObjectMapper · high confidence
Application bootstrapping and configuration loading restructured
The application's initialization flow has been reorganized to use a centralized config aggregator (config/config.php) that explicitly loads module providers (Core, CLI, Rest, etc.) and applies post-processors for base paths, multi-segment slugs, and redirect methods. A new container.php entry point now handles environment variable promotion via the EnvVars enum, sets global PHP settings (memory limit, timezone, error reporting), and registers the service container. Dedicated config files (cli-app.php, cli-config.php, run.php) now clearly separate CLI application setup, Doctrine migrations configuration, and standard HTTP application execution, replacing the previous monolithic or scattered bootstrap logic.
config · high confidence
Async listeners now support granular real-time update topic configuration
The async notification system for short URLs and visits has been refactored to introduce an abstract base class structure, allowing individual real-time update topics (such as new short URLs, new visits, or orphan visits) to be enabled or disabled independently via configuration. This change ensures that notifications to remote systems like Mercure, RabbitMQ, or Redis pub/sub are only sent for the specific event types that the user has explicitly opted into, reducing unnecessary network traffic and allowing for more precise control over real-time data streams.
module/Core/src/EventDispatcher/Async · high confidence
Automated MSSQL ODBC driver installation in CI
The CI pipeline now includes a dedicated script to install the Microsoft ODBC driver 18 for SQL Server on Ubuntu 24.04. This change ensures the required database connectivity libraries are present for MSSQL tests, replacing manual or ad-hoc setup steps with a reproducible installation process.
data/infra/ci · high confidence
CLI application initialization is now handled by a dedicated factory
The creation of the Symfony Console Application has been moved into a new ApplicationFactory. This factory retrieves CLI configuration and application options from the container to instantiate the console application and register commands via a ContainerCommandLoader, centralizing the bootstrap logic for the CLI module.
module/CLI/src/Factory · high confidence
CLI module introduces centralized configuration loading
The CLI module now includes a ConfigProvider that abstracts configuration loading by invoking loadConfigFromGlob to merge configuration files matching the pattern {,\*.}config.php. This change establishes a standardized mechanism for the CLI module to load its configuration from multiple sources, replacing any previous ad-hoc configuration handling within this module.
module/CLI/src · high confidence
CLI short URL commands migrated to Symfony attributes and invokable pattern
The short URL management commands (create, edit, list, delete, resolve, and visits) have been rewritten to use Symfony Console attributes (e.g., \#\[AsCommand\], \#\[Option\], \#\[MapInput\]) and the invokable command pattern. This change introduces structured input validation via Valinor mappers, replaces the previous Zend/Laminas paginator with Pagerfanta, and standardizes command behavior across the CLI module. Users will experience consistent command signatures, improved input handling, and updated output formatting for short URL operations.
module/CLI/src/Command/ShortUrl · high confidence
Centralized async listener control and request ID propagation
The event dispatcher now uses a new EnabledListenerChecker to explicitly gate async job dispatching based on specific feature flags (RabbitMQ, Redis Pub/Sub, Mercure, Matomo, and GeoLite), ensuring that async tasks are only created when their corresponding integrations are enabled. Additionally, a new RequestIdProvider forwards the request ID from the sync process to async job processes, improving traceability across distributed operations.
module/Core/src/EventDispatcher/Helper · high confidence
Config post-processors reorganized and enhanced for routing flexibility
The configuration post-processing logic has been restructured into dedicated classes within the Core module. A new BasePathPrefixer automatically prepends the configured base path to route and middleware paths, ensuring correct URL generation when deployed behind proxies or in subdirectories. The ShortUrlMethodsProcessor now dynamically sets allowed HTTP methods on the redirect route based on the configured redirect status code, restricting legacy 301/302 redirects to GET only while allowing 307/308 redirects to use any method. Additionally, a MultiSegmentSlugProcessor enables support for multi-segment slugs when the corresponding environment variable is enabled, modifying route patterns to accept multiple path segments.
module/Core/src/Config/PostProcessor · high confidence
Configurable per-domain not-found redirects with placeholder support
The configuration layer now supports defining distinct redirect targets for not-found scenarios (invalid short URLs, regular 404s, and base URL misses) on a per-domain basis. The new \NotFoundRedirectResolver\ processes these redirects and replaces \{DOMAIN}\ and \{ORIGINAL\_PATH}\ placeholders in the target URLs with the corresponding values from the request, ensuring that dynamic paths and domains are correctly preserved in the final redirect location.
module/Core/src/Config · high confidence
Core configuration options migrated to environment variables
The application's configuration system has been refactored to load settings directly from environment variables via new Options classes in the Core module. This change introduces dedicated configuration objects for CORS (including credentials and origin handling), real-time update topics, redirect behavior (status codes, cache visibility, and extra path modes), URL shortening (schema, slug length, trailing slashes), tracking preferences (anonymization, IP/referrer/UA disabling), robots.txt rules, and RabbitMQ integration. Users can now control these behaviors entirely through environment variables, replacing previous configuration mechanisms.
module/Core/src/Config/Options · high confidence
Core exceptions now return RFC 7807 Problem Details
The exception classes in the Core module (such as ValidationException, ShortUrlNotFoundException, and TagNotFoundException) have been refactored to implement the ProblemDetailsExceptionInterface. This change means that API errors now return structured JSON responses conforming to RFC 7807, including standardized fields like 'type', 'title', 'status', and 'detail', rather than generic error messages. This provides clients with machine-readable error codes and consistent formatting for all Core-level failures.
module/Core/src/Exception, module/Rest/src/Exception · high confidence
Database schema refactoring for domains, geolocation, and redirect rules
The entity mappings in the Core module have been restructured to support new database tables and improved data modeling. A new \domains\ table has been added to manage custom domain configurations, including specific redirect URLs for regular 404s and invalid short URLs. Geolocation tracking is now persisted via a \geolocation\_db\_updates\ table that records the status, reason, and errors of database downloads. Redirect logic has been expanded with new \short\_url\_redirect\_rules\ and \redirect\_conditions\ tables, allowing short URLs to have multiple conditional redirects with specific match keys and values. Additionally, visit tracking has been enhanced with a \redirect\_url\ field on the \visits\ table to record the final destination of a visitor, and visit counts are now stored in dedicated \short\_url\_visits\_counts\ and \orphan\_visits\_counts\ tables for more efficient aggregation.
module/Core/config/entities-mappings · high confidence
Database schema updates for dynamic redirections, visit counting, and security hardening
This release introduces several database migrations to support new features and improve data integrity. A new rule-based redirection system is added via the short\_url\_redirect\_rules, redirect\_conditions, and their join table, replacing the legacy device\_long\_urls table which is migrated and dropped. Visit counting is optimized by introducing short\_url\_visits\_counts and orphan\_visits\_counts tables to store aggregated counts instead of raw row counts. Security is enhanced by hashing all API keys in the database using SHA256 and adding a redirect\_url column to the visits table to track final redirect destinations. Additionally, long URL columns are converted to TEXT type to support longer URLs, a long\_url\_hash column is added to short\_urls for efficient lookups, and a geolocation\_db\_updates table is created to track geolocation database download statuses. Several fixes address index ordering and transactional behavior issues specific to Microsoft SQL Server.
module/Core/migrations · high confidence
Database-level pagination for tags list
The tags listing now uses database-level pagination instead of loading all tags into memory. This change introduces new paginator adapters that fetch tags directly from the database with support for search filtering, ordering by name, and API key restrictions, improving performance and scalability for large tag collections.
module/Core/src/Tag/Paginator · high confidence
Domain list now includes configured not-found redirects
The domain listing response now includes specific not-found redirect configurations for each domain. A new \DomainItem\ model serializes domain data to include a \redirects\ field, allowing users to see the custom 404 handling rules configured per domain rather than just the domain authority and default status.
module/Core/src/Domain/Model · high confidence
Domain repository now filters results by API key permissions
The new DomainRepository implementation ensures that domain lists and lookups respect API key roles. When an API key is provided, the repository applies specifications to filter results: for DOMAIN\_SPECIFIC roles, it restricts domains to those explicitly allowed by the key; for AUTHORED\_SHORT\_URLS roles, it restricts domains to those associated with short URLs created by that key. This prevents unauthorized domains from appearing in lists or being resolved via API key authentication.
module/Core/src/Domain/Repository · high confidence
Domain service now supports per-domain not-found redirects and API key scoping
The new DomainService introduces the ability to configure specific not-found redirects for individual domains via the \configureNotFoundRedirects\ method, moving beyond the previous global default. Additionally, the \listDomains\ method now respects API key permissions, filtering results based on \DOMAIN\_SPECIFIC\ roles to ensure users only see domains they are authorized to manage.
module/Core/src/Domain · high confidence
Enhanced tag listing with visit statistics and flexible ordering
The tags list endpoint now returns detailed visit statistics (total visits, non-bot visits) alongside the tag name and short URL count. This change introduces new data models (TagInfo, OrderableField) and parameter handling (TagsParams, TagsListFiltering) to support ordering by various fields and to expose the new visit summary data in the API response.
module/Core/src/Tag/Model · high confidence
Enhanced visit tracking with type filtering, bot stats, and redirect URL capture
The visit tracking system now captures the redirect URL for every tracked visit, allowing users to see where visitors were sent. Visit statistics have been expanded to distinguish between total visits and non-bot visits for both regular and orphan visits. Additionally, orphan visits can now be filtered by their specific type (invalid short URL, base URL, or regular 404) via the API and CLI, providing more granular control over visit data analysis.
module/Core/src/Visit/Model · high confidence
Error pages now use custom inline styles instead of Bootstrap
The 404 and invalid short-code error templates have been replaced with new standalone HTML files that no longer depend on the Bootstrap framework. These pages now feature a lightweight, self-contained design using system fonts and inline CSS, removing the external dependency on league/plates and Bootstrap while maintaining a clean, centered layout for error messages.
module/Core/templates · high confidence
Extracted Matomo visit tracking into a dedicated service
The logic for sending visit data to Matomo has been extracted into a new \SendVisitToMatomo\ service. This service acts as an event listener that intercepts \UrlVisited\ events, checks if Matomo integration is enabled, and delegates the actual transmission to a \MatomoVisitSender\ while ensuring errors are logged without disrupting the main application flow.
module/Core/src/EventDispatcher/Matomo · high confidence
Extracted utility functions to global helpers and replaced short-code generator
The application now uses extracted global helper functions for common array operations (contains, some, every, map) and utility tasks like date parsing, locale normalization, and crawler detection, replacing previous inline or library-specific implementations. Additionally, the short code generation logic has been updated to use NanoID instead of the previous short-id library, supporting both strict (alphanumeric) and standard (lowercase alphanumeric) modes.
module/Core/functions · high confidence
Geolocation database updates are now tracked with retry logic and progress reporting
The geolocation database updater has been moved to the Core module and now tracks download attempts, allowing it to skip updates if a download is already in progress or if too many consecutive errors have occurred recently. It also reports progress during downloads and distinguishes between new database creation and updates to existing files.
module/Core/src/Geolocation · high confidence
Improved event dispatching reliability and real-time update capabilities
This change introduces a new listener that automatically closes and reopens database connections after asynchronous tasks, preventing connection leaks and ensuring the entity manager is in a clean state for subsequent operations. It also adds a new event listener that triggers geolocation for previously unlocated visits whenever the GeoLite2 database is updated or created, ensuring location data remains current. Additionally, the system now supports granular control over real-time update topics, allowing individual topics (such as new visits or short URL updates) to be enabled or disabled independently, and refactors the update generation logic into a dedicated service for better maintainability.
module/Core/src/EventDispatcher · high confidence
Introduction of NotFoundType model for categorizing 404 errors
A new NotFoundType class has been added to the error handler model layer to explicitly categorize not-found visits. This class analyzes the incoming HTTP request to determine if the 404 error corresponds to a base URL access, a regular 404, or an invalid short URL, providing a structured way to distinguish between these different types of missing resources.
module/Core/src/ErrorHandler/Model · high confidence
Migrated API key entity mappings to external configuration files
The entity mapping definitions for the ApiKey and ApiKeyRole classes have been moved from inline annotations to external PHP configuration files located in the entities-mappings directory. These new files define the database schema for the api\_keys and api\_key\_roles tables, including the one-to-many relationship between API keys and their associated roles, ensuring the metadata is now managed via a centralized configuration approach.
module/Rest/config/entities-mappings · high confidence
Mitigate excessive error logging for unconfigured Mercure
A new middleware, NotConfiguredMercureErrorHandler, has been added to the REST module to handle Mercure-related exceptions more gracefully. Previously, errors from clients not using Mercure triggered large error traces in the logs; this change intercepts those specific exceptions, logs a simple warning message instead, and returns a standard Problem Details response, reducing log noise for users who have not configured Mercure.
module/Rest/src/Middleware/Mercure · high confidence
Modernized visit tracking with new entities and enhanced data capture
The visit tracking system has been refactored to introduce dedicated entities for counting visits: \OrphanVisitsCount\ for unlinked visits and \ShortUrlVisitsCount\ for visits to specific short URLs, enabling more granular analytics. The core \Visit\ entity now captures the \redirectUrl\ where a visitor was sent and exposes the \visitedUrl\ in JSON serialization for all visit types, not just orphans. Additionally, the \VisitLocation\ entity has been modernized to standardize geolocation data storage, and the \Visit\ entity's date field is now immutable.
module/Core/src/Visit/Entity · high confidence
New CLI and persistent-worker entry points for Symfony Console, Doctrine, RoadRunner, and FrankenPHP
The application now uses dedicated scripts in the bin directory to bootstrap its runtime environments. The bin/cli script initializes the Symfony Console application for command-line interactions, while bin/doctrine provides a custom entry point for Doctrine ORM console tools. Additionally, bin/roadrunner-worker.php and bin/frankenphp-worker.php replace previous execution models with persistent workers that handle HTTP requests and, in RoadRunner's case, dispatch async event listeners as background jobs, including garbage collection and request ID forwarding.
bin · high confidence
New DTOs for short URL list and count filtering
The persistence layer now uses dedicated Data Transfer Objects, ShortUrlsCountFiltering and ShortUrlsListFiltering, to encapsulate query parameters for short URL operations. These classes standardize how filters such as search terms, tags, date ranges, and API key constraints are passed to the repository, ensuring consistent handling of filtering logic across the application.
module/Core/src/ShortUrl/Persistence · high confidence
New configuration structure for core application components
The application now uses a new set of global configuration files in config/autoload to manage core settings. This includes dedicated configs for the database connection (entity-manager.global.php) with support for encryption and specific driver options, caching and Redis integration (cache.global.php), and a centralized error handling setup using Problem Details (error-handler.global.php). Additionally, the middleware pipeline (middleware-pipeline.global.php) and routing (routes.config.php) are now explicitly defined, supporting features like IP geolocation, trailing slash handling, and multi-segment slugs. The installer configuration (installer.global.php) has also been updated to reflect the available setup options.
config/autoload · high confidence
New development infrastructure with FrankenPHP, RoadRunner, and PHP 8.5 support
The development environment has been restructured to include new Dockerfiles for FrankenPHP, PHP-FPM, and RoadRunner, all based on PHP 8.5 Alpine images. This change introduces RoadRunner as the application server for development, replacing previous setups, and adds a dedicated Nginx reverse proxy configuration for the Mercure hub. The infrastructure now standardizes on specific versions for native dependencies, including PDO SQLSRV 5.13 and MS ODBC 18, while ensuring consistent extension installation (such as xdebug, APCu, and zip) across all development containers.
data/infra · high confidence
New health check and Mercure info endpoints with improved reliability
The REST module now exposes two new endpoints: a /health endpoint that verifies database connectivity and returns a JSON status (pass/fail) along with the application version, and a /mercure-info endpoint that provides the Mercure hub URL and a valid JWT subscription token. The health check implementation has been hardened to handle database connection exceptions gracefully, preventing random 503 responses when the underlying connection is expired. Additionally, the Mercure info action now correctly formats the hub URL by appending the /.well-known/mercure path segment.
module/Rest/src/Action · high confidence
New model classes for pagination, device/browser detection, ordering, and renaming
The Core/Model layer introduces several new value objects to support updated functionality: AbstractInfinitePaginableListParams standardizes pagination parameters; Browser and DeviceType enums parse user agents (via phpuseragentparser) to identify browsers and devices for dynamic redirects; Ordering provides a structured way to handle list sorting fields and directions; Renaming encapsulates API key rename operations with validation; and BulkDeleteResult models the outcome of bulk deletion actions.
module/Core/src/Model · high confidence
New short URL request processing middleware components
The REST API now includes four new middleware classes in the ShortUrl namespace to handle request preparation and response formatting for short URL creation. CreateShortUrlContentNegotiationMiddleware allows clients to request plain text responses containing only the short URL via a format query parameter or Accept header. DropDefaultDomainFromRequestMiddleware automatically removes the default domain from request payloads to simplify downstream processing. OverrideDomainMiddleware enforces domain restrictions for API keys with domain-specific roles by injecting the authorized domain into the request. ShortUrlOptionsPayloadMiddleware populates the request body with default short URL configuration options (such as short code length and slug settings) from the application configuration before the request reaches the handler.
module/Rest/src/Middleware/ShortUrl · high confidence
Optimized tag listing with visit statistics and native query performance
The tag list view now displays detailed visit statistics, including total visits, non-bot visits, and the count of associated short URLs, by leveraging the new short\_url\_visits\_counts table. To ensure this data loads efficiently, the underlying repository has been rewritten to use native SQL sub-queries instead of the ORM, which prevents the entire dataset from being loaded into memory during pagination. This change also improves ordering reliability across different database engines, such as PostgreSQL, and respects API key restrictions to filter tags based on domain or authorship permissions.
module/Core/src/Tag/Repository · high confidence
Prevents API key creation with domain-only roles on the default domain
The CLI now validates API key configurations and throws an error if a user attempts to create an API key using a domain-specific role (DOMAIN\_SPECIFIC) on the default domain. This change ensures that domain-only roles are restricted to non-default domains, preventing invalid configurations that would otherwise be accepted.
module/CLI/src/Exception · high confidence
REST API middleware migrated to PSR-15 with Laminas and enhanced CORS handling
The REST module's middleware stack has been rewritten to implement the PSR-15 standard, replacing previous implementations with new classes for authentication, body parsing, and cross-origin resource sharing (CORS). Authentication is now handled by a dedicated middleware that validates API keys via headers or query parameters, while body parsing has been updated to robustly decode JSON payloads and throw specific exceptions for malformed data. CORS behavior has been refined to dynamically resolve allowed HTTP methods from route definitions and ensure OPTIONS requests return empty 204 responses. Additionally, the infrastructure has been migrated from Zend to Laminas, and an implicit options middleware factory has been added to standardize preflight response handling.
module/Rest/src/Middleware · high confidence
REST API visit endpoints restructured with new actions and pagination
The REST API for visits has been reorganized into specific action classes (ShortUrlVisitsAction, TagVisitsAction, DomainVisitsAction, OrphanVisitsAction, NonOrphanVisitsAction, GlobalVisitsAction) that share a common AbstractListVisitsAction base. This change introduces Pagerfanta-based pagination for listing visits across all scopes (short URLs, tags, domains, orphan/non-orphan) and adds a new DELETE endpoint (/visits/orphan) to remove orphan visits. The GlobalVisitsAction now returns aggregated stats rather than a paginated list, and all list endpoints now support domain filtering where applicable.
module/Rest/src/Action/Visit · high confidence
RabbitMQ listeners now respect individual real-time updates configuration
The \NotifyNewShortUrlToRabbitMq\ and \NotifyVisitToRabbitMq\ listeners have been refactored to explicitly check the \RabbitMqOptions::enabled\ flag via the new \isEnabled()\ method. This change ensures that RabbitMQ publishing for short URL creation and visit tracking events is individually controllable through the real-time updates configuration, rather than being implicitly enabled or disabled by broader system settings.
module/Core/src/EventDispatcher/RabbitMq · high confidence
Redis ACL configuration for credentials
Added a Redis ACL configuration file (redis-acl.conf) that defines a user 'foo' with access to all commands and keys, enabled with the password 'bar', and sets a requirepass directive.
data/infra/redis · high confidence
Refactor CLI short URL input handling with new input classes
The CLI short URL commands now use dedicated input classes (ShortUrlCreationInput, ShortUrlDataInput, ShortUrlsParamsInput) to handle argument parsing and mapping. This change introduces Symfony Console attributes for defining options and arguments, replacing previous manual parsing logic. Users will see no functional change in command behavior, but the underlying structure for processing short URL creation, editing, and listing parameters has been standardized.
module/CLI/src/Command/ShortUrl/Input · high confidence
Refactored 404 handling into domain-specific redirect and tracking middlewares
The 404 error handling logic has been split into distinct middlewares to support per-domain redirect configurations and improved visit tracking. The new NotFoundRedirectHandler now attempts to resolve redirects specific to the current domain before falling back to default settings, while the NotFoundTrackerMiddleware ensures that all not-found visits (including those resulting in redirects) are properly recorded in the analytics.
module/Core/src/ErrorHandler · high confidence
Refactored REST short URL actions to use Valinor DTOs and Laminas
The REST actions for managing short URLs (create, edit, list, resolve, delete, and single-step create) have been refactored to use Valinor for validating and mapping request payloads into domain DTOs (ShortUrlCreation, ShortUrlEdition, ShortUrlsParams) instead of manual parsing. The actions now rely on Laminas Diactoros for HTTP responses and explicitly pass the API key from the authentication middleware to the underlying services. Additionally, the edit action now uses the PATCH method, and the single-step creation endpoint supports an optional domain override.
module/Rest/src/Action/ShortUrl · high confidence
Refactored database initialization and migration commands
The database setup workflow has been restructured to improve reliability and separation of concerns. The \db:create\ command now explicitly checks for existing tables before attempting creation, and automatically creates the underlying database if it is missing, preventing errors when the database does not yet exist. The \db:migrate\ command remains available for updating the schema. Both commands are now hidden internal utilities that utilize a locking mechanism to prevent concurrent execution, and they rely on a dedicated connection without a database name for initial setup tasks.
module/CLI/src/Command/Db · high confidence
Refactored short URL models to use readonly classes and new validation enums
The short URL models in the Core module have been refactored to use PHP readonly classes (ShortUrlCreation, ShortUrlEdition, ShortUrlIdentifier, ShortUrlWithDeps, ShortUrlsParams, UrlShorteningResult, ExpiredShortUrlsConditions) and new enums (ShortUrlMode, OrderableField, TagsMode). This change introduces a strict/loose mode for short URLs, allows ordering the short URL list by non-bot visits, and adds filtering capabilities by domain authority and API key name. The models now handle validation and mapping internally using converters, replacing previous external validation logic.
module/Core/src/ShortUrl/Model · high confidence
Refactored short URL repository logic and added expired URL deletion support
The short URL repository layer has been restructured to improve performance and maintainability. A new \ShortUrlListRepository\ now handles listing and filtering short URLs, utilizing sub-queries to load visit counts and avoiding N+1 database problems by joining domains. A new \ExpiredShortUrlsRepository\ has been introduced to support the deletion of expired short URLs based on conditions like past validity dates or reached visit limits. Additionally, the \ShortUrlRepository\ now includes logic to search by long URL hash when checking for existing short URLs, and the \CrawlableShortCodesQuery\ extracts crawlable short codes into a dedicated, memory-efficient iterator.
module/Core/src/ShortUrl/Repository · high confidence
Refactored tracking actions and introduced dynamic robots.txt generation
The core tracking logic has been restructured by introducing an AbstractTrackingAction that centralizes short URL resolution and request tracking, with specific implementations for RedirectAction (handling HTTP redirects) and PixelAction (returning a tracking pixel). Additionally, a new RobotsAction has been added to dynamically generate the robots.txt file, allowing users to configure whether all short URLs are crawlable and to customize user agents via the RobotsOptions.
module/Core/src/Action · high confidence
Refactored visit geolocation logic into dedicated service classes
The geolocation logic for visits has been reorganized into the new Visit\\Geolocation namespace, introducing specific classes to handle different aspects of the process. VisitLocator now manages the iteration and persistence of visit locations, while VisitToLocationHelper is responsible for resolving IP addresses to geographic locations, including explicit handling for localhost and empty addresses. This separation isolates the geolocation concerns from the broader visit management code.
module/Core/src/Visit/Geolocation · high confidence
Refactored visit tracking and listing into a modular Visit namespace
Visit tracking and listing logic has been reorganized into the new Shlinkio\\Shlink\\Core\\Visit namespace to improve code structure and maintainability. This change introduces dedicated services for tracking requests (RequestTracker) and managing visit deletion (VisitsDeleter), alongside a centralized helper (VisitsStatsHelper) that coordinates pagination adapters for short URLs, tags, domains, and orphan visits. The refactoring also standardizes filtering and persistence models (e.g., VisitsCountFiltering, OrphanVisitsListFiltering) and ensures that visit persistence is wrapped in database transactions for atomicity.
module/Core/src/Visit · high confidence
Replaced Laminas Paginator with Pagerfanta
The pagination system has been migrated from Laminas Paginator to Pagerfanta. This change introduces a new abstract adapter class, AbstractCacheableCountPaginatorAdapter, which implements the Pagerfanta AdapterInterface and includes internal caching for result counts to optimize performance during pagination operations.
module/Core/src/Paginator · high confidence
Repository interface exposes count method
The EntityRepositoryInterface now includes a count method, allowing callers to retrieve the number of entities matching specific criteria without needing to fetch the full result set. This change standardizes the repository contract to align with Doctrine's ObjectRepository capabilities, ensuring consistent counting behavior across implementations.
module/Core/src/Repository · high confidence
ShortUrl entity refactored with calculated hash, mode support, and import tracking
The ShortUrl entity has been significantly modernized: it now includes a calculated and indexed \longUrlHash\ for efficient lookups, supports distinct short URL modes (including path prefix handling for custom slugs), and tracks import source and original short code for migrated URLs. The entity also exposes redirect rules and utilizes a pre-calculated visits count collection to optimize listing performance, while removing legacy device-specific long URL support and enforcing stricter validation during creation and edition.
module/Core/src/ShortUrl/Entity · high confidence
Standardized short URL data transformation via new transformer service
The system now uses a dedicated ShortUrlDataTransformer to convert ShortUrl entities and ShortUrlWithDeps models into a consistent array format for API responses. This change ensures that the short URL string is explicitly included in the output alongside the entity's data properties, providing a uniform structure for consumers of the short URL data.
module/Core/src/ShortUrl/Transformer · high confidence
Tag management now supports renaming, pagination, and API key restrictions
The tag module has been refactored to introduce a dedicated TagService and Tag entity, enabling new capabilities and stricter security. Users can now rename tags via a dedicated endpoint, with the system preventing conflicts if the new name already exists. Tag listing endpoints now support pagination for better performance with large datasets. Additionally, API keys restricted to short-url operations are now explicitly blocked from deleting or renaming tags, enforcing role-based access control for these destructive actions.
module/Core/src/Tag · high confidence
Track geolocation database update status and reasons
The system now records the reason for each geolocation database download attempt and tracks its outcome (in-progress, success, or error) via new entity classes. This allows users to understand why a download was triggered and whether it completed successfully or failed, improving visibility into geolocation data maintenance.
module/Core/src/Geolocation/Entity · high confidence
Visitor geolocation now handled via middleware
The system now uses the new IpGeolocationMiddleware to determine visitor location instead of relying on the previous LocateVisit event. This middleware checks if geolocation is enabled and if the GeoLite2 database is available before resolving the IP; it also ensures that localhost requests are skipped and do not trigger geolocation attempts.
module/Core/src/Geolocation/Middleware · high confidence
Fixes
Prevents duplicate tag and domain creation errors during short URL creation
The short URL creation process now uses a new resolver mechanism that prevents race conditions when creating multiple short URLs simultaneously with the same new tags or domains. By implementing locking mechanisms for tag and domain persistence, the system ensures that duplicate entries are not created in the database, fixing issues where concurrent requests would fail due to unique constraint violations.
module/Core/src/ShortUrl/Resolver · high confidence
Test coverage
Added API test fixtures for comprehensive REST testing; Added API tests for REST middleware behavior; Added API tests for device-specific redirects and robots.txt behavior; Added CLI end-to-end tests for short URL and API key commands; Added database tests for DomainRepository API key permissions; Added database tests for TagsPaginatorAdapter; Added database tests for short URL repository operations; Added database tests for the API key repository; Added database tests for visit count tracking listeners; Added database tests for visit deletion and iteration repositories; Added integration tests for MatomoSendVisitsCommand; Added shell scripts to standardize API and CLI test execution; Added specification classes for short URL filtering by API key and domain; Added specification for counting tags by name; Added test coverage for API key short-URL restriction logic; Added test specification for domain filtering; Added tests for CLI ApplicationFactory; Added tests for Core ConfigProvider and test environment credentials; Added tests for Core EventDispatcher components; Added tests for EnabledListenerChecker and RequestIdProvider; Added tests for ObjectMapper validation middleware and cache delegator factory; Added tests for ReadEnvVarCommand; Added tests for Rest module ConfigProvider; Added tests for ShortUrlDataTransformer metadata and tag handling; Added tests for Tag repository filtering and statistics; Added tests for config post-processors; Added tests for custom slug handling in short URL creation; Added tests for geolocation and role configuration exceptions; Added tests for short URL relation resolvers; Added tests for the NotConfiguredMercureErrorHandler middleware; Added tests for the SendVisitToMatomo event listener; Added tests for visit geolocation logic; Added unit tests for API key management CLI commands; Added unit tests for API key role specifications and metadata handling; Added unit tests for ApiKeyMeta and RoleDefinition models; Added unit tests for ApiKeyService; Added unit tests for CORS and Real-Time Updates configuration options; Added unit tests for Core exception classes; Added unit tests for Core utility classes; Added unit tests for CrawlingHelper; Added unit tests for DomainService operations; Added unit tests for GeolocationDbUpdater; Added unit tests for Health and MercureInfo REST actions; Added unit tests for ImportedLinksProcessor; Added unit tests for ManageRedirectRulesCommand; Added unit tests for Matomo integration components; Added unit tests for Mercure event listeners; Added unit tests for REST middleware components; Added unit tests for REST redirect rule actions; Added unit tests for REST short URL actions; Added unit tests for REST visit actions; Added unit tests for RedirectCondition and ShortUrlRedirectRule entities; Added unit tests for RedirectConditionType validation; Added unit tests for Redis Pub/Sub event listeners; Added unit tests for ShortUrl entity behavior; Added unit tests for ShortUrlCreated and UrlVisited events; Added unit tests for ShortUrlRepositoryAdapter; Added unit tests for Tag entity and TagService; Added unit tests for Visit CLI commands; Added unit tests for Visit and VisitLocation entities; Added unit tests for configuration and redirect resolution components; Added unit tests for core ShortUrl services; Added unit tests for core tracking and crawling actions; Added unit tests for database CLI commands; Added unit tests for domain redirect and listing actions; Added unit tests for domain-related CLI commands; Added unit tests for not-found error handling middleware; Added unit tests for short URL CLI commands; Added unit tests for short URL REST middleware; Added unit tests for short URL helper services; Added unit tests for short URL redirect middleware behaviors; Added unit tests for short URL redirect rule management and resolution; Added unit tests for tag management CLI commands; Added unit tests for tag management REST actions; Added unit tests for the API key role resolver; Added unit tests for the CLI RedirectRuleHandler; Added unit tests for the IP geolocation middleware; Added unit tests for the Visitor model; Added unit tests for the enumValues helper function; Added unit tests for visit paginator adapters; Added unit tests for visit tracking and deletion services; Expanded API test coverage for short URL and visit management; New centralized test configuration and bootstrap files; New test utility classes for API testing.
Dependencies
Updated project dependencies to latest stable versions
The project's composer.json has been updated to require PHP 8.4 and upgraded several key dependencies to their latest major versions, including PHPUnit 13, Symfony components (Console, Filesystem, Lock, Process, String) version 8.1, Doctrine DBAL 4.4, and the shlink ecosystem packages (shlink-common 9.0.1, shlink-installer 10.1, shlink-importer 5.8). This ensures compatibility with modern PHP features and incorporates the latest bug fixes and performance improvements from these libraries.
(dependencies) · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Baseline
- First survey — no prior run to compare against. CAI 58.
Lenses
- Code Health 98
- Architecture 84
- Maturity 71
- Readiness 48
- Security 65
- Domain Modelling 100
- Accessibility 60
Changes since last survey
- 300 commits — 270 feature/other, 30 fixes
By area
- (repo) — 82 commits
- (root) — 63 commits
- module/CLI — 61 commits
- module/Core — 55 commits
- module/Rest — 14 commits
- .github/workflows — 9 commits
- data/infra — 7 commits
- bin/test — 2 commits
- docs/swagger — 2 commits
- .github/actions — 1 commit
- config/autoload — 1 commit
- config/test — 1 commit
- data/migrations_template.txt — 1 commit
- docs/adr — 1 commit
Notable commits
- fix: Apply fixes for PHPUnit 13
- fix: Attempting to fix migration in MS SQL
- fix: Fix ListShortUrls CLI test
- fix: Fix MS SQL ODBC links in docker images
- fix: Fix PHPStan error
- fix: Fix clearing entity cache on startup silently failing when using redis/valkey
- fix: Fix codecov/codecov-action arguments for v5
- fix: Fix error when setting max results in a delete query
- fix: Fix error when trying to persist non-utf-8 title
- fix: Fix issue reported by phpstan in CrossDomainMiddleware
- fix: Fix long URL edition via CLI
- fix: Fix merge conflicts
- fix: Fix migrations running when updating to Shlink 5.1.x while using Microsoft SQL database
- fix: Fix missing static check
- fix: Fix notices reported by latest PHPUnit version
- fix: Fix pagination in short-url:list command
- fix: Fix static analysis
- fix: Fix static analysis after update to doctrine/orm 3.7
- fix: Fix type definition of ShortUrlsParams::itemsPerPage
- fix: Fix weird alignment of comment after autoformatting
- …and 280 more
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
shlinkio/shlink was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 19 September 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit c303aff18f8108ca1e17ec5c697a63afd8efd4fe — the exact code this score is about.
- Scored under rubric-2026.09.15 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer preprod-13a154b7f5d1.