Grav E-commerce — SimpleCart, Payment Gateways and Products
In this tutorial, you'll learn Grav e-commerce with the SimpleCart plugin — setting up products, configuring payment gateways, building a shopping cart, managing checkout flow, and selling digital and physical goods.
What You'll Learn
- Installing and configuring SimpleCart
- Creating products with frontmatter
- Payment gateway integration (PayPal, Stripe)
- Shopping cart and checkout flow
- Digital downloads and physical shipping
- Order management and notifications
- Cart templates and customization
Why It Matters
In WordPress, e-commerce requires WooCommerce — a heavy plugin with database tables. In Grav, the SimpleCart plugin provides lightweight e-commerce functionality using Grav's file-based architecture. Products are pages with special frontmatter. Orders are stored as files. No database needed. This makes SimpleCart ideal for small shops, digital product sales, and developers who want a Git-friendly e-commerce setup.
Real-World Use
A developer sells Grav themes and plugins. Each product is a page with a "Buy" button. When a customer purchases, SimpleCart processes the payment through Stripe and emails a download link. The order is saved as a JSON file. The developer updates product prices and descriptions by editing Markdown files — no admin panel navigation needed.
Learning Path
flowchart LR
A["Web Services"] --> B["E-commerce with Grav
← You are here"]:::current
B --> C["Caching Deep Dive"]
C --> D["Performance Optimization"]
D --> E["Security"]
E --> F["Git Workflow"]
F --> G["CLI Tools"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Installing SimpleCart
bin/gpm install simplecart
Configuration
user/config/plugins/simplecart.yaml:
enabled: true
currency: USD
currency_symbol: $
payment:
stripe:
enabled: true
publishable_key: 'pk_test_your_publishable_key'
secret_key: 'sk_test_your_secret_key'
paypal:
enabled: false
client_id: 'your-paypal-client-id'
secret: 'your-paypal-secret'
mode: sandbox # or live
shipping:
methods:
free:
label: Free Shipping
price: 0
standard:
label: Standard Shipping
price: 5.99
express:
label: Express Shipping
price: 14.99
tax:
enabled: true
rate: 0.10 # 10%
notifications:
admin_email: admin@example.com
customer_email_subject: 'Your order confirmation'
Creating Products
Products are pages with simplecart_product: true in frontmatter:
user/pages/04.shop/01.grav-theme/default.md:
---
title: Grav Documentation Theme
simplecart_product: true
price: 49.99
compare_at: 79.99
sku: GRAV-THEME-001
type: digital
file: /downloads/grav-theme-v1.0.zip
images:
- /images/products/theme-preview.jpg
- /images/products/theme-dashboard.jpg
categories:
- themes
- documentation
tags:
- grav
- theme
- responsive
shipping:
weight: 0
requires_shipping: false
---
Product Types
Digital products:
---
type: digital
file: /downloads/ebook.pdf
---
Physical products:
---
type: physical
shipping:
weight: 1.5
dimensions:
length: 10
width: 8
height: 2
requires_shipping: true
---
Variable products (with options):
---
simplecart_product: true
price: 29.99
variants:
- name: Basic
price: 29.99
sku: LICENSE-BASIC
- name: Pro
price: 99.99
sku: LICENSE-PRO
- name: Enterprise
price: 299.99
sku: LICENSE-ENT
---
Product Listing Template
user/themes/mytheme/templates/product.html.twig:
{% extends 'default.html.twig' %}
{% block content %}
<article class="product-detail">
<div class="product-gallery">
{% for image in page.media.images %}
<img src="{{ image.cropResize(600, 600).url }}"
alt="{{ page.title }}"
loading="lazy" />
{% endfor %}
</div>
<div class="product-info">
<h1>{{ page.title }}</h1>
{% if page.header.compare_at %}
<p class="price-compare">
<s>${{ page.header.compare_at }}</s>
</p>
{% endif %}
<p class="price">${{ page.header.price }}</p>
<div class="description">
{{ page.content|raw }}
</div>
{% if page.header.simplecart_product %}
<form class="simplecart-form"
data-product="{{ page.route }}"
data-price="{{ page.header.price }}"
data-name="{{ page.title }}">
{% if page.header.variants %}
<div class="product-options">
<label for="variant">Version</label>
<select name="variant" id="variant">
{% for variant in page.header.variants %}
<option value="{{ variant.sku }}"
data-price="{{ variant.price }}">
{{ variant.name }} - ${{ variant.price }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="quantity">
<label for="quantity">Quantity</label>
<input type="number" name="quantity" id="quantity"
value="1" min="1" max="99" />
</div>
<button type="submit" class="btn btn-primary add-to-cart">
Add to Cart
</button>
</form>
{% endif %}
</div>
</article>
{% endblock %}
Cart Template
user/themes/mytheme/templates/cart.html.twig:
{% extends 'default.html.twig' %}
{% block content %}
<h1>Shopping Cart</h1>
<div id="simplecart-cart">
{% if cart.items|length > 0 %}
<table class="cart-table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
<th></th>
</tr>
</thead>
<tbody>
{% for item in cart.items %}
<tr>
<td>{{ item.name }}</td>
<td>${{ item.price }}</td>
<td>
<input type="number" value="{{ item.quantity }}"
min="1" class="cart-quantity"
data-line="{{ loop.index0 }}" />
</td>
<td>${{ item.total }}</td>
<td>
<button class="btn-remove" data-line="{{ loop.index0 }}">
Remove
</button>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="3">Subtotal</td>
<td>${{ cart.subtotal }}</td>
</tr>
{% if cart.shipping > 0 %}
<tr>
<td colspan="3">Shipping</td>
<td>${{ cart.shipping }}</td>
</tr>
{% endif %}
{% if cart.tax > 0 %}
<tr>
<td colspan="3">Tax</td>
<td>${{ cart.tax }}</td>
</tr>
{% endif %}
<tr class="total">
<td colspan="3">Total</td>
<td>${{ cart.total }}</td>
</tr>
</tfoot>
</table>
<div class="cart-actions">
<a href="/checkout" class="btn btn-primary">
Proceed to Checkout
</a>
<a href="/shop" class="btn btn-secondary">
Continue Shopping
</a>
</div>
{% else %}
<div class="empty-cart">
<p>Your cart is empty.</p>
<a href="/shop" class="btn btn-primary">Browse Products</a>
</div>
{% endif %}
</div>
{% endblock %}
Checkout Template
user/themes/mytheme/templates/checkout.html.twig:
{% extends 'default.html.twig' %}
{% block content %}
<h1>Checkout</h1>
<form id="checkout-form" method="POST">
<div class="checkout-grid">
<div class="billing-section">
<h2>Billing Details</h2>
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" name="name" id="name" required />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" name="email" id="email" required />
</div>
<div class="form-group">
<label for="address">Address</label>
<input type="text" name="address" id="address" required />
</div>
<div class="form-row">
<div class="form-group">
<label for="city">City</label>
<input type="text" name="city" id="city" required />
</div>
<div class="form-group">
<label for="zip">ZIP Code</label>
<input type="text" name="zip" id="zip" required />
</div>
</div>
</div>
<div class="payment-section">
<h2>Payment</h2>
<div id="stripe-card-element"></div>
<div id="card-errors" class="error-message"></div>
<div class="order-summary">
<h3>Order Summary</h3>
<div id="checkout-summary"></div>
</div>
<button type="submit" class="btn btn-primary btn-block">
Pay ${{ cart.total }}
</button>
</div>
</div>
</form>
{% endblock %}
Learning Path
flowchart LR
A["Web Services"] --> B["E-commerce with Grav
← You are here"]:::current
B --> C["Caching Deep Dive"]
C --> D["Performance Optimization"]
D --> E["Security"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Common Mistakes
Not setting
simplecart_product: true: Products must have this flag in frontmatter. Without it, SimpleCart does not recognize the page as a product and the "Add to Cart" button does not work.Missing payment gateway configuration: SimpleCart requires at least one payment gateway (Stripe or PayPal) to be configured. Without it, checkout fails even if the cart is valid.
Forgetting SKU for each product: SKU (Stock Keeping Unit) is required for inventory tracking and order management. Each product must have a unique SKU.
Not handling digital file delivery: For digital products, configure the file delivery method (download link, email attachment) and ensure the file path is correct.
Incorrect tax configuration: Tax rates vary by location. Set the correct tax rate for your jurisdiction and clearly show tax amounts in the cart.
Practice Questions
What frontmatter flag marks a page as a SimpleCart product? Answer:
simplecart_product: true. Without this, the page is treated as regular content and cannot be added to the cart.How do you create a product with multiple variants (e.g., different license types)? Answer: Add a
variantslist in frontmatter with each variant havingname,price, andsku. The cart template should show a variant selector.What payment gateways does SimpleCart support? Answer: Stripe and PayPal. Both require API keys configured in
simplecart.yaml. Additional gateways can be added through custom plugins.How do you handle digital product delivery? Answer: Set
type: digitalandfile: /path/to/filein product frontmatter. SimpleCart sends a download link or provides direct download after payment.Challenge: Build a complete e-commerce site with 10 products (5 digital, 5 physical). Configure Stripe as the payment gateway. Create a shopping cart with quantity controls, a checkout form with address validation, order confirmation emails, digital download delivery, admin order management, and product filtering by category. Test the entire flow from browsing to payment to delivery.
FAQ
Mini Project
Goal: Build a complete e-commerce store with 10 products.
- Install and configure SimpleCart with Stripe
- Create 10 products (mix of digital and physical)
- Configure shipping methods (free, standard, Express)
- Set up tax calculation (10% VAT)
- Create product listing and detail templates
- Build a shopping cart with quantity controls
- Build a checkout form with Stripe payment
- Configure order confirmation emails
- Set up digital download delivery
- Test the complete purchase flow and verify order storage
What's Next
Now you have e-commerce capabilities. Next, learn Caching in depth:
Continue to Lesson 35: Caching Deep Dive — Cache types, cache warming, cache busting, and performance strategies.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro