Developers Store

Platform

Store

Frontpage Store: live catalog, storefront markup, Stripe checkout, CLI, and the public v1 API.

Tested Store walkthroughs

Start with these copyable workflows, then use the reference for the full command and field list. Response excerpts come from tested calls; extra fields are omitted and IDs may be normalized.

Install npm i -g frontpage-host@latest, set FRONTPAGE_API_KEY on your server, and replace acme with your site slug. Site roles, CLI access, and product permissions still apply. Use IDs and versions returned by your own calls.

CLI --json wraps success in ok, command and data. HTTP returns the resource body directly.

On this page:

Create an item from a JSON file

Prerequisites: Store access, API access, and an editor key. Catalog writes save immediately; they do not publish page content. 4999 means 49.99 in a currency with two decimal places; use integer minor units, never a decimal or a currency symbol.

Save camp-hat.json. Even a product without options has a default variant. In a JSON file, price and stock belong in variants:

{
  "itemId": "camp-hat",
  "title": "Camp hat",
  "requiresShipping": true,
  "variants": [
    {
      "variantId": "default",
      "priceCents": 4999,
      "stock": 10,
      "options": {}
    }
  ]
}
frontpage store items create camp-hat --file camp-hat.json --site acme --json
{
  "ok": true,
  "command": "store",
  "data": {
    "slug": "acme",
    "item": {
      "itemId": "camp-hat",
      "title": "Camp hat",
      "checkout": "cart",
      "active": true,
      "variants": [
        {
          "variantId": "default",
          "options": {},
          "priceCents": 4999,
          "discountPriceCents": null,
          "stock": 10,
          "active": true
        }
      ]
    }
  }
}

Result: an active catalog item with ten units is saved. This does not prove the storefront is ready to take payment. Get the item to confirm its stored variants:

frontpage store items get camp-hat --site acme --json

The flag form is also supported: items create camp-hat --title "Camp hat" --price-cents 4999 --stock 10. Those flags build the variant structure. Do not put priceCents only at the top level of a --file payload.

Update stock and confirm the final value

frontpage store items stock camp-hat --add 5 --site acme --json
{
  "ok": true,
  "command": "store",
  "data": {
    "slug": "acme",
    "itemId": "camp-hat",
    "variantId": "default",
    "stockNow": 15,
    "change": "added 5"
  }
}

Our test started at 10 and read back 15. For a multi-variant item, include --variant VARIANT_ID.

Retry: adding five twice adds ten. If the response is lost, get the item and reconcile inventory before retrying. --set 15 sets an absolute value; it can overwrite another inventory update, so recheck concurrent changes. Do not assume every write has an idempotency key. An archived item retains its ID and history; restore it instead of creating another item with the same ID.

Use the same catalog through HTTP

curl --fail-with-body "https://app.frontpage.host/api/v1/sites/acme/shop/items/camp-hat"   -H "Authorization: Bearer $FRONTPAGE_API_KEY"

HTTP 200 returns the body directly; the equivalent CLI response puts this under data:

{
  "slug": "acme",
  "item": {
    "itemId": "camp-hat",
    "title": "Camp hat",
    "variants": [
      {
        "variantId": "default",
        "options": {},
        "priceCents": 4999,
        "discountPriceCents": null,
        "stock": 15,
        "active": true
      }
    ]
  }
}

This is an excerpt; full items also include IDs, categories, images, timestamps, customizations and other settings. Keep the API key out of your public storefront. Public Store markup uses the catalog binding layer, not this private owner endpoint.

Finish the storefront and prove checkout

Step Action Completion check
Catalog Create the item and read it back. Correct active variant, price and stock.
Payment connection frontpage payments connect --no-open, complete the signed-in browser flow, then frontpage payments status. The returned readiness state allows charges; a connect URL alone is insufficient.
Fulfillment Use frontpage store settings get and the fulfillment commands in this reference. At least one supported pickup/shipping method, correct shipping rules and readiness.
Page Add the complete grid/product, Add, cart, and checkout markup below. The public page binds live catalog values and opens a working cart.
Purchase Complete a test checkout on isolated data and inspect the resulting order. The order's payment state, items, currency, inventory and receipt match the purchase. A checkout URL or browser return alone does not prove payment.

The documentation test verified catalog/stock through the real handlers; it did not make a live purchase. The reference below includes copyable storefront markup, option/variant controls, custom fields, orders, receipts, and fulfillment. Store uses Payments' shared currency and account rather than a separate currency setting.

Store reference

What this is

Frontpage Store is the product list for a site. You create items, give them prices and stock, and Frontpage charges those prices at checkout. Pay buttons and the online cart read the same list. The sellable unit is the variant (the SKU): price, sale price, and stock live there. Public shipping is the store fulfillment setting, not a per-variant extra. The item is the merchandising parent: title, description, photos, and option names such as Size or Color.

An item with no real options still has one variant, id default. That is how a $50 deposit and a t-shirt in S/M/L share one set of commands.

Store is a platform product, like People. It is not a Modules card and it is not Square. Do not enable it with frontpage modules enable shop — that command does not exist. Write data-fp-store-grid / data-fp-store-product markup (see Build the storefront). Do not invent a bare data-fp-store attribute. Printify products arrive here after the owner connects in Modules. See printify. Do not insert rows into the shop tables and do not write src/content/products/.

frontpage store categories create hats --label Hats
frontpage store items list
frontpage store items create camp-hat --title "Camp hat" --price-cents 4999 --categories hats
frontpage store items list --category hats
frontpage store variants add camp-hat --id m --option Size=M --price-cents 4999 --stock 8
frontpage store items stock camp-hat --variant m --add 5
frontpage store settings get
frontpage payments connect --no-open
frontpage payments status

