Making P&T: Juniper
Posted by Brandon Kelly on Mar 5, 2010
This is the fourth and final installment in a series about the design and development behind what is now Pixel & Tonic. Yesterday we looked at how we’re doing documentation. Today we’ll take a look at the other aspects of add-on management and commerce.
November, 2008. Four months since I had moved out and taken a new job. And I was under the impression that my stint with ExpressionEngine was over. I had only developed a couple websites with EE at my previous job. One of them required I build Playa, and when the site was done I released it for free as a token of thanks for all of the other free add-ons people had released. I had no intention of actively supporting or maintaining it – especially since I wasn’t even using EE at the new job.
But then I joined Twitter. ExpressionEngine users followed me, and I followed them back. I started seeing mentions of Playa here and there, and realized that people were actually using it. But they weren’t all nice things – many were having issues. I wouldn’t stand for that, so I started releasing updates. And with that, finding a way to manage releases.
At first, I turned to LG Data Matrix. Each time I released an update, I’d create a new row in an LGDM field – saving only the Version and Release Date in text fields, and the Release Notes in a textarea. A separate MH File field held the latest file.
But I wanted the dates to be stored as actual timestamps – something I could reformat within my templates (for, say, a Changelog feed). And I wanted a checkbox cell to define which rows would show up on my add-on overview pages (in a “Recent Changes” section). So I hacked those cell-types into LG Data Matrix myself. (Later, Leevi was kind enough to include my changes in his 1.1 update.)
Working on LG Data Matrix got me thinking: How cool would it be if the add-on came with an API that made it easy for anyone to create cell-types? Wait… how awesome would it be if there were an API that made entire field-types easy to create? And, once that was established, I could clone LG Data Matrix with that API, and then it could load these field-types itself, so they’d double as cell-types!
A couple months later, FieldFrame and FF Matrix were born. And not long after that, nGen File Field.
Which brings us back to managing releases. With FF Matrix and nGen File, I could now store each individual release’s ZIP file right alongside the rest of its metadata:

Going Commercial
April, 2009. I released Playa 2, a notable update that brought the add-on far beyond its original scope. So I decided that it was time to start charging.
For payment processing, I did what I think most add-on developers have done to date: take the Simple Commerce module, and hack the hell out of it.
The two big changes were:
- Don’t require a user to be logged-in to make a purchase. (I didn’t have any sort of member accounts, and wasn’t interested in dealing with that just so people could buy things.)
- Upon completion of a purchase, generate a license key. Store it alongside the other purchase data in exp_simple_commerce_purchases, and make it an available variable for my Customer Email Template.
When a customer completed a purchase, they would receive their License email, which included a Download URL. The URL was essentially a script that searched the purchases table for the license key it was passed, and if a record was found, served up the latest File for the associated product.
Though the system worked (for the most part, anyway), I was never happy with it. I didn’t like how disconnected everything was – release metadata and files being stored in a Matrix field; purchases being handled by a separate, hacked module; and file downloading by its own one-off script. I like to keep things contained, and this was anything but.
Enter Juniper
Pixel & Tonic gave me a clean slate. I could do anything I wanted. So I spent some time identifying what I disliked about the old system, what type of features I’d like to see, and what would be the most straightforward solution for myself and my customers.
Here’s a summary of what I came up with:
- Since items for purchase are typically associated with entries, so their commerce setup should take place right within a tab on the publish page.
- This tab should hold two things: the item’s price, and release matrix. The release matrix should record the Version, Release Date, Release Notes, and File for each release.
- Since license generation is tied to purchases, the module should handle the commerce.
- Since file downloading is conditional upon either the item being free or the member having paid for it, the module should handle that as well.
Essentially, take each of the components of the old system, and combine them into one module that’s specifically tailored to my own needs (selling software).
The end result is Juniper. Here’s what the publish tab looks like:

