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)

#ConditionHow it is satisfied
1Store must be ApprovedAdmin approves via POST /api/v1/admin/stores/{id}/approve, or the admin created the store directly (auto-approved).
2Store must have a slugSlug is auto-generated from the store name during provisioning. If missing, contact support.
3Store must have an active or trialing subscriptionA free_trial subscription is provisioned automatically on store creation.
4At least one product with a price should existProducts 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

StatusWrite operationsPublish allowed
PendingBlockedNo
ApprovedAllowedYes (if slug present)
RejectedBlockedNo

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 slug from the store name
  • Provisions a free_trial subscription

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

ValueName
0General
1Flowers
2Electronics
3Clothing
4Food
5Pharmacy
6Beauty
7Books

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 = true
  • Slug is not null
  • Store is not soft-deleted
  • Only products with price > 0 are 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:

  • items must contain at least one entry
  • Each sku must belong to an active product in this store with price > 0
  • customerPhone must be a valid phone format
  • addressProvinceCode and addressAreaId must 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.

RuleExample
Lowercase all charactersFlora Baghdadflora-baghdad
Replace non-alphanumeric characters with -Store & Co.store---co-
Collapse multiple dashes into onestore---co-store-co
Trim leading / trailing dashes-store-co-store-co
Append -2, -3, etc. if name is already takenflora-baghdadflora-baghdad-2

Permissions reference

PermissionWho has itWhat it allows
store.storefront.viewstore_owner, store_staffRead storefront settings and templates
store.storefront.managestore_ownerUpdate settings, apply templates, publish / unpublish
admin.stores.createadminCreate a pre-approved store
admin.stores.approveadminApprove a pending store registration
admin.stores.rejectadminReject 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