Use frontpage-host 0.2.1 or later. Store owns products, cart, fulfillment and orders. Payments owns Stripe setup, tax, Pay now and invoices. Upgrade with npm i -g frontpage-host@latest.

Build the storefront

The catalog lives in Store. The page is yours. Write markup; Frontpage fills live items from the Store catalog and a single product from the live Store item. Do not type titles, prices, or images into the file. Do not invent a site-page getShopItems helper. Do not write src/content/products/. Do not invent a bare data-fp-store attribute — that is not a category flag. The real attrs are data-fp-store-grid, data-fp-store-product, data-fp-store-bind, and the cart attrs below.

A fully functional shop needs all of these. Missing any one leaves visitors unable to buy.

  1. At least one active item (frontpage store items create or manage_store). Money is integer cents only.
  2. A product grid: <section data-fp-store-grid data-fp-requires="store"> with <template data-fp-product-card>, binds, CSS, and <button data-fp-add-to-cart data-fp-store-bind="itemId" data-fp-requires="store">.
  3. Header cart chrome: data-fp-cart-open and data-fp-cart-count, both with data-fp-requires="store".
  4. A cart: <aside data-fp-cart data-fp-requires="store" hidden> with <template data-fp-cart-line>, empty state, binds, empty <div data-fp-cart-method></div>, data-fp-cart-close, and data-fp-checkout. The platform places this aside on the right. It must start hidden.
  5. Stripe: frontpage payments connect (admin key; finish logged-in in the browser). Do not paste a stripeAccountId.
  6. Fulfillment: frontpage store settings set-fulfillment after charges are on (pickup and/or shipping).

Store-ready = Stripe chargesEnabled and fulfillment set. Visitor Add / Cart unhide only then. Pay buttons use data-fp-pay + data-fp-requires="pay" and unhide when charges are on (fulfillment not required). The Store owner panel is Starter+; Free hides that panel and manage_store, not a Store-ready visitor cart.

Product grid

<section data-fp-store-grid data-fp-requires="store">
  <div data-fp-store-list>
    <template data-fp-product-card>
      <img data-fp-store-bind="image" alt="" />
      <h3 data-fp-store-bind="title"></h3>
      <p data-fp-store-bind="description"></p>
      <p data-fp-store-bind="price"></p>
      <a data-fp-store-bind="href">View</a>
      <button data-fp-add-to-cart data-fp-store-bind="itemId" data-fp-requires="store">
        <span data-fp-store-bind="cta">Add to cart</span>
      </button>
    </template>
  </div>
  <p data-fp-store-empty>Nothing here yet.</p>
</section>

Card binds: itemId, title, description, descriptionHtml, descriptionExcerpt, image, price, compareAtPrice, cta, href, url, optionName, stock. Dates or invented keys stay empty. itemId stamps Add to cart or Choose options from the live product. cta is Add to cart or Choose options. href / url default to /products/{itemId} when the product template exists. Set data-fp-product-base="/shop" on the grid when the product template lives under /shop. If they said no product pages, omit href and url. Filter a listing with data-category="hats".

Search, price, and More

A grid with no numeric data-limit does not paint the whole shop. The platform asks for offset 0 and limit 24, then mounts a search box, Min and Max price boxes, and a More button. The owner does not write those controls. A shop of 253 products shows 24 cards, then More.

Search matches the title, not the description. Min and Max use the store currency inside the box ($, C$, ¥, and the rest). Typing 25 on a US store means $25. The request converts that to whole cents (min=2500). An empty end is open. The filter uses the price printed on the card, which is the first live variant. A cheaper size on a later variant does not pull the card onto the page.

More repeats the same search, category, and price bounds and appends the next 24. Changing the search or a price starts again at offset 0 and replaces the cards. Each price box has a clear control. Restyle form.fp-store-filters, .fp-store-field, .fp-store-field-label, input.fp-store-search-input, .fp-store-price, .fp-store-price-box, .fp-store-price-symbol, .fp-store-clear, .fp-store-search-go, and .fp-store-more. Remove one control with data-fp-store-search="off", data-fp-store-price="off", or data-fp-store-more="off" on the grid. A numeric data-limit="12" is one short list of 12 and gets none of those controls. data-limit="all" is not a number, so the grid still pages at 24. The server returns at most 48.

<section data-fp-store-grid data-category="hats" data-fp-store-price="off" data-fp-requires="store">
  <!-- hats only, no price boxes; search and More stay -->
</section>

The card payload does not change: title, description, images, price, and View stay. Do not hand-build the page controls. The platform stamps Add to cart or Choose options; keep the authored button and its styles. price is what the buyer pays. compareAtPrice is the usual cost and only fills when there is a sale — style it with a strikethrough. Do not swap the two binds.

Allowed exception: one featured product may use <button data-fp-add-to-cart="camp-hat" data-fp-requires="store"> when that itemId already exists. A shop or merch section must be the grid template.

The form and search input have separate hooks. .fp-store-search remains an input-only compatibility alias. Each label.fp-store-field contains a normal-flow label and either the search input or a .fp-store-price-box. Style the form for layout, the search input and price box for borders, and the inner price input for text. Avoid painting both the label and its input. Soft defaults in @layer fp-defaults provide wrapping controls with a 44px minimum height. Your CSS and utilities can override every visual property, including a sidebar layout. Set --fp-store-control-height to change the default height.

