Adding a shop to a Ghost site: the one-script approach

Ghost doesn't ship with a shop. Here are the three ways people add commerce to it in 2026, why each is awkward, and what a one-script embed actually does.

Ghost doesn't ship with a shop. That's been true since 2013, and unless something dramatic happens at Ghost Foundation, it'll be true in 2030 too. The team has been pretty explicit about this. Ghost is a publishing platform, not a commerce platform. They built memberships, paid tiers, newsletters, and called it a day on monetisation.

This is fine. It's also why every Ghost writer who wants to sell a t-shirt, a PDF, an ebook, or a hardware kit ends up with the same Google search at some point: "how do I add a shop to Ghost?"

I've watched the search results for that query for two years. They are bad. The top answers are either Snipcart docs from 2021, a Ghost forum thread where someone hand-wrote a Stripe Checkout button in 2019, or some YouTube tutorial showing you how to embed a Gumroad widget that doesn't quite line up with your theme. None of those answers are wrong, exactly. They're just incomplete.

So this is the post I wish had existed when I started.

The three ways people currently add commerce to Ghost

Before I get to the one-script approach, here's the landscape as it actually exists.

Option one: paste a Stripe Payment Link. This is the path of least resistance. You go to your Stripe dashboard, you create a Payment Link, you copy the URL, you put it in a button. It works. It's also a checkout that opens on stripe.com, doesn't carry your branding past the first half-second, doesn't know what's in stock, can't bundle items, can't talk to Ghost membership tiers, and gives you nothing on the Ghost side. You sell a thing, you get an email from Stripe, you go and fulfil it. That's it.

It's fine for selling one digital product to a small list. It collapses the moment you have variants, weight-based shipping, or more than three SKUs.

Option two: embed buy buttons from Gumroad, Lemon Squeezy, or Sellfy. This is the most popular path. You sign up for one of these platforms, you upload your products there, and you embed a "Buy" button into your Ghost post. The platform handles checkout. The platform also takes between 5% and 10% of every sale, plus the Stripe fee, plus a monthly fee on top of that for the higher tiers.

The friction here isn't the embed. The embed is fine. The friction is everything around it. Your customers leave Ghost to check out. Your refunds happen on a different dashboard. Your inventory lives somewhere else. Your members from Ghost are not your customers on Gumroad. The two databases never meet.

And the take-rate, of course, compounds. On a £5,000-a-month Lemon Squeezy operation you're paying somewhere north of £4,000 a year in pure platform fee. Forever.

Option three: a dedicated cart script like Snipcart. This is the closest thing to what I'm about to describe. Snipcart was the original "drop two script tags into your HTML and you have a cart" product. It's been around forever, it works, and the Ghost docs used to point at it.

I have a whole separate post on why I built xVoid instead of using Snipcart, but the short version is: Snipcart's pricing has crept up over the years, the admin UI is dated, and it has no awareness of Ghost as a CMS. It doesn't know what a member is. It can't gate a download by Ghost tier. It can't render a product grid that pulls live from your dashboard inventory. It's a cart, in the literal sense. A thing that holds items at checkout. Everything around the cart is your problem.

What a one-script embed actually does

The thing all three options above have in common is that they're external. You're adding a thing into Ghost. The thing doesn't know about Ghost.

A one-script embed reverses the relationship. The script runs inside Ghost, knows about Ghost (your members, your tiers, your domain), and renders shop interface into whatever post or page you put it in.

Here's the entire embed:

<script src="https://cloud.xvoid.dev/shop.js" data-id="ckpd"></script>

That's it. That's the whole shop. When the page loads, the script does three things in roughly this order:

  1. It looks at the data-id attribute, fetches the product from your xVoid dashboard, and renders a card right where the script tag sits. Name, price, image, variants, stock, "Buy" button.
  2. It initialises a cart state in browser memory. Subsequent embeds on the same page (a /shop page with twelve products, for example) all talk to the same cart.
  3. It loads Stripe.js lazily, only after the customer clicks Buy. Until that click, your page weight is about 28KB compressed, which is less than most marketing-site fonts.

When the customer hits Buy, the script opens a payment sheet on your own domain (pay.yourdomain.com if you've set up a custom checkout domain, pay.xvoid.dev if you haven't), pre-filled with the line items. Stripe Connect routes the charge directly to your Stripe account. xVoid never touches the money. The customer gets a receipt. You get a Ghost-aware order in your dashboard, including which member placed it, if they were logged in.

The whole flow is roughly four hundred lines of JavaScript and a Stripe redirect. There is no React shell, no shadow DOM gymnastics, no iframe. The cart renders into your theme using your theme's CSS where possible and a small custom-property layer where it has to, which means the buy button mostly looks like the rest of your Ghost site.

What this looks like inside a Ghost post

Ghost gives you a thing called an HTML card. You hit slash in the editor, you type "html", and you get a plain text input that accepts arbitrary HTML. Anything you put in there renders inside your post wherever you placed the card. This is the trick.

A normal product paragraph in one of my posts looks like this:

The kit I'm running this weekend is below.
You can grab one here.

<!-- HTML card -->
<script src="https://cloud.xvoid.dev/shop.js" data-id="ckpd"></script>

When the post publishes, the paragraph reads as written and the script tag becomes a buy card with the CAN Keypad Kit's current price, current stock, and a working buy button. If I change the price in the xVoid dashboard, every post containing that product updates the next time someone loads the page. I never edit Ghost again.

For a dedicated /shop page, I make a Ghost page (not a post), drop in a single HTML card with one script tag per product, or one tag with no data-id attribute (which renders the full grid), and I'm done. Same embed. Different surface.

The live example

tracktuned.club is a Ghost site I run. It publishes long-form motorsport pieces. It also sells things. Stickers, a CAN keypad kit, a few prints, a paid-member tier. All of the commerce on that site is exactly one script tag, repeated in different places, with different data-ids.

If you open the page source on a sticker product post, search for cloud.xvoid.dev/shop.js, and look at what's around it, you'll find just the post body. No iframe. No popup. No "Powered by" banner. The Stripe call happens in the background. The customer never leaves the domain.

What it doesn't do

I want to be honest about the gaps, because every "here's how it works" blog post leaves them out.

The script doesn't override your Ghost search. If your reader searches your site for "keypad", they get blog posts that mention the word, not the product. Search-as-shop is a separate problem nobody on the Ghost side has tackled well yet.

The script doesn't change your Ghost theme. The buy card has its own layout. You can adjust accents through CSS custom properties, but you can't restyle the variant picker from your default.hbs. If your theme is very opinionated, the card will look slightly different from the rest of your site.

The script doesn't work inside Ghost's email newsletters. Ghost emails strip JavaScript, which is correct. Nobody wants JavaScript in their inbox. So if you want to sell from inside an email, you fall back to a plain CTA link that points readers to the post where the embed lives.

That's the honest list. Everything else, in my experience, works.

What to do next

If you write on Ghost and you've been waiting to sell something, the answer is the same regardless of which platform you pick. Paste a script tag into an HTML card. Either ours, Snipcart's, or even a raw Stripe Payment Link if your product is dead simple. The pattern is the same. The point of this post is that the pattern exists, and you don't have to leave Ghost to use it.

If you'd like the version with no take-rate, no per-product fee, and a dashboard that knows about your Ghost members, that's xVoid. There's a 7-day free trial - you can cancel any time before day 8 and won't be charged.

But mostly, the next time you Google "how do I add a shop to Ghost", I hope this post is what comes up. The answer is one script tag. Everything around that tag is decoration.