Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ollie.shop/llms.txt

Use this file to discover all available pages before exploring further.

Overview

This guide walks through building a free shipping progress bar component. You’ll learn how to:
  • Create a component in your project
  • Access checkout data with the SDK
  • Run it locally
  • Register and deploy it to your store

Prerequisites

  • The Ollie Shop CLI installed (npm install -g @ollie-shop/cli) — see Installation
  • A scaffolded project (npx create-ollie-shop) — see the Quickstart
  • Basic React knowledge
  • Access to a store
A component is a folder under components/ in your project. It is not a standalone package — ollieshop component create only registers metadata in the database; it does not scaffold a folder. The actual code lives in your project.

Create the Component

1

Create the folder

Inside your project, create components/FreeShippingBar/ with an index.tsx and a meta.json:
your-project/
├── components/
│   └── FreeShippingBar/
│       ├── index.tsx          # Component code (entry point)
│       ├── styles.module.css  # Styles (optional)
│       └── meta.json          # Component metadata (id, slot, props)
├── package.json               # Project dependencies
└── ...
2

Add meta.json

meta.json tells the tooling where the component goes. The slot is where it renders in the checkout (see Slots). Leave id out for local development — an unlinked component gets a temporary studio-* id until you register it.
meta.json
{
  "name": "Free Shipping Bar",
  "slot": "your-slot"
}

Build the Component

Create components/FreeShippingBar/index.tsx:
index.tsx
import React from 'react';
import { useCheckoutSession } from '@ollie-shop/sdk';
import styles from './styles.module.css';

export default function FreeShippingBar() {
  const { session } = useCheckoutSession();
  const { totals, locale } = session;

  const threshold = 10000; // $100 in cents
  const subtotal = totals.items;
  const remaining = threshold - subtotal;
  const progress = Math.min((subtotal / threshold) * 100, 100);

  const format = (cents: number) =>
    new Intl.NumberFormat(locale.language, {
      style: 'currency',
      currency: locale.currency,
    }).format(cents / 100);

  if (remaining <= 0) {
    return (
      <div className={`${styles.shippingBar} ${styles.success}`} role="status" aria-live="polite">
        <span>✓ Free shipping unlocked</span>
      </div>
    );
  }

  return (
    <div className={styles.shippingBar} role="status" aria-live="polite">
      <p>Add {format(remaining)} for free shipping</p>
      <div className={styles.progressTrack}>
        <div className={styles.progressFill} style={{ width: `${progress}%` }} />
      </div>
    </div>
  );
}

Add Styles

Create components/FreeShippingBar/styles.module.css:
styles.module.css
.shippingBar {
  background: #f3f4f6;
  padding: 12px;
  text-align: center;
  font-size: 14px;
}

.success {
  background: #10b981;
  color: white;
}

.progressTrack {
  background: #e5e7eb;
  height: 6px;
  border-radius: 3px;
  margin-top: 8px;
  overflow: hidden;
}

.progressFill {
  background: #3b82f6;
  height: 100%;
  transition: width 0.3s ease;
}

Test Locally

Start the local dev server from your project root:
npm start
# or, directly:
ollieshop start
This discovers your components, opens Ollie Studio in the browser, and hot-reloads as you edit.

Register & Deploy

1

Register the component

Create the component record in a store version to get its component id. You can do this in the admin dashboard, or via the CLI:
ollieshop component create \
  --version-id <version-uuid> \
  --name "Free Shipping Bar" \
  --slot your-slot
2

Deploy

Deploy bundles the local components/FreeShippingBar folder and uploads it to the builder. Pass the component id from the previous step and the folder name:
ollieshop deploy \
  --component-id <component-uuid> \
  --name FreeShippingBar \
  --wait
--wait polls until the build finishes.
3

Verify

ollieshop component list --store-id <store-uuid>

Using Checkout Data

The SDK exposes hooks for reading checkout data. useCheckoutSession returns the parsed session:
const { session } = useCheckoutSession();

session.cartItems        // Array of cart items
session.totals.items     // Subtotal of items, in cents
session.totals.shipping  // Shipping cost, in cents (optional)
session.totals.total     // Grand total, in cents
session.locale           // { currency, language, country }
session.customer         // Customer data (optional)
See the hooks reference and the CheckoutSession type for the full shape.

Adding Interactivity

Cart changes go through useCheckoutAction, not useCheckoutSession:
import { useCheckoutAction } from '@ollie-shop/sdk';

function RemoveFirstItemButton() {
  const { execute, isPending } = useCheckoutAction('REMOVE_ITEMS', {
    onError: ({ serverError }) =>
      console.error('Failed to remove item', serverError?.message),
  });

  return (
    <button onClick={() => execute([0])} disabled={isPending}>
      Remove first item
    </button>
  );
}

Best Practices

Performance

// Memoize expensive calculations derived from the session
const totalSavings = useMemo(
  () =>
    session.cartItems.reduce(
      (sum, item) => sum + (item.originalPrice - item.price) * item.quantity,
      0,
    ),
  [session.cartItems],
);

Error Handling

Surface failures to the user via useMessages:
const { addMessage } = useMessages();
const { execute } = useCheckoutAction('ADD_ITEMS', {
  onError: ({ serverError }) => addMessage({ type: 'error', error: serverError }),
});

Accessibility

<div role="status" aria-live="polite">
  {remaining > 0
    ? `${format(remaining)} until free shipping`
    : 'Free shipping unlocked'}
</div>

Next Steps

Custom Component 101

A fully custom CartItem walkthrough

API Reference

Complete SDK documentation