One product page

A good store has product pages. Unless they said they do not want product pages, write one src/pages/products/item.astro (or src/pages/shop/item.astro). That one template serves every kebab itemId URL. The gateway serves that template for /products/{kebabId} and /shop/{kebabId} before 404.html. An exact static file still wins. Do not use a leading underscore — Astro will not emit that file. Do not write one typed file per product.

<article data-fp-store-product data-fp-requires="store">
  <div data-fp-store-images>
    <template data-fp-store-image><img data-fp-store-bind="image" alt="" /></template>
  </div>
  <h1 data-fp-store-bind="title"></h1>
  <p data-fp-store-bind="price"></p>
  <p data-fp-store-bind="compareAtPrice"></p>
  <div data-fp-store-bind="description"></div>
  <p data-fp-store-bind="optionName"></p>
  <div data-fp-store-variants></div>
  <p data-fp-store-bind="stock"></p>
  <div data-fp-store-customizations></div>
  <button data-fp-add-to-cart data-fp-store-bind="itemId" data-fp-requires="store">
    <span data-fp-store-bind="cta">Add to cart</span>
  </button>
  <p data-fp-store-empty>This product is gone.</p>
</article>

Optional data-item-id pins an item. Otherwise the last kebab path segment is the id. Frontpage fills from the live Store item. optionName is the live version type. Live variants mount into the empty slot from optionNames in catalog order — do not invent SKUs. Shared customer questions (text, textarea, choice, checkbox, email, phone, number, date and address) mount into data-fp-store-customizations above Add to cart. stock is remaining for the selected version; omit or hide that bind to hide leftover counts. Each distinct version, choice, or typed value is its own cart line. The picker stamps data-fp-variant. Product JSON-LD and the document title come from the live item and the presentment currency when present. Do not bake a Product price on BaseLayout jsonLd. A missing item shows data-fp-store-empty and noindex.

Product description

Description is a small markdown subset stored on the item: paragraphs, **bold**, *italic*, ## / ###, - or 1. lists, and [text](url) (https, http, /, mailto:). First paragraph is the default card. Story, dimensions, and lists go below. The product-page bind is <div data-fp-store-bind="description"> so headings and lists can render; cards stay <p>. Bind descriptionHtml to show the full story on a card, or descriptionExcerpt to force the first paragraph on the product page. Do not write HTML. Do not bake the story into item.astro. CLI: --description still works; JSON --file is the multiline path.

Cart

<button data-fp-cart-open data-fp-requires="store">
  Cart (<span data-fp-cart-count>0</span>)
</button>
<aside data-fp-cart data-fp-requires="store" hidden>
  <button data-fp-cart-close aria-label="Close cart">Close</button>
  <div data-fp-cart-empty>Your cart is empty.</div>
  <div data-fp-cart-list>
    <template data-fp-cart-line>
      <img data-fp-cart-bind="image" alt="" />
      <p data-fp-cart-bind="title"></p>
      <p data-fp-cart-bind="unitPrice"></p>
      <button data-fp-cart-qty="-1">−</button>
      <span data-fp-cart-bind="qty"></span>
      <button data-fp-cart-qty="+1">+</button>
      <p data-fp-cart-bind="lineTotal"></p>
      <button data-fp-cart-remove>Remove</button>
    </template>
  </div>
  <div data-fp-cart-summary>
    <p data-fp-cart-bind="subtotal"></p>
    <div data-fp-cart-method></div>
    <p data-fp-cart-bind="shipping"></p>
    <p data-fp-cart-fee hidden>Service fee <span data-fp-cart-bind="fee"></span></p>
    <p data-fp-cart-bind="total"></p>
    <button data-fp-checkout>Checkout</button>
  </div>
</aside>

Cart binds: image, title, unitPrice, qty, lineTotal, subtotal, shipping, fee, total. Line items go in data-fp-cart-list. Totals, fee, method, and checkout go in data-fp-cart-summary so the list scrolls and checkout stays on screen. Restyle those hooks, or set data-fp-cart-scroll="off" if you own the layout. Write empty data-fp-cart-method only. The platform mounts pickup/delivery and puts the country picker inside Delivery. You may restyle .fp-cart-method-option; do not rebuild the picker. A leftover data-fp-cart-country slot is optional and stays empty. Do not write /__shop/choose, prompt(), card fields, or a pay modal. Standalone Offerings use their own Payments drawer, without Store cart markup or fulfillment controls. The Store aside must start hidden. The platform places it on the right under --fp-toolbar-offset; do not leave it in the page flow. If you omit the fee row, the platform injects a Service fee line when the quote has one. Do not invent a tax bind or a tax amount in the cart. Tax appears on Stripe Checkout, not in the drawer.

Store uses Add to cart

All Store products use cart checkout, including products saved with an old instant setting. No checkout-mode write exists. Standalone Pay now buttons require saved, registered Offerings in Payments. Open-amount products use --amount open --min-cents; their chosen amount stays on the cart line.

Optional Service fee: frontpage store settings set-checkout-fee --cents 200 (or --off). It applies to Add to cart and Pay now. Bind it as fee.

Sales tax

Tax setup now lives in Payments Settings and frontpage payments tax. It reuses the same Stripe Express account, tax settings and registrations. Currency comes from Stripe and is read-only.

Pay buttons

Use Payments Offerings for deposits, retainers, donations and one-time fees. Create the Offering first, then bind its returned data-fp-pay markup and data-fp-pay-bind="price". The saved object owns price, minimum, questions and receipt template. Store products cannot use Pay now. Never take a fixed charge from data-amount or build card fields.

Store vs Stripe vs Square

These are easy to mix up. They do different jobs.

Product What you manage Who takes payment Command
Frontpage Store Your products, prices, and stock Stripe, after you connect frontpage store …
Stripe connect The shared Stripe account and Payments currency, and (for the cart) pickup or shipping Stripe frontpage payments connect
Pay button on the page A standalone payment backed by a saved Offering in Payments. Store products use the cart; an old instant setting does not create an Offering. Stripe Saved Payments Offering plus bound data-fp-pay markup
  • frontpage payments connect connects Stripe for the cart and pay buttons. Neither command creates products.
  • Square stays supported. If they already run pickup on Square, use ordering. Do not copy a Square menu into Store.
  • Published checkout reads Store. Visitor pages fill from the Store catalog. New product work uses these commands, not src/content/products/.
  • The in-product editor can write Store with manage_store. Create a category before assigning it. Do not write src/content/products/. Do not paste a stripeAccountId. A public product page is one src/pages/products/item.astro (or shop) with data-fp-store-product binds. Frontpage fills it from the live Store item. Do not type titles or prices into one file per product. Do not invent a site-page getShopItems helper.

Who can read and write

Same site fence as push, inbox, and people. A key cannot open another tenant’s products by guessing a slug or an item id.

Who What happens
No key, or a bad key HTTP 401. CLI exit 4. Nothing is read or written.
Valid key, but you are not a member of --site HTTP 404 Site not found. Same response for an unknown slug or a deleted site.
Member with role viewer items list, items get, categories list, settings get, and payments status work. Create, update, archive, activate, images, stock, variants, categories writes, and settings writes return HTTP 403. CLI exit 4. Orders and payments need editor (same as inbox). Payments connection and availability changes need admin.
Member with role editor or admin Read and write Store on that site only. payments connect and payments availability on|off need admin.
Item id copied from site B, used with --site of site A HTTP 404 Item not found. Same public id on two sites is two products.
  • --site is a subdomain slug only (example: acme). Not a custom domain, not acme.frontpage.host, not a site UUID. See Flags.
  • A JSON body that includes siteId is ignored. The server uses the site from the slug after membership is checked.
  • Keys are account-wide. One fp_live_… acts as that user on every site they belong to.

Full 401 / 403 / 404 table: Isolation & keys.

Targeting a site

  1. --site <slug>, -s <slug>, or --site=<slug> on this command. Wins for this process only.
  2. Otherwise the slug from frontpage use <slug> in this folder.
  3. If neither is set, exit 2: No site selected. Run: frontpage use <subdomain> or pass --site <subdomain>.
frontpage use acme
frontpage store items list

frontpage store items list --site acme
frontpage store items create camp-hat --site bakery --title "Camp hat" --price-cents 4999

# Rejected locally (exit 2), never sent
frontpage store items list --site acme.frontpage.host
frontpage store items list --site https://acme.frontpage.host

Money

Every money flag is integer cents. $49.99 is 4999. Do not send 49.99. Do not send $4999. The CLI rejects those locally (exit 2) before any request.

Field Allowed What it means
priceCents Integer 50 to 10,000,000 Usual cost. $0.50 to $100,000.00. The buyer pays this unless discountPriceCents is set. Same as Price in the Store panel.
discountPriceCents null, or integer 0 to 10,000,000, and strictly less than priceCents Sale price Frontpage charges. Same as Sale price in the Store panel. Not a coupon. Not a percent. Not a “compare at” number you type higher than the price. Must be lower than priceCents. On create, omit it or use null for no sale. On update, omit it to keep the current sale; send null to remove the sale and restore the regular price. Zero means free, not sale removal. The site strikes through the usual price while a sale is active.
shippingCents Legacy response field; not writable Do not send this field. Use Store fulfillment settings and product extraShippingCents for shipping charges.
minCents Same bounds as priceCents. Required when amount is open Lowest gift / open amount the buyer may choose

Charge amount is the discount when it is set and valid, otherwise the regular price. A caller cannot send a cheaper price at checkout and have it stick. Open-amount items (gift cards) need the buyer’s chosen amountCents at checkout, at least minCents.

Item id

Public id for the product. Kebab-case, 2 to 40 characters: ^[a-z0-9][a-z0-9-]{1,39}$. Unique on that site, not across Frontpage. Examples: camp-hat, session-deposit.

  • Positional: frontpage store items get camp-hat
  • Flag: frontpage store items get --id camp-hat or --item camp-hat
  • A string that does not match the regex fails locally as Item not found.
  • A valid id that is not on this site is HTTP 404 Item not found.
  • Creating the same id twice on one site is HTTP 409 That item id already exists on this site.

Items

items list

Returns non-archived items and their variants. Bare frontpage store items is the same as list.

Flag Required What it does
--archived no Include archived items. Server query is ?archived=1.
--category hats no Only items in that live category. Unknown or archived slug returns an empty list, not 404. Server query is ?category=hats.
frontpage store items list
frontpage store items list --category hats
frontpage store items list --archived
frontpage store items list --json

JSON: { "slug": "acme", "items": [ … ] }. Empty list prints No items.

--json returns every product and every variant. On a large shop that payload is millions of tokens. Do not paste it into an AI prompt. The terminal form without --json prints one line per product. For one product's variants, description, and images, use items get.

