The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.


HTML::Paginator - Object-Oriented Pagination for Web Applications


  use CGI;
  use HTML::Paginator;

  my $cgi = new CGI;
  my $page = $cgi->param('page') || 1;

  my @items = (1..67);
  my $book = HTML::Paginator->new(25,@items);
  $book->Name_Item('random item');
  @items = $book->Contents($page);

  print "<html>\n  <head>\n  <title>Sample Script</title>\n  ",
  # it bugs me that people use CGI methods for stuff like that 
  #   above. Gaaah.
  print "<body>\n",
  print "  </li>$_</li>\n" for @items;
  print "</ul>\n",


HTML::Paginator is an Object-Oriented module intended to make pagination of large lists easy. Using an amazing (or amazingly simple) internal method, it takes your favourite array and it slices, it dices, and it makes Julien Fries out of your array.

It's an HTML module because that's where it's most useful. However, a small amount of finagling can make it useful for any interface, really.


You create a Paginator object, which I'm calling a 'book' for lack of a better term, by calling the new($@) method, like is done with most OO modules.

new takes two or more arguments: the first is the number of items you want per page. The second and all following are the items you want sliced up into seperate pages. For instance, you could pull the results of a SQL query in to be sliced up, and display 10 per page:

  my @stuff;
  while (my $row = $my_query->fetchrow_hashref) {
      push @stuff, $row;

  my $book = HTML::Paginator->new(10, @stuff);

  for my $row ($book->Contents($page) {
      print $row->{column_to_print};

It's that easy.

Of course, if your database is slow, or you have a huge number of results, you don't want to pull down all of them first. I recommend getting a count, using paginator to slice *that* up, and then working a little programmer magic to get back only the slice of the table you want (Oracle would let you use rownum, while with MySQL you might have to work harder, doing a few small queries to whittle things to where you want them).

Then again, who says you're using a database? You could even use this to paginate a huge text document in an external file, with a while(<>) and a counter scalar, maybe. Ahh, this is all your job. I did the slicing.

As a note, HTML::Paginator acts like it thinks in terms of 1-indexed arrays. It doesn't, really. It just pretends to with its public methods. This is because while we all know that arrays should be zero-indexed, the user doesn't, and seeing page=0 in their URL looks goofy to them. So we're nice to them. They won't thank you because the web is full of ungrateful bastards, but you can feel nicer about yourself for knowing you were nice to a bastard. Or something.

Public Methods

Several nice convenience methods are supplied, so you can make the module do the thinking and you can back to drinking... or whatever. Hey, it rhymed.

new($@) (constructor)

As stated above, this creates a new Paginator object and slices it up into little pieces (or big pieces, as you prefer). It takes 2 or more arguments: the number of items per page, and the array or list (not an arrayref, BTW. Dereference any arrays you intend to hand to this first (or en passe)). For instance:

  my $book = HTML::Paginator(25, @array);

or perhaps

  my $book = HTML::Paginator->new(100, @{$object->{arrayref}});

Takes any string and makes that the internal name for whatever you're chopping up a list of. The default is 'result'. For instance, if you have a list of kittens for sale, you would call:


Takes any string and makes that the plural-iser for the item name. The default is 's'. For instance, if you'd set the item name to 'child' with Name_Item, you would want to set the plural correctly, so that it didn't consider more than one to be 'childs':


Like Set_Plural above, sets the singular. The default is '' (empty string). You may want to set the singular when the word changes form based on plurality. For instance, if you were strange enough to list octopi (that's the plural for 'octopus'), you would want to set the name to the least common denominator, and set the plural and singular forms:


While you can, theoretically, set the name to '' and the Singular and Plural forms to be the entire words:


I don't recommend it, because there is, in one of the two convenience methods, a case where the item name is ucfirst-ed. This will miss this case.


This method takes the current page number as an argument. It returns the slice of the array or list you handed it corresponding to the page number.

This is the method that is most important and useful to this whole thing, really.


Next takes a page number as an argument and returns the number of items in the *following* page. So if you slice up 67 items, page 6 will have items 51-60, and page 7 will have items 61-67. Thus, if you call:

  my $page7_items = $book->next(6);

Your $page7_items will be set to 7. Of course, you can feel free to use this for it's boolean value as well. I do.


Similar to Next above, this method returns the number of items in the page BEFORE the page number given in the argument. This is really only useful for its boolean value, however, as the only way to get a different number than the number of pages you set is to ask for page 1 (which will return 0), or a page outside of the page list (i.e. number of pages+1 will return the number of items in the last page, while number of pages +2 will return 0).


Quite similar to the Next and Previous methods above, Item_Count returns the numbe of items in the current page. This is useful for finding out if there are any items at all (a boolean use) or just any old use you feel like putting it to. Of course, its argument is the page being referred to.


This method returns the total number of pages in the 'book'. It takes no arguments at all. All it does is return the scalar value of @{$book->{page}}, the internal arrayref.


This useful litle method returns the 1-indexed number of the first item in the current page as it equates to all of the items in the original list. It takes a page number as an argument. Assuming you haven't changed the default array index or gotten rid of the original array, ($book->Content($page))[0] will always match @original_array[$book->First($page)-1]. Using whatever names.


Like First_Item above, this returns the 1-indexed number of the *last* item in the current page slice as it associates with the original array. Again, it takes a page number as an argument.


This super cool method takes a page number as an argument, and returns a nicely formatted sentence telling you where you are in the 'book'. The results look like:

  Results 51 to 60 of 67 (Page 6 of 7)

Isn't that nice? Of course, if you have set Name_Item to 'g' and Set_Plural to 'eese', it will say:

  Geese 51 to 60 of 67 (Page 6 of 7)

And if you did the thing with mouse/mice that I said not to, it will say:

  mice 51 to 60 of 67 (Page 6 of 7)

With 'mice' in lowercase, which is why I said 'don't do that.'

If it's called wrong it tells you (via its return) in a square-bracketed SSI-esque sort of way.

Another really cool method, this also takes a page number as an argument (is that part getting redundant or is it just me?) and returns a spiffy-cool formatted HTML link for each page forward or back (with 'page' as the CGI parameter name that looks like so:

  <a href="your_url?page=1">Last 10 items</a> | <a href="your_url?page=3"> Next 6 items</a>

or whatever. Again, setting the item name and plurality stuff changes the appearance. There is also some stuff below for property setting. Um, sorry, you can't change the cgi parametre name yet -- look for a later version. I just thought of that as I was writing this POD.

One of the cool things about this is that it actually preserves any other arguments in the query string, but replaces its page number (and sticks it at the end). This way you can use it with other arguments at the same time. As long as they aren't 'page' (hey, I can't think of everything off the bat!)

If it fails it returns a square bracketed message telling you it failed. This means you called it wrong (i.e. you gave it a silly page number like -1 or 0, or you didn't give it a page number, or you tried to call it as a sub instead of a method, or whatever.)

Private Methods


This method does all the work, chopping and slicing like one of those crazy chefs at Benihana.


This does the work of setting your plurals and stuff.


This resets things based on html text you may have changed


This does what the public method Item_Count does, but it takes a 0-indexed subarray index rather than a 'page number'. Thus for page 1, you ask for 0. Actually, you don't -- use the public methods.


html properties

The object has a hashref of properties keyed by 'html' inside it. This is set by the Name_Item and Set_Plural/Singular methods. You get to these with:


You can, if you really need to, mess around with these. They are as follows:

  pre_cap: The thing that begins the HTML navigation tag.
           Default empty string.
  end_cap: The thingy on the other end. Default empty string.
  previous_icon: a thingy pointing left. You can replace this
                 with an image tag or something if you want.
                 Default '&lt;&lt;' (<<)
  next_icon: As above with previous icn, but pointing right.
             Default '&gt;&gt;' (>>)
  singular: what you set with Set_Singular. Default ''
  plural: what you set with Set_Plural. Default 's'
  item_name: what you set with Name_Item. Default 'result'
  seperator: a thingy between direction links if there are two.
             Default ' | '
  href_link: the URL to link to. Defaults to $ENV{REQUEST_URI}
             whatever that is. If you have to change this,
             remember that the CGI parametre name for the page
             rests here, not in the place where the number is
             filled in. Actually, you can always
             s/page/your_param_name/ here to change the parametre
             name the hard way.
  previous_text: A sprintf template to point at the prior page.
                 Don't break the template if you feel you must
                 change it.
                 Default "Last %d $obj->{html}->{item_name}%s"
  next_text: As above.
             Default "Next %d $obj->{html}->{item_name}%s"

You can change the num_per_page if you want, though you will have to call the private method _Ginsoo to reslice. The num_per_page property is:


This is where the original array is stored. Note that is IS kept around. After the object is created you can remove it if you have to, and you can use this property if you suddenly change your mind in a fit of boredom. For instance, if you decide you want to slice up different stuff, you can. Just set this property to an arrayref of your choice:

  $book->{book} = \@new_array

If you have to do this, remember to call $book->_Ginsoo before you expect it to do anything, really.


- Add an easy way to set the CGI parametre name.

- Add the option of giving it a negative number as the first argument, and use that to slice for a given number of pages with whatever number per page, instead.

- Methods to set words like 'Last' and 'Next' for easy locale changing.

- A 'goooooooogle' style Page Navigation bar, maybe.


Dodger -