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
- Update stock and confirm the final value
- Use the same catalog through HTTP
- Finish the storefront and prove checkout
- Store reference
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.
- At least one active item (
frontpage store items createormanage_store). Money is integer cents only. - 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">. - Header cart chrome:
data-fp-cart-openanddata-fp-cart-count, both withdata-fp-requires="store". - 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, anddata-fp-checkout. The platform places this aside on the right. It must start hidden. - Stripe:
frontpage payments connect(admin key; finish logged-in in the browser). Do not paste astripeAccountId. - Fulfillment:
frontpage store settings set-fulfillmentafter 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 connectconnects 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 writesrc/content/products/. Do not paste astripeAccountId. A public product page is onesrc/pages/products/item.astro(orshop) withdata-fp-store-productbinds. Frontpage fills it from the live Store item. Do not type titles or prices into one file per product. Do not invent a site-pagegetShopItemshelper.
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. |
-
--siteis a subdomain slug only (example:acme). Not a custom domain, notacme.frontpage.host, not a site UUID. See Flags. -
A JSON body that includes
siteIdis 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
-
--site <slug>,-s <slug>, or--site=<slug>on this command. Wins for this process only. -
Otherwise the slug from
frontpage use <slug>in this folder. -
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-hator--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 mRestore 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 ornullfor unlimited) andchange. nullstock is unlimited.--add/--removeon unlimited is an error.- You cannot subtract below zero. Use
--set 0to mark sold out. --addand--settogether 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/.
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/restore | editor | Restore the same item ID | Restored product |
| POST | /shop/items/:itemId/variants/:variantId/restore | editor | Restore the same variant | Updated product |
| GET / POST | /shop/settings/availability | viewer / admin | Write requires enabled and confirm:true | Store availability, independent of Payments |
| POST | /shop/settings/receipt | editor | templateId; empty string selects stock receipt | Saved 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 quoteorshop checkout. - Stripe connect is
frontpage payments connect. - No site-page
getShopItemshelper. A public hats listing isdata-fp-store-gridwithdata-category="hats". A public product URL is the oneitem.astrotemplate. - No coupon codes.
If you are writing an agent
- Read this page,
frontpage help store, andget_module_docs({ module: "store" })before inventing verbs, flags, or markup. - Unknown leftover flags fail closed.
- Integer cents only.
$49.99is4999. --add 5adds 5.--remove 5removes 5.--set 20means 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) withdata-fp-store-productbinds so each product keeps its own URL. If they said no, skip that file and do not bindhreforurl. Do not use a leading underscore. Do not write one file per product.get_itemis catalog work, not paste. - Do not write
src/content/products/. Do not inventgetShopItemson the site. Do not bake titles, prices, orProductJSON-LD prices. - Do not write a pay modal,
/__shop/choose, orprompt(). 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
- Email for the buyer receipt template
- Content & settings for inbox
- Rate limits
- Command reference