The editor agent does not use this command to scan a catalog. It calls manage_store with action: "list_items". That returns one page, not the shop. Default and maximum limit is 25. Each row is itemId, title, active, categories, variantCount, minPriceCents, and maxPriceCents. The payload also has total, offset, limit, and truncated. Read total for a count. Pass offset (0, then 25, then 50) for the next page. Pass category to keep the page on one live slug. Pass q for a case-insensitive title match (q is at most 200 characters). Pass min and max as whole cents; a product matches when the price on the card is inside the range. The card price is the first live variant. Either end may be omitted. Those filters run before the page is cut. list_items still includes paused products and products with no live variant. A price filter needs a live variant in range. The public grid is live products only. Do not walk every page just to count. get_item with itemId is the full product. The public product grid is a different page size: 24. It uses the same offset, limit, total, and truncated fields. Do not invent a cursor or a second paging style.

items get

One item plus every variant (including inactive). Required: item id.

frontpage store items get camp-hat
frontpage store items get --id camp-hat --json

items create

Creates one item. Variants are allowed only on create. A later update that sends variants is rejected. After create, use variants add|update|archive|restore.

Defaults if you omit them: amount is fixed. Checkout always uses Add to cart. You still need a title and a price (or a --file that includes them).

Flag Required Values What it does
item id (positional, --id, or --item) yes Kebab id, 2–40 chars Public product id
--title yes (unless in --file) Text, trimmed, max 200 chars Name shown to the owner and, later, to buyers
--description no Plain text, max 8000 chars Optional body. Not HTML
--amount no (default fixed) fixed or open fixed charges the variant price. open is a gift card / buyer-chosen amount. Open requires --min-cents
--min-cents when amount is open Integer cents, 50–10,000,000 Lowest amount the buyer may choose. Illegal on fixed
--requires-shipping no (default true for fixed-price products; false for open-amount gift cards) true or false Whether this item needs a ship-to / pickup path
--option-names no Comma list, max 8 names, each max 40 chars, unique Example Size,Color. Sets option types on create. Existing products can rename option types or return to a single SKU through items update --file; see below
--categories no Comma list of live slugs, max 8 Example hats,summer. Create each category first. An unknown slug is 400 Create category hats first.
--price-cents yes unless --file already has variants Integer cents, 50–10,000,000 Builds the first variant when you are not passing a variants array in a file
--discount-cents no Integer cents, strictly less than price Sale the buyer pays on that first variant. Must be lower than --price-cents. Omit when there is no sale.
--stock no Integer count, or unlimited Counted inventory, or unlimited (services, deposits, digital). Omit to leave the server default (unlimited)
--variant-id / --variant no (default default) Kebab id, 1–40 chars Id of the first variant. Use s / m when you also pass option names
--option when the item has option names Name=Value, repeatable. Value max 80 chars Example --option Size=S --option Color=Red. Keys must match --option-names
--urls / --url / --image no Site paths or HTTPS URLs, max 12. Repeat or comma-separate Site path /__media/uploads/<this-slug>/… or an HTTPS URL already saved on this site. Upload first. No http:// or data URIs
--file no Path to a JSON object Body sent as create. Flags override file fields. File may include a variants array for several SKUs at once
# One product, no sizes
frontpage store items create session-deposit --title "Session deposit" --price-cents 5000

# Hat with Size, first SKU is S, in hats
frontpage store items create camp-hat --title "Camp hat" --requires-shipping true --option-names Size --variant-id s --option Size=S --price-cents 4999 --stock 10 --categories hats

# Gift card
frontpage store items create gift-card --title "Gift card" --amount open --min-cents 2500 --price-cents 2500

items update

Patches merchandising on an existing item. Required: item id and at least one field. Rejects a variants array; use variant commands to edit SKUs. A JSON file may set optionNames to the same number of new names to rename option types, or an empty list to return to one default SKU. Turning options on uses variants add.

Flag What it does
--title Replace the title
--description Replace the description (pass a string; empty clears to blank)
--amount fixed or open. Switching to open requires --min-cents
--min-cents New open-amount floor
--requires-shipping true or false
--active true or false. Inactive items stay in the owner list and are not for sale. Prefer items activate --off for the same job
--categories Replace the category list. Empty is not a flag; pass the slugs you want. Create them first.
--urls Replace the image list (same rules as create)
frontpage store items update camp-hat --title "Camp hat v2"
frontpage store items update camp-hat --categories hats

Unlimited inventory and product options

Use CLI 0.2.9 or later for items update --file. Explicit flags override values in the JSON file.

Inventory updates preserve stock when omitted. A whole count of 0 means sold out; null means unlimited. The CLI sends null for --stock unlimited or items stock --set unlimited. Read the saved item back before reporting success.

frontpage store variants update camp-hat --id default --stock unlimited
frontpage store items stock camp-hat --set unlimited
frontpage store items get camp-hat --json

To return an option-based product to one SKU, save {"optionNames":[]} in single-sku.json, inspect the live variants, then run:

frontpage store items get camp-hat --json
frontpage store items update camp-hat --file single-sku.json
frontpage store items get camp-hat --json

The existing first live variant supplies the default SKU’s regular price, sale price and stock. No sale remains no sale; unlimited remains unlimited. Other live variants are archived. Rename option types with a same-length optionNames list in the JSON file. Keep the product ID and history; do not recreate it.

The website agent uses manage_store with stock:null or adjust_stock set:null. Its update_variant operation rejects unsupported fields and empty changes before saving. Use discountPriceCents, not discount_price_cents. This validation does not change the HTTP or CLI field contract.

Restore archived products and variants

frontpage store items restore camp-hat
frontpage store variants restore camp-hat --id m

