Flows
Storefront Publish Flow
Publishing a storefront and theme.
Store Storefront — Preparation & Publishing Flow
This document explains how a store prepares its public-facing website (storefront) and publishes it so customers can browse products and place orders. It covers prerequisites, the step-by-step setup sequence, all relevant endpoints, request/response shapes, and the validation rules enforced at each stage.
Overview
A storefront is the public webpage that represents a store. Once published, any visitor with the store's URL can view products and create orders — no authentication required. The store owner configures everything (branding, social links, SEO, FAQs, policies) through the authenticated store-area API before flipping the publish switch.
Admin creates / approves store
│
▼
Store gets a slug + trial subscription
│
▼
Store configures storefront settings ◄── optional but recommended
│
▼
Store adds products with prices
│
▼
Store publishes storefront
│
▼
Public URL is live ──► customers browse & order
Prerequisites (conditions that must be true before publishing)
| # | Condition | How it is satisfied |
|---|---|---|
| 1 | Store must be Approved | Admin approves via POST /api/v1/admin/stores/{id}/approve, or the admin created the store directly (auto-approved). |
| 2 | Store must have a slug | Slug is auto-generated from the store name during provisioning. If missing, contact support. |
| 3 | Store must have an active or trialing subscription | A free_trial subscription is provisioned automatically on store creation. |
| 4 | At least one product with a price should exist | Products with no price are hidden from the public storefront. |
Attempting to publish without a slug returns:
HTTP 400
{
"success": false,
"message": "A slug must be assigned before publishing. Contact support.",
"code": 400
}
Registration statuses
| Status | Write operations | Publish allowed |
|---|---|---|
Pending | Blocked | No |
Approved | Allowed | Yes (if slug present) |
Rejected | Blocked | No |
Step-by-step flow
Step 1 — Admin creates or approves the store
Option A — Admin creates store directly (pre-approved)
POST /api/v1/admin/stores
Permission: admin.stores.create
{
"name": "string",
"phone": "string",
"address": "string",
"lat": 0.0,
"lng": 0.0,
"email": "string",
"ownerFullName": "string",
"ownerPhone": "string",
"ownerPassword": "string"
}
The system automatically:
- Sets
RegistrationStatus = Approved - Generates a unique
slugfrom the store name - Provisions a
free_trialsubscription
Option B — Approve a self-registered store
POST /api/v1/admin/stores/{storeId}/approve
Permission: admin.stores.approve
{
"note": "string|null"
}
Response:
{
"success": true,
"message": "Store approved successfully",
"data": {
"store_id": "guid",
"registration_status": "approved"
}
}
Note: Admin approval does not create a subscription. A subscription is only provisioned at store creation time (steps above). If a self-registered store needs a subscription, it must be assigned manually via admin or through the upgrade flow.
Step 2 — (Optional) Pick a template
Templates pre-fill the niche, apply a color theme, and define which page sections are supported. They are optional; the store can configure everything manually.
List templates
GET /api/v1/store/storefront/templates
Permission: store.storefront.view
Response includes full template details including color presets:
{
"success": true,
"data": [
{
"id": "guid",
"key": "flora",
"name": "Flora",
"niche": 1,
"description": "string",
"themeColor": "#hex",
"thumbnailUrl": "https://...",
"previewImageUrl": "https://...",
"previewUrl": "https://live-demo.example.com/flora",
"isActive": true,
"editableColorSlots": ["primary", "secondary", "accent"],
"supportedSections": ["hero", "featured", "categories"],
"defaultColorPresetId": "guid|null",
"colorPresets": [
{
"id": "guid",
"name": "Rose Gold",
"isDefault": true,
"colors": {
"primary": "#e8b4a0", "secondary": "#f5e6df", "accent": "#c07a6a",
"background": "#ffffff", "surface": "#fdf6f3", "text": "#2c1810"
}
}
]
}
]
}
Apply a template (standalone):
POST /api/v1/store/storefront/templates/{templateId}/apply
Permission: store.storefront.manage
Response: { "template_id": "guid", "niche": "string" }
Or apply template + color preset as part of PUT /storefront:
{
"templateId": "guid",
"colorPresetId": "guid"
}
Step 2.5 — (Optional) Configure storefront sections
Templates define supported sections (hero, featured products, categories carousel, etc.). Each section can be reordered, toggled, and given per-section content.
PUT /api/v1/store/storefront/sections
Permission: store.storefront.manage
Content-Type: multipart/form-data
Request (all section fields optional except key):
{
"sections": [
{
"key": "hero",
"isEnabled": true,
"sortOrder": 1,
"title": "Welcome",
"titleAr": "أهلاً وسهلاً",
"subtitle": "Shop our latest collection",
"ctaLabel": "Shop Now",
"ctaLink": "/products"
},
{ "key": "featured", "isEnabled": true, "sortOrder": 2 }
]
}
Section images are sent as form parts named sections[n].image. Only sections included in the request are upserted — sections not mentioned are left unchanged.
Step 3 — Configure storefront settings
PUT /api/v1/store/storefront
Permission: store.storefront.manage
Content-Type: multipart/form-data
Request (all fields optional):
{
"description": "string|null",
"niche": 0,
"contactEmail": "string|null",
"whatsAppNumber": "string|null",
"telegramHandle": "string|null",
"facebookUrl": "string|null",
"instagramUrl": "string|null",
"twitterUrl": "string|null",
"seoTitle": "string|null",
"seoDescription": "string|null",
"seoKeywords": "string|null",
"faqs": [{ "question": "string", "answer": "string" }],
"policies": [{ "title": "string", "content": "string" }],
"customDomain": "string|null",
"templateId": "guid|null",
"colorPresetId": "guid|null",
"themeColors": {
"primary": "#hex", "secondary": "#hex", "accent": "#hex",
"background": "#hex", "surface": "#hex", "text": "#hex"
}
}
Image file parts: coverImage, bannerImage.
All fields are optional. Any field set to null clears that value.
Niche values
| Value | Name |
|---|---|
| 0 | General |
| 1 | Flowers |
| 2 | Electronics |
| 3 | Clothing |
| 4 | Food |
| 5 | Pharmacy |
| 6 | Beauty |
| 7 | Books |
Response:
{
"success": true,
"message": "Storefront settings updated.",
"data": {
"id": "guid",
"name": "string",
"slug": "string",
"isPublished": false,
"publicUrl": "https://{host}/api/v1/public/storefronts/{slug}",
"description": "string|null",
"logoUrl": "string|null",
"coverImageUrl": "string|null",
"niche": 0,
"bannerImageUrl": "string|null",
"contactEmail": "string|null",
"whatsAppNumber": "string|null",
"telegramHandle": "string|null",
"facebookUrl": "string|null",
"instagramUrl": "string|null",
"twitterUrl": "string|null",
"seoTitle": "string|null",
"seoDescription": "string|null",
"seoKeywords": "string|null",
"customDomain": "string|null",
"faqs": [],
"policies": [],
"templateId": "guid|null",
"colorPresetId": "guid|null"
}
}
Step 4 — Review current storefront state
GET /api/v1/store/storefront
Permission: store.storefront.view
Returns the same shape as the update response above. Use this to verify settings before publishing.
Step 5 — Publish the storefront
POST /api/v1/store/storefront/publish
Permission: store.storefront.manage
No request body required.
Success response:
HTTP 200
{
"success": true,
"message": "Storefront published.",
"data": {
"slug": "store-slug",
"is_published": true
}
}
Failure — slug not assigned:
HTTP 400
{
"success": false,
"message": "A slug must be assigned before publishing. Contact support.",
"code": 400
}
After a successful publish, the public URL is live:
GET /api/v1/public/storefronts/{slug}
Step 6 — (Optional) Unpublish
DELETE /api/v1/store/storefront/publish
Permission: store.storefront.manage
Response:
{
"success": true,
"message": "Storefront unpublished.",
"data": {
"slug": "store-slug",
"is_published": false
}
}
The public URL stops responding once unpublished.
Public storefront — customer-facing endpoints
These endpoints require no authentication. They are accessible by any visitor after the store is published.
Browse the storefront
GET /api/v1/public/storefronts/{slug}
Conditions enforced:
IsStorefrontPublished = trueSlugis not null- Store is not soft-deleted
- Only products with
price > 0are included
Response (expanded — all available fields):
{
"success": true,
"data": {
"id": "guid",
"name": "string",
"slug": "string",
"description": "string|null",
"logoUrl": "string|null",
"coverImageUrl": "string|null",
"bannerImageUrl": "string|null",
"niche": "string|null",
"template": { "key": "string", "name": "string", "niche": 0 },
"theme": {
"primary": "#hex", "secondary": "#hex", "accent": "#hex",
"background": "#hex", "surface": "#hex", "text": "#hex"
},
"categories": [
{ "id": "guid", "name": "string", "imageUrl": "string|null", "sortOrder": 0 }
],
"contact": {
"email": "string|null",
"whatsAppNumber": "string|null",
"telegramHandle": "string|null",
"facebookUrl": "string|null",
"instagramUrl": "string|null",
"twitterUrl": "string|null"
},
"faqs": [{ "question": "string", "answer": "string" }],
"policies": [{ "title": "string", "content": "string" }],
"seo": {
"seoTitle": "string|null",
"seoDescription": "string|null",
"seoKeywords": "string|null"
},
"products": [
{
"id": "guid",
"sku": "string",
"name": "string",
"description": "string|null",
"defaultUnitPrice": 0.0,
"salePrice": 0.0,
"imageUrl": "string|null",
"images": [{ "url": "string" }],
"categoryId": "guid|null",
"categoryName": "string|null",
"brand": "string|null",
"unit": "string|null",
"isFeatured": false,
"isDiscounted": false,
"isActive": true,
"sortOrder": 0
}
]
}
}
Place an order
POST /api/v1/public/storefronts/{slug}/orders
Request:
{
"customerName": "string",
"customerPhone": "string",
"customerAddress": "string",
"addressProvinceCode": "string",
"addressAreaId": "guid",
"items": [
{
"sku": "string",
"quantity": 1
}
],
"notes": "string|null",
"paymentMethod": "Cash | Transfer | Card"
}
Validations applied:
itemsmust contain at least one entry- Each
skumust belong to an active product in this store withprice > 0 customerPhonemust be a valid phone formataddressProvinceCodeandaddressAreaIdmust be valid system values
Response:
{
"success": true,
"data": {
"id": "guid",
"publicId": "string",
"status": "Pending",
"source": "Storefront",
"orderValue": 0.0
}
}
The order appears in the store's order list with source = "Storefront".
Slug generation rules
Slugs are generated automatically from the store name during provisioning. They cannot be changed by the store owner.
| Rule | Example |
|---|---|
| Lowercase all characters | Flora Baghdad → flora-baghdad |
Replace non-alphanumeric characters with - | Store & Co. → store---co- |
| Collapse multiple dashes into one | store---co- → store-co |
| Trim leading / trailing dashes | -store-co- → store-co |
Append -2, -3, etc. if name is already taken | flora-baghdad → flora-baghdad-2 |
Permissions reference
| Permission | Who has it | What it allows |
|---|---|---|
store.storefront.view | store_owner, store_staff | Read storefront settings and templates |
store.storefront.manage | store_owner | Update settings, apply templates, publish / unpublish |
admin.stores.create | admin | Create a pre-approved store |
admin.stores.approve | admin | Approve a pending store registration |
admin.stores.reject | admin | Reject a pending store registration |
Full sequence diagram
Admin Store Owner Customer
│ │ │
│ POST /admin/stores │ │
│ (or approve existing) │ │
│──────────────────────────► │ │
│ slug + trial sub created │ │
│ │ │
│ │ GET /store/storefront/templates
│ │──────────────────────►│ (optional)
│ │ │
│ │ PUT /store/storefront
│ │ (configure branding, SEO, links)
│ │ │
│ │ POST /store/catalog/products
│ │ (add products with prices)
│ │ │
│ │ POST /store/storefront/publish
│ │─────────────────────►│
│ │ { is_published: true }
│ │ │
│ │ GET /public/storefronts/{slug}
│ │◄─────────────────────│
│ │ │
│ │ POST /public/storefronts/{slug}/orders
│ │◄─────────────────────│
│ │ { id, status: Pending, source: Storefront }
Source: DOCS/flows/store-storefront-publish-flow.md