Dynamic rule-based redirects system

Shlink 4.0.0 introduced a new system to dynamically redirect visitors to different long URLs based on runtime conditions.

It works by allowing you to define any number of rules for a short URL, which are checked sequentially based on their priority, until one matches.

If a rule matches, the visitor is redirected to that rule’s long URL, and only if none match, they are redirected to the default long URL normally.

Every rule can be composed of any number of conditions, but all of them need to match in order to consider the rule a positive match.

Supported conditions

Currently, Shlink supports the next condition types:

  • Language: Checks the Accept-Language header in order to see if one specific language is accepted.
  • Query param: Verifies if the query string contains a specific query param with a specific value.
  • Device: Tries to match the visitor’s device based on the User-Agent header, and infer if it’s Android, iOS or Desktop. Other devices like Tablet, iPad etc. could be added in the future.

    Note

    Device rules used to be handled separately, but they were migrated to the new rule system for consistency and flexibility.

  • IP address (since v4.2.0) : Allows to dynamically redirect based on the visitor’s IP address, via exact match (100.200.80.40), CIDR block matching (192.168.10.0/24) or wildcard pattern matching (11.22.*.*).
  • Geolocation: country code (since v4.3.0) : Checks the visitor’s ISO 3166-1 alpha-2 two-letter country code resolved via geolocation.
  • Geolocation: city name (since v4.3.0) : Checks the visitor’s city name resolved via geolocation.

    Warning

    Geolocation-based rules are prone to false positives/negatives (specially by city name), since geolocating a visitor based on their IP address is not perfect.

Handling redirect rules

There are mainly two ways to manage dynamic redirect rules for a specific short URL:

  • The REST API, via POST /short-urls/[shortCode]/redirect-rules endpoint, which expects a body like this:
    {
    "redirectRules": [
    {
    "longUrl": "https://example.com/android-en-us",
    "conditions": [
    {
    "type": "device",
    "matchValue": "android",
    "matchKey": null
    },
    {
    "type": "language",
    "matchValue": "en-US",
    "matchKey": null
    }
    ]
    },
    {
    "longUrl": "https://example.com/fr",
    "conditions": [
    {
    "type": "language",
    "matchValue": "fr",
    "matchKey": null
    }
    ]
    },
    {
    "longUrl": "https://example.com/query-foo-bar-hello-world",
    "conditions": [
    {
    "type": "query-param",
    "matchKey": "foo",
    "matchValue": "bar"
    },
    {
    "type": "query-param",
    "matchKey": "hello",
    "matchValue": "world"
    }
    ]
    }
    ]
    }
  • The command line interface, via short-code:manage-rules command:
    Terminal window
    $ shlink short-url:manage-rules rmD8f
    ---------- ------------------- -----------------------------
    Priority Conditions Redirect to
    ---------- ------------------- -----------------------------
    1 device is android https://example.com/android
    2 device is desktop https://example.com
    ---------- ------------------- -----------------------------
    What do you want to do next? [Save and exit]:
    [0] Add new rule
    [1] Remove existing rule
    [2] Re-arrange rule
    [3] Save and exit
    [4] Discard changes
    >

FAQs

  • Can I use the same type of condition more than once in the same rule?

    Yes. Of course, this does not always make sense, like a visitor will never be using an Android and iOS device at the same time, but you could check for more than one query param, or if more than one language is accepted.

  • Can I make a rule match as soon as some of its conditions match?

    No. In order to match a rule, all its conditions need to positively match. There’s no way to configure a partial match.

    However, this behavior can be easily mimicked by defining consecutive rules that redirect to the same long URL.

    For example, if you want to redirect to https://example.com/foo when visitors are (Android AND ?foo=bar) OR (iOS AND ?hello=world), create one rule for the first two conditions, and another rule for the other two, and point both of them to https://example.com/foo.

  • How granular is the language condition?

    The Language condition allows for a lot of flexibility, as you can match full locales (in the form of en_US or en-US) but also language codes.

    For example, if you define the rule with value es-ES and the header is sent like Accept-Language: fr-FR, es-ES, en-UK, it will match positively, however, it won’t match Accept-Language: es.

    On the other hand, if the rule is defined as only es, it will match in both cases, as it means “any Spanish”.

    There’s one exception. If the Accept-Language header is sent with value *, it will be ignored and never match, as it would incorrectly match any language otherwise.

    Finally, it’s worth mentioning language matches are case-insensitive.

  • My geolocation conditions do not work. What is wrong?

    Geolocation conditions will match based on the visitor’s geolocation, so you need to make sure a GeoLite2 license key was provided, and tracking is not disabled.

    Additionally, and even if geolocation is working properly, this is known to be error-prone. Sometimes IP addresses cannot be located, or they are located in the wrong city/country.

    One way to mitigate incorrect geolocations is to combine geolocation-based rules with language rules: You could configure a rule for users in ES, and add another rule for language es-ES. If the country didn’t properly match as Spain but the visitor’s language is Spanish from Spain, you could assume the visitor is in Spain and redirect to the same place.