People are visual creatures. They like to see pretty things (that go beyond cat videos). I’m not saying this likely or because I enjoy it! It’s a hard, cold fact. Couple it with an ever decreasing attention span and rest assured your application is judged by appearance (in the first 3 seconds) rather than by features or quality. If you’re a developer that can’t afford to pay for a custom GUI design for an app, this is especially devastating. No matter how good, fast or innovative your app is if it looks like it was “designed by a developer” people won’t even give it a chance. Thankfully there are ways around this problem.

Using a tried and tested open-source admin template is undoubtedly a good start. Just as good as using reliable data annotation services. Another less-known trick is to turn dull data such as IP addresses and browser user-agent identifier into much more interesting data that can not only provide more information to the user but make it fun and good-looking as well. Best thing is – it’s a free and straightforward feature to add to any app.

tl;dr Cat videos are queuing up, let’s be fast! Without hiring a designer make your web app look 10 times more interesting by using GeoIP and WhichBrowser to add more human-friendly and useful data to them.

The standard, boring & ugly way

Instead of wasting time on theory and made up situations we’ll dive into a scenario familiar to nearly all web apps – displaying user action log data in a table. While the actual data differs from system to system, some fields are common, and those include the IP address, timestamp, and user-agent string. Merely displaying that raw data in a table results in an ugly catastrophy nobody wants to look at, let alone use every day. It looks something like the table below. Design itself aside; the data is unusable because it’s not adjusted and formatted for humans. For instance, IP addresses are not something people often remember. That’s one of the main reasons we use domain names. Showing an IP in an end-user table like this is borderline useless.

ID Timestamp IP User Agent Action
5 2018-09-08 20:03:32 Mozilla/5.0 (X11; U; Linux x86_64; de; rv: Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8 login
4 2018-09-08 06:23:01 Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko login
3 2018-09-07 22:03:12 Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 password reset
2 2018-09-02 14:12:53 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:34.0) Gecko/20100101 Firefox/34.0 logout
1 2018-09-02 12:33:43 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 failed login

For those who can’t wait – see the new & improved table.

A few adjustments go a long way

First, lose the ID column. Why would anyone need your internal database record ID? It’s an utterly useless piece of data for your users. It takes up precious screen real-estate and draws attention away from important information.

The timestamp, in its MySQL timestamp format, is human readable, but it’s far from user-friendly. Things are even worse if you keep and display it as a UNIX timestamp. Then it’s 100% unreadable. Different users (usually based on their location) prefer different formatting of time, date, currencies and numeric data. Those difference are subtle but should be respected. Users appreciate it. The age of the event is also important. For fresh ones, it’s convenient to show descriptions like “10 minutes ago”, signifying that it’s a recent event – no need to show the full timestamp. While for older ones we show the full locally formatted time and date.

Thankfully WordPress has built-in functions to deal with everything. By using the date and time format set in WP admin – Settings – General we’ll adjust the timestamp to fit the formatting of the rest of the site and by using the human_time_diff() function we’ll make it user-friendly. The following code assumes you have the timestamp in a MySQL format, but if you have it in a UNIX timestamp remove the first line.

