Simple Bookmarking App With PHP, JS & MySQL


Part of the developer's life, is to strive for simplicity in every aspect of their work. When searching for solutions to common problems, you have the choice to lock yourself into all kinds of third party services and API's, or take the opportunity and develop the functionality yourself.

In this week's tutorial, we are making a simple link sharing app. This app will give you the ability to instantly share any web page, with a single click of a bookmarklet in your bookmarks bar, and display it in a widget on your site.

Note: This tutorial is quite old and doesn't work in PHP7 and above. We are keeping it online only as a reference.

The idea

Clicking the bookmarklet includes a PHP script (evaluated as a JavaScript file) to the page you are viewing, with the title and the URL passed as GET parameters. The PHP script writes the page data to the MySQL database, and outputs a success message that is treated as JavaScript code and executed by the browser.


The database schema

Before moving to any development work, we need to create the database table that will hold all the bookmarks. The table definition is stored in table.sql in the download zip. You can run it in the SQL section of phpMyAdmin to recreate the table on your server. After this remember to change the MySQL login details in connect.php.


Notice the HASH column. This is a unique field that stores the md5() sum of the URL field. We are using this to ensure that there are no duplicate links in the database. Inserting a link that already exists, will cause the query to fail and the mysql_affected_rows() function will return 0. We are using this in the PHP section of the tut to determine what message is going to be displayed to the user, as you will see in a few moments.

Step 1 - XHTML

XHTML markup is generated on the fly by PHP. It is only needed when presenting the shared links on your website. It is basically a simple unordered list with each shared page being a li element inside it.


<ul class="latestSharesUL">

      <!-- The LI elements are populated by PHP -->

          <div class="title"><a href="" class="bookmrk">Perfection kills</a></div>
          <div class="dt">36 seconds ago</div>
          <div class="title"><a href="" class="bookmrk">The HTML5 test - How well does your browser support HTML5?</a></div>
          <div class="dt">2 minutes ago</div>

The li elements are generated after PHP runs a query against the database for the latest bookmarks, as you will see in step 3. Each li contains the title of the page and the relative time since the bookmark was added. We will get back go this in the PHP part of the tutorial.

Step 2 - CSS

Again, the CSS code is only needed in the presentation part. You can modify the styling to match the rest of your site or ignore this code completely. Also, not all the styles are given here. You can see the rest in styles.css in the download archive.


    /* The bookmark widet */
    margin:0 auto;
    border:1px solid #e0e0e0;
    text-shadow:1px 1px 0 white;

    font-family:Arial, Helvetica, sans-serif;