Restore reuses the same IDs and history. It does not restore stock, reconnect Stripe or rewrite pages.

items archive

Soft-deletes the item. It disappears from the default list. It is not for sale. The id stays taken on that site (a new create with the same id is 409). Required: item id.

frontpage store items archive camp-hat

items activate

Sets active without archiving. Required: item id.

Flag What it does
(none) Turns the item on (active: true)
--off Turns the item off. Still listed for the owner. Not for sale
--active true|false Same as on / off. Do not combine with --off unless you mean off
frontpage store items activate camp-hat --off
frontpage store items activate camp-hat

items images

Replaces the whole image list. Required: item id. Each image is a site path /__media/uploads/<this-slug>/… or an HTTPS URL already saved on this site. Max 12, each max 2000 characters. Passing no URLs stores an empty list.

frontpage store items images camp-hat --urls https://example.com/hat.webp
frontpage store items images camp-hat --url https://a.example/1.webp --url https://a.example/2.webp

Stock

items stock changes how many are left. Pass exactly one of --add, --remove, or --set.

Flag Required What the server stores Result copy
--add N one of the three delta: N (N must be a positive integer) added 5
--remove N one of the three delta: -N removed 5
--set N one of the three set: N. --set 0 is sold out. --set unlimited or --set null is unlimited set to 20 or set to unlimited
--variant / --variant-id when the item has options Which SKU to change. Optionless items use default
  • JSON always includes stockNow (number or null for unlimited) and change.
  • null stock is unlimited. --add / --remove on unlimited is an error.
  • You cannot subtract below zero. Use --set 0 to mark sold out.
  • --add and --set together fail locally (exit 2). No request is sent.
frontpage store items stock camp-hat --variant m --add 5
frontpage store items stock camp-hat --variant m --remove 5
frontpage store items stock camp-hat --variant m --set 20
frontpage store items stock session-deposit --set unlimited

After --add 5 on a variant that had 4, change is added 5 and stockNow is 9.

Categories

Hats vs coats. Not Size or Color. Those are optionNames on one product. Create the category on the site first, then assign its slug to items. An item can be in more than one (hats and summer). An item with no categories still appears in the unfiltered list. It does not appear on --category hats.

Slug uses the same kebab rule as itemId. Label is owner-facing, max 60 characters. Cap: 32 live categories per site, 8 per item. Archive drops the filter; items keep the slug in the database until you remove it, and reads strip archived slugs so a Hats list does not show a dead chip.

Assigning a slug that was never created is 400 Create category hats first. Assigning an archived slug is 400. We do not auto-create tags.

These filters are for the CLI, v1, cookie, and manage_store. A public hats listing is a normal data-fp-store-grid with data-category="hats". A public product URL is served by the one item template. Do not invent a site-page getShopItems helper. Square menu categories stay inside data-fp-order-menu.

frontpage store categories list
frontpage store categories create hats --label Hats
frontpage store categories create coats --label Coats
frontpage store categories rename hats --label Hats
frontpage store categories archive summer
frontpage store items create camp-hat --title "Camp hat" --price-cents 4999 --categories hats,summer
frontpage store items update camp-hat --categories hats
frontpage store items list --category hats

Variants

Variant ids may be one character (s, m, xl), 1 to 40 chars, same kebab rules as items except the first character can stand alone. For these verbs the item is positional or --item. --id is the variant, not the item.

variants add

Flag Required What it does
item id yes Parent product
--id / --variant yes New SKU id
--price-cents yes Regular price
--option Name=Value when the item has option names Must include every option name exactly once
--discount-cents no Sale price
--stock no Count or unlimited
--active no (default true) true or false
frontpage store variants add camp-hat --id m --option Size=M --price-cents 4999 --discount-cents 3999 --stock 4

variants update

Patch one SKU. Required: item id, --id (variant), and at least one field. Same money / stock / option / active flags as add. Use --clear-discount (CLI 0.2.7+) to end a sale without changing the regular price. Do not combine it with --discount-cents. Omitting both preserves the current sale.

frontpage store variants update camp-hat --id m --price-cents 5499
frontpage store variants update camp-hat --id m --stock unlimited
frontpage store variants update camp-hat --id m --active false
frontpage store variants update camp-hat --id m --clear-discount
frontpage store items get camp-hat --json

For a product without named variants, use --id default. When ending a sale across the catalog, clear each live variant and read it back. Store prices change immediately; removing a banner or publishing page content does not clear a sale.

The equivalent HTTP request is PATCH /api/v1/sites/:slug/shop/items/:itemId/variants/:variantId with this body:

{"discountPriceCents": null}

variants archive

Soft-deletes a SKU. You cannot archive the last live variant (400 Cannot archive the last variant.).

frontpage store variants archive camp-hat --id s

Settings

Fulfillment, order receipt and Service fee live in Store settings. Payments owns the shared currency saved during the existing Stripe setup; Store reads it and checkout is cart-only. They do not accept a Stripe account id. Connect Stripe with frontpage payments connect.

settings get

No extra flags. Viewer allowed. JSON: { "slug", "settings": { fulfillment, currency, storeCheckout, checkoutFee } }.

frontpage store settings get
frontpage store settings get --json

settings set-fulfillment

