All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
#941 Added support to provide a title for every short URL.
The title can also be automatically resolved from the long URL, when no title was explicitly provided, but this option needs to be opted in.
#913 Added support to import short URLs from a standard CSV file.
The file requires the Long URL
and Short code
columns, and it also accepts the optional title
, domain
and tags
columns.
#1000 Added support to provide a margin
query param when generating some URL's QR code.
#675 Added ability to track visits to the base URL, invalid short URLs or any other "not found" URL, as known as orphan visits.
This behavior is enabled by default, but you can opt out via env vars or config options.
This new orphan visits can be consumed in these ways:
https://shlink.io/new-orphan-visit
mercure topic, which gets notified when an orphan visit occurs.GET /visits/orphan
REST endpoint, which behaves like the short URL visits and tags visits endpoints, but returns only orphan visits.laminas/laminas-paginator
to pagerfanta/core
to handle pagination.#874 Changed how dist files are generated. Now there will be two for every supported PHP version, with and without support for swoole.
The dist files will have been built under the same PHP version they are meant to be run under, ensuring resolved dependencies are the proper ones.
#959 Deprecated all command flags using camelCase format (like --expirationDate
), adding kebab-case replacements for all of them (like --expiration-date
).
All the existing camelCase flags will continue working for now, but will be removed in Shlink 3.0.0
#862 Deprecated the endpoint to edit tags for a short URL (PUT /short-urls/{shortCode}/tags
).
The short URL edition endpoint (PATCH /short-urls/{shortCode}
) now supports setting the tags too. Use it instead.
buildEpoch
is parsed as string instead of int.itemsPerPage
query param to swagger docs for short RULs list.Access-Control-Allow-Origin
, that could not work as expected when including an IP address.Access-Control-Allow-Methods
header, which always contained all methods.#795 and #882 Added new roles system to API keys.
API keys can have any combinations of these two roles now, allowing to limit their interactions:
#833 Added support to connect through unix socket when using an external MySQL, MariaDB or Postgres database.
It can be provided during the installation, or as the DB_UNIX_SOCKET
env var for the docker image.
#869 Added support for Mercure Hub 0.10.
#896 Added support for unicode characters in custom slugs.
#930 Added new bin/set-option
script that allows changing individual configuration options on existing shlink instances.
#877 Improved API tests on CORS, and "refined" middleware handling it.
league/plates
package.mezzio/mezzio-swoole
v3.1./{shortCode}/qr-code/{size}
URL, in favor of providing the size in the query instead, /{shortCode}/qr-code?size={size}
.?format=svg
to the QR code URL.#820 Added new option to force enabling or disabling URL validation on a per-URL basis.
Currently, there's a global config that tells if long URLs should be validated (by ensuring they are publicly accessible and return a 2xx status). However, this is either always applied or never applied.
Now, it is possible to enforce validation or enforce disabling validation when a new short URL is created or edited:
POST /short-url
and PATCH /short-url/{shortCode}
endpoints, you can now pass validateUrl: true/false
in order to enforce enabling or disabling validation, ignoring the global config. If the value is not provided, the global config is still normally applied.short-url:generate
CLI command, you can pass --validate-url
or --no-validate-url
flags, in order to enforce enabling or disabling validation. If none of them is provided, the global config is still normally applied.#838 Added new endpoint and CLI command to list existing domains.
It returns both default domain and specific domains that were used for some short URLs.
GET /rest/v2/domains
domain:list
#832 Added support to customize the port in which the docker image listens by using the PORT
env var or the port
config option.
#860 Added support to import links from bit.ly.
Run the command short-urls:import bitly
and introduce requested information in order to import all your links.
Other sources will be supported in future releases.
<field>-<dir>
notation while determining how to order the short URLs list, as in ?orderBy=shortCode-DESC
. This effectively deprecates the array notation (?orderBy[shortCode]=DESC
), that will be removed in Shlink 3.0.0POST /tags
endpoint and tag:create
command, as tags are created automatically while creating short URLs.findIfExists = true
.gmp
extension to the official docker image.#746 Allowed to configure the kind of redirect you want to use for your short URLs. You can either set:
302
redirects: Default behavior. Visitors always hit the server.301
redirects: Better for SEO. Visitors hit the server the first time and then cache the redirect.When selecting 301 redirects, you can also configure the time redirects are cached, to mitigate deviations in stats.
#734 Added support to redirect to deeplinks and other links with schemas different from http
and https
.
#709 Added multi-architecture builds for the docker image.
#707 Added --all
flag to short-urls:list
command, which will print all existing URLs in one go, with no pagination.
It has one limitation, though. Because of the way the CLI tooling works, all rows in the table must be loaded in memory. If the amount of URLs is too high, the command may fail due to too much memory usage.
.
, _
or ~
.validSince
and/or validUntil
, but you are providing either one of them for the new one.#712 Added support to integrate Shlink with a mercure hub server.
Thanks to that, Shlink will be able to publish events that can be consumed in real time.
For now, two topics (events) are published, when new visits occur. Both include a payload with the visit and the shortUrl:
* A visit occurs on any short URL: `https://shlink.io/new-visit`.
* A visit occurs on short URLs with a specific short code: `https://shlink.io/new-visit/{shortCode}`.
The updates are only published when serving Shlink with swoole.
Also, Shlink exposes a new endpoint GET /rest/v2/mercure-info
, which returns the public URL of the mercure hub, and a valid JWT that can be used to subscribe to updates.
#673 Added new [GET /visits]
rest endpoint which returns basic visits stats.
#674 Added new [GET /tags/{tag}/visits]
rest endpoint which returns visits by tag.
It works in the same way as the [GET /short-urls/{shortCode}/visits]
one, returning the same response payload, and supporting the same query params, but the response is the list of visits in all short URLs which have provided tag.
#672 Enhanced [GET /tags]
rest endpoint so that it is possible to get basic stats info for every tag.
Now, if the withStats=true
query param is provided, the response payload will include a new stats
property which is a list with the amount of short URLs and visits for every tag.
Also, the tag:list
CLI command has been changed and it always behaves like this.
#640 Allowed to optionally disable visitors' IP address anonymization. This will make Shlink no longer be GDPR-compliant, but it's OK if you only plan to share your URLs in countries without this regulation.
/health
endpoint returning 503
fail responses when the database connection has expired.HEAD
requests returning a duplicated Content-Length
header..htaccess
file that was unintentionally removed in v2.1.0, making Shlink unusable with Apache.X-Request-Id
header, can be provided from outside and is set in log entries.PATCH /short-urls/{shortCode}
endpoint.#641 Added two new flags to the visit:locate
command, --retry
and --all
.
--retry
is provided, it will try to re-locate visits which IP address was originally considered not found, in case it was a temporal issue.--all
is provided together with --retry
, it will try to re-locate all existing visits. A warning and confirmation are displayed, as this can have side effects.base_url_redirect_to
simplified config option not being properly parsed.db
commands not running in a non-interactive way.GET /short-urls/{shortCode}
REST endpoint returning a 404 for short URLs which are not enabled.OPTIONS
requests including the Origin
header not always returning an empty body with status 2xx.zendframework
components to the new laminas
and mezzio
ones.#429 Dropped support for PHP 7.2 and 7.3
#229 Remove everything which was deprecated, including:
See UPGRADE doc in order to get details on how to migrate to this version.
#118 API errors now implement the problem details standard.
In order to make it backwards compatible, two things have been done:
error
and message
properties have been kept on error response, containing the same values as the type
and detail
properties respectively.v2
has been enabled. If an error occurs when calling the API with this version, the error
and message
properties will not be returned.After Shlink v2 is released, both API versions will behave like API v2.
#575 Added support to filter short URL lists by date ranges.
GET /short-urls
endpoint now accepts the startDate
and endDate
query params.short-urls:list
command now allows --startDate
and --endDate
flags to be optionally provided.#338 Added support to asynchronously notify external services via webhook, only when shlink is served with swoole.
Configured webhooks will receive a POST request every time a URL receives a visit, including information about the short URL and the visit.
The payload will look like this:
{
"shortUrl": {},
"visit": {}
}
The
shortUrl
andvisit
props have the same shape as it is defined in the API spec.
develop
branch.PHP Fatal error: Uncaught Error: Class 'Shlinkio\Shlink\LocalLockFactory' not found
happening when running some CLI commands.db:migrate
command failing because yaml extension is not installed, which makes config file not to be readable.db:create
and db:migrate
commands do not silently fail when run as part of install
or update
.#491 Added improved short code generation logic.
Now, short codes are truly random, which removes the guessability factor existing in previous versions.
Generated short codes have 5 characters, and shlink makes sure they keep unique, while making it backwards-compatible.
#418 and #419 Added support to redirect any 404 error to a custom URL.
It was already possible to configure this but only for invalid short URLs. Shlink now also support configuring redirects for the base URL and any other kind of "not found" error.
The three URLs can be different, and it is already possible to pass them to the docker image via configuration or env vars.
The installer also asks for these two new configuration options.
#497 Officially added support for MariaDB.
#482 Added support to serve shlink under a sub path.
The router.base_path
config option can be defined now to set the base path from which shlink is served.
return [
'router' => [
'base_path' => '/foo/bar',
],
];
This option will also be available on shlink-installer 1.3.0, so the installer will ask for it. It can also be provided for the docker image as the BASE_PATH
env var.
#479 Added preliminary support for multiple domains.
Endpoints and commands which create short URLs support providing the domain
now (via query param or CLI flag). If not provided, the short URLs will still be "attached" to the default domain.
Custom slugs can be created on multiple domains, allowing to share links like https://doma.in/my-compaign
and https://example.com/my-campaign
, under the same shlink instance.
When resolving a short URL to redirect end users, the following rules are applied:
#411 Added new meta
property on the ShortUrl
REST API model.
These endpoints are affected and include the new property when suitable:
GET /short-urls
- List short URLs.GET /short-urls/shorten
- Create a short URL (for integrations).GET /short-urls/{shortCode}
- Get one short URL.POST /short-urls
- Create short URL.The property includes the values validSince
, validUntil
and maxVisits
in a single object. All of them are nullable.
{
"validSince": "2016-01-01T00:00:00+02:00",
"validUntil": null,
"maxVisits": 100
}
#285 Visit location resolution is now done asynchronously but in real time thanks to swoole task management.
Now, when a short URL is visited, a task is enqueued to locate it. The user is immediately redirected to the long URL, and in the background, the visit is located, making stats to be available a couple of seconds after the visit without the requirement of cronjobs being run constantly.
Sadly, this feature is not enabled when serving shlink via apache/nginx, where you should still rely on cronjobs.
#384 Improved how remote IP addresses are detected.
This new set of headers is now also inspected looking for the IP address:
#440 Created db:create
command, which improves how the shlink database is created, with these benefits:
#442 Created db:migrate
command, which improves doctrine's migrations command by generating a lock, preventing it to be run concurrently.
#377 Updated visit:locate
command (formerly visit:process
) to automatically update the GeoLite2 database if it is too old or it does not exist.
This simplifies processing visits in a container-based infrastructure, since a fresh container is capable of getting an updated version of the file by itself.
It also removes the need of asynchronously and programmatically updating the file, which deprecates the visit:update-db
command.
#373 Added support for a simplified config. Specially useful to use with the docker container.
PUT /short-urls/{shortCode}
REST endpoint in favor of PATCH /short-urls/{shortCode}
.visit:update-db
command to not return an error exit code even if download fails, by passing the -i
flag.findIfExists
flag.SELECT COUNT(...)
with ORDER BY
in PostgreSQL databases.shlinkio/php-coding-standard
version 1.1.0visit:process
command is executed.#304 Added health endpoint to check healthiness of the service. Useful in container-based infrastructures.
Call [GET /rest/health] in order to get a response like this:
HTTP/1.1 200 OK
Content-Type: application/health+json
Content-Length: 681
{
"status": "pass",
"version": "1.16.0",
"links": {
"about": "https://shlink.io",
"project": "https://github.com/shlinkio/shlink"
}
}
The status code can be 200 OK
in case of success or 503 Service Unavailable
in case of error, while the status
property will be one of pass
or fail
, as defined in the Health check RFC.
#279 Added new findIfExists
flag to the [POST /short-url]
REST endpoint and the short-urls:generate
CLI command. It can be used to return existing short URLs when found, instead of creating new ones.
Thanks to this flag you won't need to remember if you created a short URL for a long one. It will just create it if needed or return the existing one if possible.
The behavior might be a little bit counterintuitive when combined with other params. This is how the endpoint behaves when providing this new flag:
#336 Added an API test suite which performs API calls to an actual instance of the web service.
config:generate-charset
and config:generate-secret
CLI commands.php
and json
format are loaded from config/params
folder, easing users to provided customizations to docker image.AccessLogFactory
in favor of the implementation included in zendframework/zend-expressive-swoole v2.2.0CloseDbConnectionMiddlware
to be always piped. Now the check is not even made, which simplifies everything.#208 Added initial support to run shlink using swoole, a non-blocking IO server which improves the performance of shlink from 4 to 10 times.
Run shlink with ./vendor/bin/zend-expressive-swoole start
to start-up the service, which will be exposed in port 8080
.
Adding the -d
flag, it will be started as a background service. Then you can use the ./vendor/bin/zend-expressive-swoole stop
command in order to stop it.
#266 Added pagination to GET /short-urls/{shortCode}/visits
endpoint.
In order to make it backwards compatible, it keeps returning all visits by default, but it now allows to provide the page
and itemsPerPage
query parameters in order to configure the number of items to get.
X-Api-Key
header to the list of valid cross domain headers.#236 Added option to define a redirection to a custom URL when a user hits an invalid short URL.
It only affects URLs matched as "short URL" where the short code is invalid, not any 404 that happens in the app. For example, a request to the path /foo/bar
will keep returning a 404.
This new option will be asked by the installer both for new shlink installations and for any previous shlink version which is updated.
#189 and #240 Added new GeoLite2-based geolocation service which is faster and more reliable than previous one.
It does not have API limit problems, since it uses a local database file.
Previous service is still used as a fallback in case GeoLite DB does not contain any IP address.
visit_locations
table, to be snake_case instead of camelCase.PsrLogMessageProcessor
.user_agent
column length in visits
table to 512.#237 Solved errors when trying to geo-locate null
IP addresses.
Also improved how visitor IP addresses are discovered, thanks to akrabat/ip-address-middleware package.
#214 Improved build script, which allows builds to be done without "jumping" outside the project directory, and generates smaller dist files.
It also allows automating the dist file generation in travis-ci builds.
#207 Added two new config options which are asked during installation process. The config options already existed in previous shlink version, but you had to manually set their values.
These are the new options:
ShortUrl
instead of the concept of ShortCode
when referring to the entity, and left the short code
concept to the identifier which is used as a unique code for a specific Short URL
.#181 When importing the configuration from a previous shlink installation, it no longer asks to import every block. Instead, it is capable of detecting only new config options introduced in the new version, and ask only for those.
If no new options are found and you have selected to import config, no further questions will be asked and shlink will just import the old config.
#205 Deprecated [POST /authenticate]
endpoint, and allowed any API request to be automatically authenticated using the X-Api-Key
header with a valid API key.
This effectively deprecates the Authorization: Bearer <JWT>
authentication form, but it will keep working.
As of #200 and #201 REST urls have changed from /short-codes/...
to /short-urls/...
, and the command namespaces have changed from short-code:...
to short-url:...
.
In both cases, backwards compatibility has been retained and the old ones are aliases for the new ones, but the old ones are considered deprecated.
#187 Included an API endpoint and a CLI command to delete short URLs.
Due to the implicit danger of this operation, the deletion includes a safety check. URLs cannot be deleted if they have more than a specific amount of visits.
The visits threshold is set to 15 by default and currently it has to be manually changed. In future versions the installation/update process will ask you about the value of the visits threshold.
In order to change it, open the config/autoload/delete_short_urls.global.php
file, which has this structure:
return [
'delete_short_urls' => [
'visits_threshold' => 15,
'check_visits_threshold' => true,
],
];
Properties are self explanatory. Change check_visits_threshold
to false
to completely disable this safety check, and change the value of visits_threshold
to allow short URLs with a different number of visits to be deleted.
Once changed, delete the data/cache/app_config.php
file (if any) to let shlink know about the new values.
This check is implicit for the API endpoint, but can be "disabled" for the CLI command, which will ask you when trying to delete a URL which has reached to threshold in order to force the deletion.
#183 and #190 Included important documentation improvements in the repository itself. You no longer need to go to the website in order to see how to install or use shlink.
#186 Added a small robots.txt file that prevents 404 errors to be logged due to search engines trying to index the domain where shlink is located. Thanks to @robwent for the contribution.
#145 Shlink now obfuscates IP addresses from visitors by replacing the latest octet by 0
, which does not affect geolocation and allows it to fulfil the GDPR.
Other known services follow this same approach, like Google Analytics or Matomo
#182 The short URL creation API endpoints now return the same model used for lists and details endpoints.
#170 and #171 Updated [GET /short-codes]
and [GET /short-codes/{shortCode}]
endpoints to return more meaningful information and make their response consistent.
The short URLs are now represented by this object in both cases:
{
"shortCode": "12Kb3",
"shortUrl": "https://doma.in/12Kb3",
"longUrl": "https://shlink.io",
"dateCreated": "2016-05-01T20:34:16+02:00",
"visitsCount": 1029,
"tags": [
"shlink"
],
"originalUrl": "https://shlink.io"
}
The originalUrl
property is considered deprecated and has been kept for backward compatibility purposes. It holds the same value as the longUrl
property.
originalUrl
property in [GET /short-codes]
and [GET /short-codes/{shortCode}]
endpoints is now deprecated and replaced by the longUrl
property.[GET] /short-codes
endpoint returning a 500 status code when trying to filter by tags
and searchTerm
at the same time.#175 Fixed error introduced in previous version, where you could end up banned from the service used to resolve IP address locations.
In order to fix that, just fill this form including your server's IP address and your server should be unbanned.
In order to prevent this, after resolving 150 IP addresses, shlink now waits 1 minute before trying to resolve any more addresses.
[GET] /short-codes
API endpoint.#172 Documented missing filtering params for [GET] /short-codes/{shortCode}/visits
API endpoint, which allow the list to be filtered by date range.
For example: https://doma.in/rest/v1/short-urls/abc123/visits?startDate=2017-05-23&endDate=2017-10-05
#169 Fixed unhandled error when parsing ShortUrlMeta
and date fields are already DateTime
instances.
#155 Improved the pagination object returned in lists, including more meaningful properties.
Old structure:
{
"pagination": {
"currentPage": 1,
"pagesCount": 2
}
}
New structure:
{
"pagination": {
"currentPage": 2,
"pagesCount": 13,
"itemsPerPage": 10,
"itemsInCurrentPage": 10,
"totalItems": 126
}
}
#147 Allowed short URLs to be created on the fly using a single API request, including the API key in a query param.
This eases integration with third party services.
With this feature, a simple request to a URL like https://doma.in/rest/v1/short-codes/shorten?apiKey=[YOUR_API_KEY]&longUrl=[URL_TO_BE_SHORTENED]
would return the shortened one in JSON or plain text format.
PathVersionMiddleware
, since the bug in zend-stratigility has been fixed.#125 Implemented a path which returns a 1px image instead of a redirection.
Useful to track emails. Just add an image pointing to a URL like https://doma.in/abc123/track
to any email and an invisible image will be generated tracking every time the email is opened.
#132 Added infection to improve tests
PathVersionMiddleware
being ignored when using expressive 2.2#128 Upgraded to expressive 2.2
This will ease the upcoming update to expressive 3
E_USER_DEPRECATED
errors triggered when using Expressive 2.2SymfonyStyle
ExceptionInterface
extending Throwable
.gitattributes
file to define files to be excluded from distributable packageErrorHandler
catch any other unhandled exceptionAnnotatedFactory
by ConfigAbstractFactory
NotFoundDelegate
now returns proper content types based on accepted contentImplicitOptionsMiddleware
FastRoute
routes cachePathVersionMiddleware
only to rest routes defining it by configuration instead of codeparsedBody
EntityManager
factory#46 Defined a route that returns a QR code representing the shortened URL.
In order to get the QR code URL, use a pattern like https://doma.in/abc123/qr-code
#32 Added support for other cache adapters by improving the Cache factory
#14 Added logger and enabled errors logging
#13 Improved REST authentication
X-Forwarded-For
header in order to get the visitor information, in case the server is behind a load balancer or proxyAccept
headervisit_locations
tableOrigin
header is present in the requestAcelaya\UrlShortener
to Shlinkio\Shlink