ul.latestSharesUL li{
    /* Each bookmark entry */
    border:1px solid #EAEAEA;

ul.latestSharesUL li:last-child{
    /* Targeting the last element of the set */
    border-bottom:1px solid #EAEAEA;

ul.latestSharesUL li{
    /* Adding regular and inset shadows */
    -moz-box-shadow:1px 1px 0 white inset, 0 0 2px white;
    -webkit-box-shadow:1px 1px 0 white inset, 0 0 2px white;
    box-shadow:1px 1px 0 white inset, 0 0 2px white;

    /* The date time field */

    /* The bookmark title in the widget */

By using the box-shadow and border-radius CSS3 properties, we are cutting down on the number of divs that would otherwise be needed to achieve the same design. Also notice the use of the :last-child selector, which targets the last li in the unordered list, and adds a bottom border.


Step 3 - PHP

First lets take a look at how the links are saved. As mentioned earlier, clicking the bookmarklet includes bookmark.php as a script in the head section of the current page. As it is served with a JavaScript content type, the browser will evaluate it as a regular JS file.


// Setting the content-type header to javascript:
header('Content-type: application/javascript');

// Validating the input data
if(empty($_GET['url']) || empty($_GET['title']) || !validateURL($_GET['url'])) die();

// Sanitizing the variables
$_GET['url'] = sanitize($_GET['url']);
$_GET['title'] = sanitize($_GET['title']);

// Inserting, notice the use of the hash field and the md5 function:
mysql_query("   INSERT INTO bookmark_app (hash,url,title)
                VALUES (

$message = '';
    $message = 'This URL already exists in the database!';
$message = 'The URL was shared!';

The document title and URL are passed to this script by the bookmarklet and are available in the $_GET array. The data is sanitized and validated with our custom made sanitize() function, after which it is inserted in the database. Then, after checking the status of the mysql_affected_rows() function, we assign to the $message variable the appropriate status message that is going to be displayed to the user.

I would suggest to take a quick look at bookmark.php in the download zip, to see how PHP and JavaScript work together to successfully insert the bookmark and output the result.

Now lets move on to see how the bookmarks are displayed in a simple widget.


$shares = mysql_query("SELECT * FROM bookmark_app ORDER BY id DESC LIMIT 6");

    // Shortening the title if it is too long:
        $row['title'] = mb_substr($row['title'],0,80,'utf-8').'..';

    // Outputting the list elements:
    echo '
        <div class="title"><a href="'.$row['url'].'" class="bookmrk">'.$row['title'].'</a></div>
        <div class="dt">'.relativeTime($row['dt']).'</div>

This code selects the last 6 shared links from the database, generates the appropriate LI elements containing the title as a hyperlink to the bookmarked page, and calculate the relative time since the entry was published with our custom made relativeTime() function.

The custom functions we are using are defined in functions.php.


/* Helper functions */

function validateURL($str)
    return preg_match('/(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?/i',$str);

function sanitize($str)
        $str = stripslashes($str);

    $str = strip_tags($str);
    $str = trim($str);
    $str = htmlspecialchars($str);
    $str = mysql_real_escape_string($str);

    return $str;

function relativeTime($dt,$precision=2)
    if(is_string($dt)) $dt = strtotime($dt);

    $times=array(   365*24*60*60    => "year",
                    30*24*60*60     => "month",
                    7*24*60*60      => "week",
                    24*60*60        => "day",
                    60*60           => "hour",
                    60              => "minute",
                    1               => "second");


        $output='less than 5 seconds ago';

        foreach($times as $period=>$name)
            if($exit>=$precision || ($exit>0 && $period<60)) break;

            $result = floor($passed/$period);
                $output[]=$result.' '.$name.($result==1?'':'s');
            else if($exit>0) $exit++;

        $output=implode(' and ',$output).' ago';

    return $output;

One of the guiding principles when building web applications is "Do not trust your users". This implies that all input data has to be properly escaped. This is exactly what the sanitize() function is doing - it prevents possible XSS attacks, strips any HTML tags and escapes all the HTML characters that could potentially break your markup when displayed.

The other interesting function is relativeTime(), which takes the timestamp field, assigned to each bookmark, and turns it into a user-friendly relative time string. It also takes an optional second argument, which limits the number of time units that are returned (setting precision to 1 will return 1 hour ago, instead of 1 hour and 10 minutes ago).

Step 4 - JavaScript

As the script is dynamically included in third party pages, it is not a good idea to rely on third party libraries like jQuery. This is why, for a change, we are going to work with pure JavaScript.

First, lets take a look at the bookmarklet code.

bookmarklet code

(function () {
    var jsScript = document.createElement('script');

    jsScript.setAttribute('type', 'text/javascript');
    jsScript.setAttribute('src', '/bookmark.php?url=' + encodeURIComponent(location.href) + '&title=' + encodeURIComponent(document.title));


The bookmarklet is just a regular hyperlink, which has the code above preceded with the javascript: protocol as its href attribute. When clicked, the snippet creates a new script element, sets bookmark.php as its URL (along with the encoded title and URL of the currently active page), and appends it to the head section of the document. It is not as pretty as it would have been if we used the jQuery library, but it gets the job done.

Now lets return to bookmark.php.


function displayMessage(str)
    // Using pure JavaScript to create and style a div element

    var d = document.createElement('div');

        // Applying styles:

        width = '350px';
        height = '20px';
        top = '50%';
        left = '50%';
        margin = '-30px 0 0 -195px';
        backgroundColor = '#f7f7f7';
        border = '1px solid #ccc';
        color = '#777';
        padding = '20px';
        fontSize = '18px';
        fontFamily = '"Myriad Pro",Arial,Helvetica,sans-serif';
        textAlign = 'center';
        zIndex = 100000;

        textShadow = '1px 1px 0 white';

        MozBorderRadius = "12px";
        webkitBorderRadius = "12px";
        borderRadius = "12px";

        MozBoxShadow = '0 0 6px #ccc';
        webkitBoxShadow = '0 0 6px #ccc';
        boxShadow = '0 0 6px #ccc';


    // Adding the message passed to the function as text:

    // Appending the div to document

    // The message will auto-hide in 3 seconds:

        }   catch(error){}

The JavaScript code above is right below the PHP logic that inserts the bookmark to the database in bookmark.php. The displayMessage() JavaScript function creates a div element, styles it and displays it in the center of the page.

As bookmark.php is evaluated as a JS file, every text that it outputs is treated as regular JavaScirpt code. As we mentioned in the PHP step, bookmark.php receives the document title and URL, inserts them in the database, and creates the $message variable. This is later outputted as a call to the displayMessage() function, which executes the above code and shows the message:

// Adding a line that will call the JavaScript function:
echo 'displayMessage("'.$message.'");';

With this our simple bookmarking app is complete!


If you plan to use this widget to share links with your visitors (or to save them for yourself) be sure to upload the script to a directory with a random name, as this script does not offer authentication. This is also the reason why it is so simple to set up and use.

What do you think? What do you plan to use it for?

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles


link for demo is dead :)

Martin, I've been learning from your tutorials for a while and I have to say that you are unique on the web. None else shares quality ideas like you, plus describing how to develop them. Thumbs up man, keep up the good work and all the best.

Very cool! However, how would you exactly create the favorite link in IE.. (it won'tt let you drag&drop)

Raden Indria

Thank you very much
this is what i need.


This is a killer app, i've had this idea for my upcoming blog but did not not know how to implement it!

YOU ROCK!!!!!!

Martin Angelov

Thank you for the comments!

@ Maarten

In IE you can right-click on the bookmark and choose to add it to favorites. From there you can save it to the Links folder. Also make sure that the Links toolbar is enabled and shown in the browser. But you probably shouldn't be using IE in the first place.

Daniel Groves

Awesome! Gunna set-up a secure version of this on my server as a homepage, add a few extras like RSS integration as well me thinks...

I love the stuff you are posting about, but why aren't you just always using object oriented code?

Deluxe Blog Tips

It's simple and need to be improved. For ex. the homepage just lists only last 6 urls, we can improve by using page navigation for displaying all urls in pages. Another option is organize bookmarks in Categories, which help us easy to find what we need.

very cool!

great script, love it.

i have one question. is it possible to add the website's favicon on the bookmark link? or even a custom one?

I just love your tutorials , its is really useful .

I love your tuts! You rock !

Dude, this is fantastic.

Really, really good work!

Wow, that's awesome! I've never seen something like this before. I'm really looking forward to use it on my website.

But, I agree with Deluxe Blog Tips, organizing the links in categories would be helpful.


Martin, sir you really rock!!

This totally awesome! I'm going to integrate an authentification and categorization function to share urls with a friend :)


btw.. there's a strange bug on your website see screenshot:

All content ist shifted left until I typein a letter into this form.
I'm using Opera latest stable 10.51.


happens only after commenting

Martin Angelov

Thank you, Fernandos! Fixed the bug.

Abdullah Al Mamun

you are the real web hero! more more more tuts...............
love to visit your site daily.

Please help:
I tried it, and I get this error:

Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in /home/knatan/public_html/raindrops/index.php on line 49

I checked the credentials, and nothings wrong that I can see. Maybe I need to use a different version of PHP?

Thank you!

Hehehe never mind. Forgot to upload table.sq. Ima n00b.
I guess this is what happens when I don't clean up my downloads folder.

Thank you very much for this wonderful tutorial. I would have one question though. I am trying to plug this in to wordpress and having a bit of a trouble making this use wp's current user table. To be more specific... i am trying to do the following:

Tutorialzine - PHP MySQL jQuery CSS Tutorials, Resources and Freebies
Shared by [wordpress user currently logged in to the specific site]
29 seconds ago

Could you please give me some tips on how to do this?
Again, thank you very much for this great piece of work. Congrats!

This looks very good, i've been looking on replicating the facebook sharer.php functionality (e.g. reading title, image, description from a url and launched via a bookmark)
Your app seems like a great start (still have to read the image and text in some manner), thank you!


I must be doing something wrong because it is not working. apart from the connect.php file, is there anything else i need to change? the bookmark button is not working. i cannot bookmark anything and so i think i might be missing something.

Thanks for your help.

Is there any way that users can sign up for this and each user gets his/her own list of links?
Thanks in advance.

Milo Rambaldi

absolutely awesome!!! thanks

It's not working for me. I have the correct db connection but the js script does nothing.

I had the same problem... certain sites like youtube it dosent like but try chaning the $url in bookmark.php try changing it to the real url and see if that works... ul have to recreate the boock marked link tho ... hover over it to see if it got the right link in the javascript

Brett Widmann

This is a great app. It was pretty easy to put together and easy to use once it was finished. Thanks for sharing.

hey this bookmarker does not seem to work on youtube... whats up with that?

It isn't working on sites that use Encryption (https), because the script in the bookmark is "unsafe content".

Thanks for a great tutorial!
I was just wandering why you used a hash field to check for duplicates - why don't you just make the url field a unique key?

Martin Angelov

You can't add a unique index to a column of the text type. You can only add the unique constraint to fields of a fixed length like varchar. As limiting the length of the URL is not a good idea, generating an MD5 sum is a great solution as it always has a fixed length and is unique.

Thanks, this alone was a very useful tip!


Hi Martin.
I've done a lil bit work upon your tutorial, and I wanted to share with you.
(sorry for my bad english :$ )

How could I add a remove button to this?

Good Work,

But there's a bug in displaying the date of the bookmark, it always displays 'less than 5 seconds ago'.

I am looking to implement something like this for emails, where a use is able to add an email to the 'readlist' to read later offline. Any Idea on how to go about this? Just need a push in the right direction.

Thanks again.