Product CMS
The Product CMS block is your store's back-office — a full product management interface embedded directly in your document. From a single block you can create and edit products, manage variants, upload images, publish product pages, connect Stripe for payments, configure transactional email via Resend, organise a category hierarchy, and track orders.
Overview
The block renders a dark panel with three tabs across the top: Products, Categories, and Orders. Two integration buttons — Email and Stripe — live in the top-right corner alongside the New product button.
Access model
Every server action in the Product CMS uses resolveProductOwnerId to determine which store to operate on. This means:
- Product owner — always has full access to their own store.
- Document members — authenticated collaborators invited to a document containing this block. They operate as the owner and see the same store.
- Unauthenticated users — server actions reject the request immediately. The block renders the compact read-only summary.
- Authenticated non-members — the resolver falls back to their own user ID, so they see their own (likely empty) store, not the owner's.
The readOnlyprop on the block component is the editor's global read-only flag — it is true on any published public page, which causes the compact summary to render for all visitors. It is not an ownership check.
Adding the block
/ on any empty paragraph, type product cms, press Enter.+ on any block → scroll to Commerce → Product CMS.The block loads its product list from a shared SWR cache — the same cache used by the Product Catalog and Related Products blocks. Changes made in the CMS reflect immediately across all commerce blocks on the page without a reload.
Products tab
Product list
The left panel shows all your products as a scrollable list of cards. Each card displays the primary image, product name, category, price, and status badge. Clicking a card opens the product detail panel on the right.
Product statuses
Every product is in one of three states that control its visibility and what actions are available:
Creating products
Click New product in the top-right corner. A blank product is created immediately in draft status and the product detail panel opens. Start filling in the name, price, and variants — the block auto-saves changes as you type with a 600ms debounce per field.
Product detail
Clicking a product in the list opens its detail panel on the right. The panel is divided into logical sections: status, core fields, description, images, variants, and Stripe sync.
Core fields
Images
The image strip supports multiple images per product. The first image uploaded automatically becomes the primary image — the one shown in the product list, catalog, and related products blocks.
The primary image is marked with a blue checkmark badge in the top-right corner of its thumbnail. The blue border also distinguishes it from secondary images.
Variants
Variants represent purchasable options — sizes, colors, bundles, or any dimension that changes the price or SKU. Every product needs at least one variant before it can be published.
| Column | Description |
|---|---|
| Label | The variant name shown to customers — e.g. "Small / Black" or "Bundle of 3". Editable inline. |
| Price | Per-variant price in dollars. Stored in cents. If blank, the product's base price is used. Stripe sync uses this price. |
| Size | Optional size string — "S", "M", "L", "XL", "One size", etc. |
| Color | Optional color string. |
| Stock | Optional inventory count. Informational only — not enforced at checkout. |
| SKU | Stock Keeping Unit. Optional identifier for your own inventory tracking. |
| ● | Green dot = synced to Stripe. Grey dot = not yet synced. |
| ★ | Gold star = default variant. The default variant is the one pre-selected on the product page. |
Variant actions
Publish lifecycle
The status panel at the top of every product detail panel drives the publish lifecycle. It shows the current status, what that means, any blocking issues, and the available action buttons.
Publish validation
Before a product can be published, four checks must pass. The panel shows them as a live checklist — it updates instantly as you edit, with no server round-trip:
- The product must have a name.
- The product must have at least one variant.
- At least one variant (or the base price) must have a price set.
- At least one variant must be synced to Stripe — the green dot in the variant table must appear on at least one row.
Publish actions by status
| Current status | Available actions |
|---|---|
| Draft | Publish product — creates a public product page, sets status to Active. Disabled if validation checklist has unresolved issues. |
| Active | Unpublish — sets status to Draft. The product page still exists but becomes inaccessible. Archive — hides the product with a toast confirmation. |
| Archived | Restore to draft — sets status to Draft. Publish directly — publishes directly from archived if validation passes. |
Product page
Publishing creates a dedicated product page in your blnk document tree. The page inherits the visual style (background color, font, content width) from the document that contains the Product CMS block.
Once published, the status panel shows two links:
Currency
Currency is controlled at two levels, accessible via Currency settingsat the bottom of the status panel:
MYR, USD). Stripe uses the currency set on the variant price at sync time — if you change currency after syncing, re-sync to update Stripe's price objects.Stripe integration
Click the Stripe button in the top-right corner of the block to open the Stripe settings panel. This is where you connect your account and sync products.
Connecting Stripe
You are redirected to Stripe's OAuth flow. Sign in to your existing Stripe account or create a new one.
Stripe redirects back to your blnk page with a stripe_connected=true query parameter. The panel detects this and loads your account status automatically.
The panel shows your Stripe account ID and the date connected. Funds from purchases go directly to your Stripe account — blnk never holds payments.
Syncing products
After connecting Stripe, click Sync products to register your products and variant prices with Stripe. This creates Stripe Product and Price objects for each variant that has a price set.
The sync result panel shows:
- Synced N variants — new variants registered with Stripe for the first time.
- Updated N prices — existing Stripe price objects updated because the variant price changed.
- Everything is up to date — no changes needed.
- Error lines for any variants that failed to sync.
After a successful sync, the green sync dots appear on variants in the detail panel, and the X/N synced count updates in the Stripe section of the product detail.
Disconnecting
Click Disconnect Stripe account at the bottom of the connected state. Disconnecting removes the OAuth token — your Stripe account and its history are unaffected. You can reconnect at any time.
Email integration (Resend)
Click the Email button to open the Resend settings panel. Resend sends transactional emails (order confirmations, shipping notifications) and powers your email campaign list.
Connecting Resend
Get your API key from resend.com/api-keys. The field is a password input — click the eye icon to reveal. Tab out or click away to trigger domain fetching.
The panel calls Resend's API with your key and lists your verified domains in the From email field. Unverified domains are filtered out.
Enter the name recipients will see in their inbox (e.g. “Sarawak Travellers”) and choose an email prefix (e.g. hello) combined with one of your verified domains.
The panel saves your API key, from name, and from email. The button is disabled until all fields are filled and a domain is selected.
Editing the sender
Once connected, click Editnext to the “Sending from” section to update the display name or from email address (the account's Default sender). Changes take effect on the next email send — no reconnection needed.
Multiple sender identities
Beyond the single Default sender, you can add named sender identities (e.g. orders@, support@, founder@) in the same panel, then assign one to each of three purposes: Orders & shipping, Campaigns, and Welcome emails. Any purpose left unassigned falls back to the account's Default sender automatically — deleting an identity currently assigned to a purpose reverts that purpose to Default rather than breaking sends.
In Domain Inbox, replying to or composing a new email also shows a From dropdown of your saved identities, pre-selected to whichever identity's address matches the thread you're replying to (or the account Default for a fresh conversation).
Domains
The connected state shows all domains on your Resend account with their verification status — verified or pending. Click Refreshto re-fetch domain status after adding a new domain in Resend's dashboard.
Resend requires a verified domain to send email. If no domains are verified, the panel links directly to resend.com/domains.
Categories tab
Categories organise your products and determine their URL slug hierarchy. Click the Categories tab to manage them.
Hierarchy
Categories support 2 levels: top-level parents and their direct children (subcategories). This hierarchy determines the URL slug for every product in that category:
| Category path | Product URL slug |
|---|---|
| No category | /product-name |
| Clothing | /clothing/product-name |
| Clothing → T-Shirts | /clothing/t-shirts/product-name |
| Accessories | /accessories/product-name |
Slugs are built from the category name by lowercasing and replacing spaces with hyphens. The slug preview at the top of the category panel shows live examples from your actual category names.
Category actions
Orders tab
The Orders tab shows all orders placed through your store. The tab label shows the count of unfulfilled orders as a badge so you can spot pending fulfilment at a glance.
Each order shows the customer details, line items, payment status, and fulfilment status. You can mark orders as fulfilled or refunded directly from this tab. Order stats (total revenue, total orders, paid/unfulfilled/refunded counts) appear in a summary strip at the top.
Order confirmation email status
Each order also tracks whether its confirmation email (and, once shipped, its shipping notification) actually sent. Every order row shows a small Sent, Failed, or Not sent yet badge next to a Resend button — click it to manually re-send that email (for example, after reconnecting Resend if it was disconnected when the order first came in).
The summary strip at the top includes an Email failedstat tile showing how many orders currently have a failed confirmation or shipping email, and the Orders tab's filter dropdown includes a “Failed” option to isolate them.
Read-only mode
When a document is viewed on a public page, or by an authenticated user with no membership relationship to the store — a visitor, or in preview mode — the block renders a compact read-only summary:
- A shopping bag icon and the total product count.
- Up to 5 products listed with their primary image thumbnail, name, and status badge.
- No editing controls, no tab navigation, no integration panels.
This means the CMS block is safe to leave on any page — non-owners will never see your inventory management interface.
Setup checklist
Click Stripe → Connect your Stripe account. Required for checkout to work.
Click Email → paste your API key → select a verified domain → connect. Required for order confirmation emails.
Go to the Categories tab and build your hierarchy. Do this before creating products so you can assign categories during product creation.
Click New product → fill in name, category, base price, description, images, and at least one variant with a price.
Open the Stripe panel and click Sync products. Wait for the sync result. Green dots should appear on your variants.
Open each product, verify the checklist passes, and click Publish product. Copy the public URL and test the checkout flow.