function wpr_format_timestamp( $timestamp ) {
  // convert MySQL timestamp to UNIX timestamp
  $timestamp = strtotime( $timestamp );
  if ( current_time( 'timestamp' ) - $timestamp > DAY_IN_SECONDS * 1) {
    // our event is older than a day
    // we'll show full timestamp formatted as per WP settings
    $out  = date( get_option( 'date_format', $timestamp );
    $out .= ' @ ' . get_option( 'time_format' ), $timestamp );
  } else {
    // timestamp is not older than a day
    // we'll show a message like "2 hours ago"
    $out  = human_time_diff( $timestamp, current_time( 'timestamp' ) );
    $out .=  ' ago';
  return $out;

If you want to show the “human difference” format for dates older than one day, multiply the DAY_IN_SECOND constant with the desired number of days.

Using boring IP data to achieve a “wow” effect with GeoIP

Numerous services transform IPs to human-readable geographical data (here’s 20 geoIP services compared if you want to explore) that includes country, state, city, time zone, currency info and ISP details. They all fall into two categories – remote APIs and downloadable databases. Because of daily changes to geoIP data, for most projects, API is a much better choice. Services vary in price, ease of use and precision of returned data. After years of using various services, for the last two, I’ve been using IP Geolocation. For smaller projects with up to 50,000 API requests per month, the service is completely free. A great, risk-free offer for any project and especially for testing.

Using the API is easy. Register for an account (no credit card or similar nonsense needed), grab your API key from the account page and merely call the API URL with the IP and API key to get results. In code (WordPress specific), it looks something like the code below. If you’re not using PHP, there are six SDKs available for IP Geolocation, so you’ll find something that suits your project. They also offer a downloadable database for larger projects; contact them for details.

function wpr_get_ip_info( $ip ) {
  $endpoint = '';
  // grab the API key from
  // if needed register for a free account, no credit card info needed
  $api_key = 'PUT-YOUR-API-KEY-HERE';
  $params = array('apiKey' => $api_key, 'ip' => $ip, 'fields' => 'city,country_name,country_flag');
  $url = $endpoint . '?' . http_build_query($params, '', '&');

  $response = wp_remote_request( $url );
  if (is_wp_error($response)) {
    return $response;
  if (wp_remote_retrieve_response_code($response) != 200) {
    return new WP_Error('geoip_bad_response', 'Response code ' . wp_remote_retrieve_response_code($response));
  $body = wp_remote_retrieve_body($response);
  if (empty($body)) {
    return new WP_Error('geoip_bad_response', 'Empty body.');
  $body = json_decode($body, true);
  // returned variable is an array with following fields: ip, country_name, country_flag, city
  return $body;

The API returns a whopping 26 pieces of information based on the IP – test it with your IP. Displaying all of that would be too much, so I usually opt for the country name, country flag, and city. It’s enough data and flag adds a nice visual touch. The request returns only those three variables, to save some bandwidth. If you need all fields just leave the fields array key in $params empty. Country flags image can be directly hotlinked from IP Geolocation which is quite convenient. For local IPs, you can’t show much so make sure you account for that scenario. You’ll get a WP_Error with response code 423 in case of a local IP.

User-agent string hides a wealth of data

The user-agent string is huge and not meant for direct human consumption. No sane human needs nor wants all the data shown in that mess. We need the type of device used – desktop computer, phone or something in between. Then the OS – Windows, Mac or everything else; and maybe the browser used: Chrome, Safari, Internet Explorer and Firefox. That gives the data more structure, and it separates it into three additional pieces of information useful for filtering and segmenting.

I’m a huge advocate of using things other people (smarter than me) made. Therefore we’ll use a ready-to-use, battle-tested, well-maintained library for parsing the user-agent string. Head over to WhichBrowser on GitHub to grab the PHP version. Depending on your PHP environment setup you can either install it as a Composer package – instructions are on the link above, or as a standalone piece of PHP. In case you’re in the second group include the bootstrap.php file and you’ll be ready to go.

As we the timestamp and IP improvement the amount of code we need is minimal, WhichBrowser handles the heavy lifting.

function wpr_get_browser_info( $user_agent ) {
  // assuming you don't use Composer, manually include WhichBrowser
  require_once 'whichBrowser-lib/bootstrap.php';
  // parse the user agent string
  $browser = new WhichBrowser\Parser( $user_agent );
  // grab the type and then the subtype
  // see WhichBrowser for more info
  $tmp = $browser->getType();
  $tmp = explode( ':', $tmp );
  $result['type'] = $tmp[0];
  // and the OS and browser name
  $result['os'] = $browser->os->name;
  $result['browser'] = $browser->browser->name;
  return $result;

Pass the saved user-agent string to the wpr_get_browser_info() function and you’ll get back the OS name, browser name and device type. Put it in the table in place of the raw user-agent string.

The new & improved table

Behold our new table. We worked with the same data, but formatted and processed it properly – for humans!

Timestamp Country City Browser OS Device Action
20 minutes ago Germany Berlin Firefox Ubuntu desktop login
14 hours ago  USA Philadelphia Internet Explorer Windows desktop login
September 7th, 2018 @ 10:03pm  Latvia Riga Safari iOS mobile password reset
September 2nd, 2018 @ 02:12pm  USA North Bergen Firefox OS X desktop logout
September 2nd, 2018 @ 12:33pm n/a n/a Chrome Windows desktop failed login

Have a glance at the old table once more to see how awful it was. Bear in mind – the design stayed the same! We didn’t adjust the font, colors, or anything visual – just the data! Moreover, with barely a few lines of code.

What have we learned?

We learned that our “designed by developer” GUIs are often not fun to look at or use. All other great and hard work we put in is futile if, at first glance, our app looks like crap. While I’m far from a designer and can’t give you any pointers on designing a GUI, trough the years, I’ve learned how to make boring data look interesting and more user-friendly. IP Geolocation, WhichBrowser, and similar tools can help you stand out from the crowd with just a few lines of code. So, use them!

  1. Great write up Gordan. Thanks for sharing such valuable information about transforming boring data into the better user experience.

Leave a Reply

Your email address will not be published. Required fields are marked *