For the Releases matrix, I took the opportunity to try a couple new things that might make their way into the next major release of FF Matrix. The most noticeable being that previously-saved content is initially presented in a non-editable state. If you do want to edit a cell, simply clicking on it will swap its static state with an editable field. Hitting the ESC key while that input has focus will revert the cell back. This drastically cuts down the visual clutter of the matrix, and also makes it easier to identify which cells have unsaved changes.
Templating with Juniper
I spent a lot of time on the templating end of the module, which helped me keep my site’s templates straightforward.
With the help of an additional field, for_sale, those Download / Buy Now buttons at the top of each of my add-on pages were as simple as:
{if for_sale == "y"}
<a href="{exp:juniper:add_to_cart_url entry_id='{entry_id}' return='store/cart'}">Buy Now</a>
{if:else}
<a href="{exp:juniper:download_url entry_id='{entry_id}'}">Download</a>
{/if}
{exp:juniper:add_to_cart_url} adds the item associated with the passed-in {entry_id} to the user’s cart (an array stored in the session), and optionally redirects the browser to a specified template. In this case, users are redirected to store/cart, whose template looks a bit like this:
{exp:juniper:cart_form checkout="store/checkout"}
{if no_items}<p>Your cart is empty.</p>{/if}
<table class="cart">
<thead><tr>
<th scope="col">Product</th>
<th scope="col">Quantity</th>
<th scope="col">Price</th>
<th scope="col">Total</th>
</tr></thead>
<tbody>
{items}
<tr>
<th scope="col"><a href="{path={url_title}}">{title}</a></th>
<td><input type="text" name="quantity[{item_id}]" size="2" value="{quantity}" /></td>
<td>${price}</td>
<td>${total}</td>
</tr>
{/items}
<tr>
<td colspan="3"></td>
<td><strong>Grand Total:</strong> ${grand_total}</td>
</tr>
</tbody>
</table>
<input class="button" type="submit" name="update" value="Update Cart" />
<input class="button submit" type="submit" name="checkout" value="Checkout" />
{/exp:juniper:cart_form}
The {items} tag pair loops through each unique item in the user’s cart. Both Submit buttons will update the user’s cart with the new item quantities, and then either reload the current template, or redirect to the template specified in {exp:juniper:cart_form}’s checkout= parameter, depending on which Submit button the user pressed.
The other two Juniper-related templates, store/checkout and members/index, look much like you’d expect, so we’ll skip those.
Much to be done
I launched with many of Juniper’s to-do list items unchecked, and have been adding them as I have time. This past week has seen the addition of PayPal integration, some fancy charts behind the scenes, and even public module actions for generating a LG Addon Updater version.xml file and NSM Addon Updater releases.rss files. Purchase management isn’t quite finished yet, which is why I’m running a little late on those Launch Giveaways, and there are quite a few other nice-to-have’s in the pipeline.
But I couldn’t be happier with what I accomplished in this module, especially considering all of the other things I’ve been juggling these last couple months. I feel very confident about Juniper as my new release management/purchasing/downloading platform, and the initial reaction to its checkout experience has been quite positive.
Release
It’s very likely that Juniper will be released in some fashion this year. Given that it’s a very targeted product, how I go about it – the price, the availability, the support model, etc. – are still up in the air. But it’s no secret that I think more EE add-on developers should start charging for their work, and I’m happy to help make that a reality. If it’s something you’re interested in, let me know in the comments.
This concludes Making P&T, our series on the design and development of what is now Pixel & Tonic. If you have any other subjects you’d like to hear about on Blog & Tonic, feel free to share in the comments. But for now, we’re going to get back to doing what we do best: developing add-ons.
Comments
Lodewijk Schutte
Well, I, for one, welcome our new P&T overlords. This combined with the Docs module is enough for me to make the switch to EE2 instantly. Where can I pre-order?
Richard Lomas
Brandon, I’m not going to have any money left if you keep making killer add-ons!
Emmanuel
Jeeeeezzzzzzz…this looks so interesting! keep on the good job sir, you are sure going to attract a whole new load of follow-ee-rs!
Olivier
Another cracking entry about a cracking product, well done.
Dan Jasker
I’d love to have this, but as Richard Lomas said, I’m going to be broke. However my money has gone to a great developer and your work is always amazingly easy to use yet feature rich so I know it’s being well spent. I don’t know how you do it and every time I visit your site it’s like I must buy something to replace a clunky version of something else.
Marcus Neto
me want. me want now. Any idea on a release date?
Jim
What a great end to a week of superb articles. Having wrestled with SCM for the first time this week, I was more than ready to jump on the Juniper bandwagon—knowing that the setup and execution would be a breeze. Can’t wait to add_it_to_my_cart!
Erik Reagan
I’m in. As Low said, where do we pre-order?
Hambo
Will Juniper be a fully featured system like FoxEE or CartThrob or only something for digital downloads?
Danny Tam
Anybody following e-commerce solutions for ExpressionEngine outside of Simple Commerce knows that it’s a highly sought after addition. For whatever reason, there hasn’t been a comprehensive, dedicated add-on to date to meet the demands.
I was looking at your tweets a while back and noticed you had referenced Juniper and was wondering what it was exactly. I’m glad to hear this thing is coming alive..
Do consider adding this as a full-fledge e-commerce solution for not only digital downloads. I would love the flexibility of a non-cumbersome, but highly functional e-commerce solution that integrates nicely into ExpressionEngine — a match made in heaven really — which is why I’ve always been boggled by the trivial and complicated add-on’s to “bridge” EE with other e-commerce solutions when most people would have preferred a dedicated solution anyway. Not to discredit those hard working developers, I applaud their work and efforts, but a bridge, can never substitute an inclusive product made to work within EE exclusively for purists — and at least for me, my sanity.
Brandon Kelly
The problem with eCommerce is that everybody wants something different. Your idea “highly functional” greatly differs from the next guy’s. Which is why the market is made up of highly targeted but mostly invisible products like Juniper, products that attempt the 80/20 rule like Simple Commerce, and the really big, bloated systems that try to satisfy every need.
And if you’re going to go to the work of building the latter, there’s no way in hell you’re going to spend all of that time building it specifically for one CMS. It just wouldn’t make any business sense.
I’ve made it very clear from the beginning that Juniper is a software commerce module. And I have no interest in going beyond that. Looking back, the actual commerce portion was really just a small part of the work, anyway.
Danny Tam
@Brandon
If Juniper is just a digital download module, it’s ashame. I think there’s a lot of potential to fulfill the needs of consumers looking for a be all and end all solution.
I think the beauty of a solution such as Juniper supporting things like live inventory, shipping tracking, coupon registration, etc. is that it just integrates into ExpressionEngine. The selling point of EE for me is in its extensibility. By having an e-commerce module, I’m not expecting extrenuous efforts to make a fully fledged “all in one” package. Rather, with Juniper + tangible item support + existing add-on’s, we can arguably create as powerful stores as there already exists out there, just due to the nature of how EE manipulates content and templating.
Hambo
It would have been interesting to see Juniper in the same light as FieldFrame with other contributing to it’s greatness.
Bluestrike2
Well, you’ve gone and done it; I’m drooling at the opportunity. Very, very cool work; and, for what it’s worth, I absolutely love the new site.
Great job, as always. As an aside, I hope that these changes make their way into FF Matrix next time around.
Ryan Battles
What a great write-up. I used to hate paying for things, using a free alternative when I could. Now that I’ve paid for a few of your extensions, I talk my clients into them as well because of how much easier they make common tasks. Looking forward to seeing more creations produced by Pixel and Tonic.
Mark Bowen
Hi Brandon,
Just wanted to echo some of the posts already on your blog and say well done on a great site.
I’m looking at purchasing FF Matrix in the near future and was wondering just out of interest how are you handling Credit Card payments via your site?
I see that I have the option of paying via Credit Card or via PayPal and that if I choose PayPal I get sent over to the PayPal site but that if I choose Credit Card I’m guessing that I stay on your site?
If so then how are you handling checking credit card numbers? Is that something you built?
Best wishes,
Mark
Brandon Kelly
Hi Mark,
Yes, I’m using Auth.Net’s Advanced Integration Method to handle transactions. (Not storing your CC info though.)
Mark Bowen
Hi Brandon,
Thanks. Always interested in how people are handling things.
I ended up using PayPal to purchase FF Matrix but that was because I had some funds left in the account so it was easier this way for me.
Thanks again for the info.
Best wishes,
Mark
Lea
Non-tech related question: so why the name “Juniper?” Maybe I’m a little slow b/c I’m battling the flu right now but the other names of your products make “sense” but I’m having a hard time figuring out how the term Juniper forks into digital downloads or software ecommerce?
Erik Reagan
@Lea
I would imagine that Juniper is a reference to Brandon’s love on Gin & Tonic (being the berry from which gin gets its predominant flavor from what I understand)
Brandon Kelly
Haha… yeah, Erik’s right. Nothing related to the function, and if/when I release this, I’ll probably revisit the name.
mike
What’s Juniper website? I can’t find anywhere.
Ryan
Always love the products and Jupiter is the same.
Andy Stalsberg
Is there any mileage in exploring direct debit as an optional ecommerce solution for recurring/subscription payments as opposed to card payments removing the obstacle of expiry dates on cards?
James Buckley
Heh, my only comment was I wish it was out today, as I just found out I have a need for it! Looking forward to the release, though I may have to find something else to meet my deadline.