Flag Required Values What it does
--mode / --shipping yes pickup, free-shipping, flat-rate, per-country How customers get orders. Also pass --pickup / --delivery and pickup place fields when both are on
--pickup / --delivery no Bare flag means true Turn pickup and/or delivery on. Pickup copy is --pickup-location, --pickup-address, --pickup-hours, --pickup-instructions
--flat-cents when mode is flat-rate Integer cents One shipping price for every country. $5.00 is 500
--country-rates when mode is per-country (unless rest-of-world is on) JSON array [{"country":"US","rateCents":800}] country is ISO 3166-1 alpha-2. Rate is integer cents
--rest-of-world no Bare flag means true. false / true also work Catch-all for countries not in --country-rates
--rest-cents when rest-of-world is on Integer cents That catch-all price
--free-over no Bare flag means true Waive paid shipping when the cart reaches a subtotal
--free-over-cents when free-over is on Integer cents, 1–1,000,000 That subtotal. $100.00 is 10000
frontpage store settings set-fulfillment --mode pickup --pickup-location "The shop" --pickup-address "100 Main St"
frontpage store settings set-fulfillment --pickup --delivery --mode flat-rate --flat-cents 500 --pickup-location "The shop"
frontpage store settings set-fulfillment --mode flat-rate --flat-cents 500
frontpage store settings set-fulfillment --mode per-country --country-rates '[{"country":"US","rateCents":800}]' --rest-of-world --rest-cents 1500

Availability and receipts

frontpage store settings availability get
frontpage store settings availability off --yes
frontpage store settings availability on --yes
frontpage store settings set-receipt --stock
frontpage store settings set-receipt --template <template-id>

Store off stops Store browsing/cart without stopping independent Pay now or invoices. Restore does not turn Payments on. Receipt selection reuses saved Email templates; it does not send an email.

settings set-checkout-fee

Optional Service fee on Add to cart and Pay now. Flat mode uses --mode flat --cents 200 ($2.00). Percent mode uses --mode percent --bps 300 (3%). Use --off to disable it. Bind the amount as data-fp-cart-bind="fee".

frontpage store settings set-checkout-fee --mode flat --cents 200
frontpage store settings set-checkout-fee --mode percent --bps 300
frontpage store settings set-checkout-fee --off

Payments setup

Use frontpage payments connect --no-open and frontpage payments status. The same Stripe Express onboarding and owner guidance emails remain in use. Payments availability pauses new charging and reconciles invoices/recurring billing; Store availability affects only Store. See Payments.

Orders

These read the same ledgers as frontpage inbox orders and frontpage inbox payments. They do not create orders. Editor role required.

Choose the buyer receipt in Store settings or with frontpage store settings set-receipt. Stock receipt is the default. One saved template can replace it. How the tokens fill, and how those sends count, is in Email.

frontpage store orders list
frontpage store orders get 11111111-1111-1111-1111-111111111111
frontpage payments activity list
frontpage payments activity get 11111111-1111-1111-1111-111111111111 --source payment

Get requires a row UUID. A missing or foreign id is 404. List is newest first, capped at 200.

Public v1 API

Base: https://app.frontpage.host/api/v1/sites/:slug/shop. Bearer fp_live_…. Same envelope as the rest of v1. Reads are RL-READ. Writes are RL-WRITE-LIGHT. Writes are audited (site.shop.item.create, .update, .archive, .activate, .images, .stock, site.shop.variant.add, .update, .archive, plus fulfillment, checkout-fee, receipt, availability and restore writes).

Method Path Role Body / query Returns
GET /shop/items viewer ?archived=1 and ?category=hats optional { slug, items }
POST /shop/items editor Item fields plus optional variants and categories. 201 { slug, item }. Duplicate id: 409
GET /shop/items/:itemId viewer { slug, item }
PATCH /shop/items/:itemId editor Merchandising only. variants is 400. categories replaces the list { slug, item }
POST /shop/items/:itemId/archive editor Empty object { slug, item }
POST /shop/items/:itemId/activate editor { "active": true|false } (default true) { slug, item }
PUT /shop/items/:itemId/images editor { "images": ["https://…"] } { slug, item }
POST /shop/items/:itemId/stock editor { "delta": 5 } or { "set": 20 }, optional variantId { slug, itemId, variantId, stockNow, change }
POST /shop/items/:itemId/variants editor Variant object { slug, item }
PATCH /shop/items/:itemId/variants/:variantId editor Variant patch { slug, item }
POST /shop/items/:itemId/variants/:variantId/archive editor Empty object { slug, item }
GET /shop/categories viewer ?archived=1 optional { slug, categories }
POST /shop/categories editor { "slug": "hats", "label": "Hats" }. 201 { slug, category }. Duplicate: 409
PATCH /shop/categories/:slug editor { "label": "Hats" } { slug, category }
POST /shop/categories/:slug/archive editor Empty object { slug, category }
GET /shop/settings viewer { slug, settings }
POST /shop/settings/fulfillment editor See set-fulfillment flags { slug, settings }
POST /shop/settings/checkout-fee editor { "enabled": true, "mode": "flat", "cents": 200 } or { "enabled": true, "mode": "percent", "bps": 300 } { slug, settings }
GET /shop/orders and /shop/orders/:id editor Orders ledger
POST/shop/items/:itemId/restoreeditorRestore the same item IDRestored product
POST/shop/items/:itemId/variants/:variantId/restoreeditorRestore the same variantUpdated product
GET / POST/shop/settings/availabilityviewer / adminWrite requires enabled and confirm:trueStore availability, independent of Payments
POST/shop/settings/receipteditortemplateId; empty string selects stock receiptSaved settings; no email sent

Response shapes

