Creating an E-Commerce site with 11ty, Forestry, JamCart, and Netlify

Today we'll be creating an e-commerce site from scratch using 11ty to build the website statically, Netlify for easy hosting, Forestry as our CMS, 11ty to generate the website, and JamCart to handle the shopping cart and checkout. While this may sound like a lot, each of these tools is specialized in one thing, does that one thing with ease, and is simple to use.

At the beginning of each step, there will be a link to a GitHub repository so you can either follow through step by step or just read the code.

You can see our final result at

Before We Begin

Through the rest of this tutorial, we will assume a basic level of knowledge with a few different tools. Specifically:

  • Using Node in your local development environment
  • Basic workflows with git
  • Hosting repositories on GitHub
  • HTML, CSS, and JavaScript

These will not broken down in order to limit the scope of this tutorial and allow us to focus on the frameworks being used.

Creating a Project

Step one is to create a spot for all of our new files. We'll call our new folder 11ty-forestry-netlify, but obviously you can call your folder whatever you please.

~ $ mkdir 11ty-forestry-netlify
~ $ cd 11ty-forestry-netlify

From there, we will want to create a new node project and install 11ty.

~/11ty-forestry-netlify $ npm init -y
~/11ty-forestry-netlify $ npm install --save-dev @11ty/eleventy

This will create a package.json file for you. You should take a second to look through it and make sure you're OK with the defaults.

Finally, we want to initialize a git repository and push our changes. For this tutorial, let's assume we're using GitHub here although GitLab and BitBucket would work just as well. Don't forget to create a .gitignore file so we don't accidentally commit node_modules.

~/11ty-forestry-netlify $ echo 'node_modules' > .gitignore
~/11ty-forestry-netlify $ git init
~/11ty-forestry-netlify $ git add --all
~/11ty-forestry-netlify $ git commit -m "Initial commit"
~/11ty-forestry-netlify $ git remote add origin
~/11ty-forestry-netlify $ git push origin main

Having your repository hosted is important for connecting to both Forestry and Netlify.


Our code so far

I like to have data available to work with before beginning work on layout and templates. Since we were planning to use Forestry as CMS on this project, we'll go ahead and set it up now so we can use that to create our initial data set.

Head on over to and create an account. Once through registration and looking at your Dashboard, click "Add Site". Forestry will present you with a short wizard, allowing you to import your repository.

Pick our SSG, 11ty
Choose our repository host
Connect our new respository

Now that we have our site connected to Forestry, we need to start adding data. First step: define the shape of our data. Hit "Front Matter" in the sidebar and then click "Add Template" in the top corner.

Select "Create new"
Name our template "Product"

We now have a blank template for our data and can add new fields to structure our documents from here. We'll keep it simple, just adding fields for title, price, image, and description. In the end, we it should look like this:

Configured Template

Once we have a template and need to tell Forestry where to save files. Click "Configure sidebar", "Add Section", and then choose "Directory". We do want multiple products on our website after all!

We'll call this "Products" and store our products in "src/products". It is important to change "File Match" to only match markdown files: we'll use other files in this directory to configure 11ty and we don't want those accidentally getting caught up. Finally, select the template "Product" we created earlier and we're good to go. It should look like this:

Configured Directory

Hit "Done" and then "Save" our changes to the sidebar. If we did this correctly, we'll see "Products" appear in the sidebar off the left. This is where our work finally starts to pay off. We have a functioning CMS and can start to create our products.

First Product

Once you have finished creating your products, remember to do git pull on your local repository! Forestry is a git-based CMS, so every change is immediately committed to your repository.


Our code so far

11ty is a static site generator. It's fantastic at taking files on our computer (in our case, the markdown files created by Forestry), passing them through templates, and then outputting static html files. We can then host those files on Netlify but we'll need to do some configuration first.


Create a file named .eleventy.js in your root directory and copy this over:

module.exports = (config) => {

  config.addFilter("money", function (value) {
    const formatter = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' })
    return formatter.format(value)

  return {
    dir: {
      input: "src"

This accomplishes three completely seperate things.

  • config.addPassthroughCopy designates folders that should just be copied over in their entirety. Here we've designated uploads, the folder created by Forestry for our image uploads, and static, a folder we haven't created yet but will use for storing css, favicon, and other static assets.

  • config.addFilter gives us a helper method to use in our templates. This is an e-commerce shop, so formatting numbers to look like actual prices is important. Intl.NumberFormat will do all the heavy lifting for us, so our logic is pretty simple.

  • We return a configuration object to 11ty. 11ty's default configuration is to use the current working directory for input. Using a folder for input that contains our package.json, output directory, and assorted other private files always has made me uneasy. To solve this, we will simply move the input directory to "src", which already contains our "products" folder.

Next, we should add a new line to our .gitignore:


_site is the output directory for 11ty and we don't want to commit this into our repository. Netlify will build the project for us as part of deployment.

Opening up your pacakge.json files, let's add two lines to the "script" block:

  "build": "eleventy",
  "dev": "eleventy --serve"

Now we can run npm run build and npm run dev, without having to remember framework specific commands. We could just run eleventy directly, but it is typically easier to follow standard node conventions. We can start the development server now, although it will only display an error since we haven't written the homepage yet.


With setup out of the way, we can begin writing the website proper. We want a homepage, with a list of all of our products, as well as an individual page for each product. These pages should all share a wrapper containing things such as our site navigation and html skeleton.

We'll start with that global wrapper. 11ty supports multiple different template engines and we'll use Liquid to write our layouts today. Given the limited scope of our templates, these would be indentical if we were to use Nunjucks and would take only mild alteration to run in one of the other options.

When layouts are referenced in 11ty, it looks for an "_includes" folder inside our input directory. Since we changed the input directory to "src", we'll want to create src/_includes/global.liquid and populate it like so:

<!doctype html>
<html lang="en">
    <title>{{ title }}</title>
    <meta charset="utf-8" />
    <meta name="description" content="{{ description }}" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="/static/global.css" />
      <p id="site-logo">
        <a href="/">Our Demo Site</a>
      <p id="open-cart">#OPEN CART#</p>

    {{ content | safe }}

This is pretty standard boiler plate html with just a few additions.

Of special note are the values inside our curly braces. This is how we inject data from our source files here. title and description we'll need to provide on our own for each page. content is a special variable provided by 11ty and is how we can nest layouts inside of each other. The safe filter afterwards just notes that we don't have to do html escaping on this block. #OPEN CART# doesn't do anything and is just a reminder for us. We'll come back later and fix this when we integrate JamCart.

To create our first page, create src/index.liquid. This will map to the homepage of our website and should look like this:

title: 11ty, Forestry, and Jamcart E-Commerce Demo 
description: A demonstration of how to easily build an e-commerce website.
layout: global.liquid

<h1 id="category-title">Our Products</h1>
<ul id="products">
  {% for product in collections.product %}
      <a href="{{ product.url }}">
        <img alt="" src="{{ }}" />
        {{ }}
      <p class="price">{{ | money }}</p>
  {% endfor %}

The part in front bracketed by --- is referred to as "front matter". Data can be written here either in YAML, as demonstrated here, or JSON format, allowing us to associate data with our layout or markdown files. We define title and description here because our own layout file uses these to render the page title and meta description. layout is special and tells 11ty which layout to use, relative to the "_includes" folder. This is the file we've already created for our overall site layout.

|money just runs the filter we defined in .eleventy.js earlier. Its sole purpose is to take a decimal number and format it as currency for display.

collections.product is a special array maintained by 11ty and contains all documents with the tag "product". While we have products, we haven't actually tagged them as a "product" yet so this is currently empty. Once we have documents in this collection, each item returned will contain metadata about the document, including the url where the output lives, and everything we defined in front matter is accessible under data.

To populate this collection, we can add tags: product to each of our products. Adding that to each file individually would be tedious but we can instead take advantage of 11ty's data cascade. If we define a file name products.json inside of our "products" folder, 11ty will use that as default values for all documents inside the folder.

  "layout": "product.liquid",
  "tags": "product" 

At this point, you can actually open your website and see a homepage! Feel free to grab the files from the demo repository to add a little style.

The final step is to create the layout for our product pages. We already referenced it products.json, so just create src/_includex/product.liquid:

layout: global.liquid

<div id="product-view">
  <img alt="" src="{{ image }}" height="959" width="640" />

  <form class="body">
    <h1>{{ title }}</h1>
    <p class="price">{{ price | money }}</p>
    <h2>Product Description</h2>
    <p>{{ description }}</p>
        <select name="Size">
          <option selected>Medium</option>
      #ADD TO CART#

Defining layout at the top here allows us to chain layouts. Our data files will get fed in to this and then the results of that get fed into global.liquid, giving us our top navigation bar. #ADD TO CART#, like the #OPEN CART# before, is just a reminder to ourselves to come back and fix this. We'll be replacing when we install JamCart.


Our code so far

We have some critical functionality still left to be implemented on our website: the entire shopping cart and checkout. For that, we'll need an account with JamCart. After registering your account, navigate to the "Configure" page of the dashboard. Here, we'll grab the code we need to install on our website:

<script type="module" data-currency="USD" data-id="ACCOUNT_ID" src=""></script>

You'll need to grab the code from your dashboard, not from the line above, since your own account id is required for this. Just open our global layout in src/_includes/global.liquid and add this script anywhere instead of <head>.

While this file is open, let's also replace #OPEN CART# with <jamcart-open></jamcart-open> and then take a look at our website. The result isn't fantastic since the default styling clashes with our black navigation bar. Fortunately, we can override the html however we want:

  My Cart (<jamcart-cart-count></jamcart-cart-count>)

Now that we can open a shopping cart, we need the ability to add items to it. Open up src/_includes/product.liquid and replace #ADD TO CART# with:

  image="{{ image }}"
  name="{{ title }}"
  price="{{ price }}"
  url="{{ page.url }}"></jamcart-add>

This creates an "Add To Cart" button and passes the parameters to JamCart. page here is a special variable supplied by 11ty that we use to pass the current page's url. include-form-data instructs JamCart to include all data from the containing form. That includes the size <select> we included in this template.

We just have a few steps left with JamCart:

  1. Whitelist the domain our code is hosted on
  2. Create a Shipping Method

We can't whitelist the domain until we know which domain to whitelist, so we'll come back to that after uploading our code to Netlify.

Creating a new Shipping Method just requires opening the dashboard, going to "Shipping", and pressing "Create".

New Shipping Method

Here, we've created free ground shipping for anyone inside the USA. Be aware that customers will not be allowed to checkout if they have an item that requires shipping and you have not yet created a shipping method that includes their address.


Our code so far

We have a working shopping cart now, but checkout won't work. JamCart needs to be able to crawl our website to verify the price of items and that requires getting our code hosted. We'll be using Netlify to fix that.

Getting our code hosted is really simple. If you haven't already, remember to commit and push your code. Then login to Netlify and, from your Dashboard, hit "New site from Git".

Create Site Form

Select GitHub and follow the prompts to connect your repository. Once the repository is connected, double check the "Basic build settings". Netlify automatically filled these out for me correctly in our tutorial example:

Build Settings

The very last thing we need to do is authorize this domain with JamCart. Returning to your dashboard, fill out "Domain Whitelist" with the domain your website is hosted on.

That's It!

We now have a functioning e-commerce website. Any changes or additions you make on Forestry will immediately be committed, causing Netlify to redeploy your website. Our website consists entirely of static html pages which results in the usual perks of JAMstack.

Just check out your Lighthouse score