{
  "slug": "acme",
  "item": {
    "itemId": "camp-hat",
    "title": "Camp hat",
    "checkout": "cart",
    "amount": "fixed",
    "requiresShipping": true,
    "active": true,
    "optionNames": ["Size"],
    "categories": ["hats"],
    "variants": [
      {
        "variantId": "m",
        "options": { "Size": "M" },
        "priceCents": 4999,
        "discountPriceCents": 3999,
        "shippingCents": 0,
        "stock": 20,
        "active": true
      }
    ]
  }
}
{
  "slug": "acme",
  "itemId": "camp-hat",
  "variantId": "m",
  "stockNow": 25,
  "change": "added 5"
}

Worked example

frontpage use acme
frontpage store categories create hats --label Hats
frontpage store items create camp-hat --title "Camp hat" --requires-shipping true --option-names Size --variant-id s --option Size=S --price-cents 4999 --stock 10 --categories hats
frontpage store variants add camp-hat --id m --option Size=M --price-cents 4999 --discount-cents 3999 --stock 4
frontpage store items stock camp-hat --variant m --add 5
frontpage store items list --json
frontpage store items get camp-hat
frontpage store settings set-fulfillment --mode pickup
frontpage payments connect --no-open
frontpage payments status

Errors

What you did HTTP / CLI Typical message
--price-cents 49.99 or $4999 exit 2, no request $49.99 is 4999
Assign hats before creating the category 400 / exit 2 Create category hats first.
--add and --set together exit 2, no request Pass either --add, --remove, or --set
Unknown leftover flag exit 2, no request Unknown flag
No site selected exit 2 No site selected.
Bad or missing API key 401 / exit 4 Unauthorized
Viewer create or stock 403 / exit 4 This action requires editor access
Slug you do not belong to 404 / exit 4 Site not found.
Item id not on this site 404 / exit 4 Item not found.
Duplicate item id 409 / exit 3 That item id already exists on this site.
Discount not lower than regular 400 / exit 2 discountPriceCents must be lower than priceCents.
Subtract below zero 400 / exit 2 Cannot subtract stock below zero.
Delta on unlimited stock 400 / exit 2 Cannot apply delta to unlimited stock.
Archive the last variant 400 / exit 2 Cannot archive the last variant.
Change the number of option types without returning to one SKU 400 / exit 2 Use a same-length optionNames list to rename types, or an empty list to return to one SKU. Keep the product ID.
Rate limit 429 / exit 2 rate_limited. Honor Retry-After

Not in this CLI

  • No shop quote or shop checkout.
  • Stripe connect is frontpage payments connect.
  • No site-page getShopItems helper. A public hats listing is data-fp-store-grid with data-category="hats". A public product URL is the one item.astro template.
  • No coupon codes.

If you are writing an agent

  • Read this page, frontpage help store, and get_module_docs({ module: "store" }) before inventing verbs, flags, or markup.
  • Unknown leftover flags fail closed.
  • Integer cents only. $49.99 is 4999.
  • --add 5 adds 5. --remove 5 removes 5. --set 20 means 20 left.
  • Create a category before assigning it. Unknown slug is 400, not an auto-create.
  • Store is cart-only. Read Payments docs for standalone Pay now and invoices.
  • Before you add products, edit products, or write store pages, call get_module_docs({ module: "store" }).
  • When they ask to add, show, or build a store: list items (create if empty), then write both templates plus CSS on the current page in that same turn. The cart aside starts hidden. Then connect Stripe once if needed, then set fulfillment.
  • Unless they said they do not want product pages: write one src/pages/products/item.astro (or shop) with data-fp-store-product binds so each product keeps its own URL. If they said no, skip that file and do not bind href or url. Do not use a leading underscore. Do not write one file per product. get_item is catalog work, not paste.
  • Do not write src/content/products/. Do not invent getShopItems on the site. Do not bake titles, prices, or Product JSON-LD prices.
  • Do not write a pay modal, /__shop/choose, or prompt(). Standalone Offerings use the independent Payments drawer; never add a Store cart for them.
  • Do not paste a stripeAccountId.
  • Do not treat inbox rows as products.
  • Do not publish unless the user asked. Catalog writes do not change HTML. Markup writes need a compile; then publish only if they asked.

Save a complete product edit

With CLI 0.3.5 or later, frontpage store items update ITEM_ID --file edit.json saves changed product fields and variant edits together. It uses the existing PATCH /api/v1/sites/SLUG/shop/items/ITEM_ID. Send only changed fields. One transaction saves all local changes or none; ordinary fields use the last successful save.

{
  "title": "Camp hat",
  "variantUpdates": [
    {
      "variantId": "default",
      "priceCents": 1300,
      "stock": 3,
      "expectedStock": 5
    }
  ]
}

Read items get first. Each variantUpdates entry needs a live variantId and changed fields: priceCents, discountPriceCents, options, active or stock. Prices use integer cents. Null clears a discount or sets unlimited stock. Omitted fields stay untouched.

An explicit stock change requires expectedStock, copied from the saved count or null for unlimited. If the current count differs and the target would change it, the whole edit returns 409 without saving. Read the product and review stock before retrying; never silently replace the expected count. A target already equal to current stock is a no-op, so repeating a completed target after a lost response does not overwrite inventory.

To turn versions on in the same edit, send optionNames plus firstVariant with the normal create-variant fields. Do not combine it with variantUpdates. Turning versions off uses optionNames:[]; any included variant changes then address default. Existing individual variant and stock commands remain available. Provider-owned options and delivery remain managed through their provider.

The internal agent uses the same fields on manage_store update_item. Site, role and plan gates still apply. IDs and order history do not change. The response contains the saved product; a later list refresh is separate from that save.

Also see