v1.4.0 is ready - local pickup, location-aware discovery, shipping zones, and a rebuilt mobile storefront.Read update guide
logo
DocsDocumentation
⌘ K
Documentation·Store setup

Storify Setup & Installation

Everything you need to install, configure, and deploy Storify - from your first pnpm install to AI, payments, messaging, marketplace billing, POS, and production hosting.

Chapter 01 · Overview

Welcome to Storify

Storify is a production-ready AI-powered ecommerce platform built with Next.js 16, React 19, Tailwind CSS 4, MongoDB, and Better Auth. This documentation covers setup, configuration, payments, AI, omnichannel messaging, marketplace billing, POS, localization, deployment, and ongoing maintenance for release 1.3.1.

What's in the package

Full source code, storefront, admin dashboard, vendor dashboard, staff and POS workflows, database models, seed and migration scripts, operating docs under /docs, and PWA assets.

Commerce modes

Run a single-vendor store or enable marketplace mode with a vendor registration wizard, approval, commissions, payouts, vendor dashboards, and optional paid vendor subscription plans.

AI Studio & Sales Agent

A storefront AI Sales Agent plus a dashboard AI Studio that writes product, category, collection, brand, and blog copy, SEO metadata, and generates or edits images.

Omnichannel inbox

Storefront live chat unified with WhatsApp, Facebook Messenger, Instagram Direct, and Telegram in one conversation model with assignment, templates, and escalation.

Payments

Stripe, PayPal, Razorpay, Paystack, Pesapal, ioTec, and cash-on-delivery are built into checkout and admin settings.

POS, inventory & barcodes

A responsive POS register with held orders and receipt printing, multi-location inventory, transfers, pre-orders, EAN/UPC/GTIN barcodes, and thermal label printing.

Physical & digital products

Variants with visual swatches, global variant templates, 3D models, external video embeds, size guides, and digital products with private downloads and free samples.

Storage choices

Local disk, Cloudflare R2, or any S3-compatible bucket, with a provider-aware Media Library and automatic WebP conversion for uploaded images.

Recommended first path

Start with Quickstart, then Environment Variables, then Database Setup and Admin & Roles. Once you can log in, work through Settings Reference from the admin dashboard.

Upgrading from an older release

If you already run 1.0, 1.1, 1.2, or 1.3, read Updates & Migrations before deploying. Several releases ship index and data migrations that must run once against your existing database.

Chapter 02 · Quickstart

Get running in 5 minutes

If you already have Node, pnpm, and MongoDB ready, this is the fastest path to a working Storify installation. Each step is expanded later in the docs.

  1. 1

    Install dependencies

    From the unzipped Storify source folder, install all packages with pnpm.

    bash
    cd storify
    pnpm install
  2. 2

    Create the env file

    Copy the bundled .env.example to .env, then set MONGODB_URI and generate BETTER_AUTH_SECRET. Payment, SMTP, OAuth, analytics, storage, and messaging credentials can also be saved later from Admin -> Settings, which always wins over .env.

    bash
    cp .env.example .env
    openssl rand -base64 32   # paste into BETTER_AUTH_SECRET
  3. 3

    Connect MongoDB and seed

    With MONGODB_URI set, load demo store data so you can explore admin, vendor, customer, products, orders, POS, inbox, and settings immediately.

    bash
    pnpm db:seed
  4. 4

    Start the dev server

    Open http://localhost:3000. The seed command prints demo credentials in your terminal. Change all demo passwords before production.

    bash
    pnpm dev

BETTER_AUTH_SECRET is mandatory

A production build refuses to start while the secret is empty, a known placeholder, or shorter than 32 characters. Generate a real value before pnpm build.

Chapter 03 · Requirements

System & runtime requirements

Storify runs anywhere modern Node.js apps can run. These versions match the bundled package and are the supported baseline for buyer installs.

Node.js 20.19.28+

Use Node 20 LTS or newer. The app uses Next.js 16.2, React 19.2, and the React Compiler, so Node 18 is not supported.

pnpm 10.24.0

The package pins pnpm in packageManager and engines. Enable it with corepack, then run pnpm install from the project root.

MongoDB 6+ (7 recommended)

Mongoose 9 and the MongoDB 7 driver ship in the package. Atlas is recommended; self-hosted works when MONGODB_URI and MONGODB_DB_NAME are correct.

Storage (choose one)

Local disk for a single persistent server, or Cloudflare R2 / any S3-compatible bucket for product images, 3D models, blog and category media, chat attachments, and digital files.

A scheduler for cron routes

Email delivery retries, vendor subscription billing, the messaging outbox, and escalations run as authenticated HTTP GET routes. Vercel Cron is preconfigured; on a VPS use system cron.

Optional third-party services

OpenAI for AI features, a Meta developer app for WhatsApp/Messenger/Instagram, a Telegram bot, QZ Tray for direct thermal printing, and an SMTP provider for transactional email.

Hosting suggestion

For most buyers, Vercel + MongoDB Atlas + Cloudflare R2 is the fastest path. VPS and Docker are covered below, and are the better choice if you want the Local storage provider.

Local storage needs a persistent disk

The Local provider writes under the app's upload directory. It suits a single VPS or container with a mounted volume, not serverless or multi-instance deployments where the filesystem is ephemeral.

Chapter 04 · Download & Setup

Get the source running locally

Download from CodeCanyon, unzip the package, and install dependencies. Storify ships as a ready-to-run Next.js project.

  1. 1

    Download from CodeCanyon

    Sign in to CodeCanyon -> Downloads -> Storify. Choose 'All files & documentation'.

  2. 2

    Unzip the package

    Extract the archive to a permanent location. The main application folder is usually /storify, and the bundled operating guides live in /storify/docs.

    bash
    unzip storify-v1.3.1.zip -d ~/projects/
    cd ~/projects/storify
  3. 3

    Install dependencies

    Storify uses pnpm for fast, deterministic installs. Run this once after every download or update.

    bash
    corepack enable
    pnpm install
  4. 4

    Verify the install

    Run lint, types, tests, and a production build before deployment. All four are the smoke check the package is shipped against.

    bash
    pnpm lint
    pnpm typecheck
    pnpm test
    pnpm build

Use pnpm only

The lockfile is pnpm-only. Mixing package managers can corrupt dependency resolutions.

Chapter 05 · Environment Variables

Configure .env

Storify scripts read from .env, and Next.js also loads it during development and production builds. Integration credentials can live here or in Admin -> Settings; a value saved in Settings always wins, and the matching variable below acts as a per-field fallback. Keep this file private and never commit real secrets.

.env.example (abridged — the bundled file is fully commented)
bash
# ---- Database (required) ----
MONGODB_URI=mongodb://localhost:27017/storify?authSource=admin
MONGODB_DB_NAME=storify
# MONGODB_MAX_POOL_SIZE=50
# RATE_LIMIT_STORE=memory

# ---- Better Auth (required) ----
BETTER_AUTH_SECRET=
BETTER_AUTH_URL=http://localhost:3000

# ---- App configuration (required) ----
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_NAME=Storify
NEXT_PUBLIC_SUPPORT_EMAIL=support@example.com
DEMO_MODE=false
NEXT_PUBLIC_ENABLE_PWA_IN_DEV=false

# ---- Initial admin fallbacks for pnpm create-admin ----
ADMIN_NAME=Admin
ADMIN_PASSWORD=change-me

# ---- OpenAI (optional, billed by OpenAI) ----
OPENAI_API_KEY=your-openai-api-key-here
AI_HERO_BANNER_ENABLED=false

# ---- Scheduled jobs ----
CRON_SECRET=replace-with-a-long-random-secret

# ---- Email / SMTP ----
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-smtp-username
SMTP_PASS=your-smtp-password
SMTP_FROM="Storify <no-reply@example.com>"

# ---- Web push (optional) ----
WEB_PUSH_PUBLIC_KEY=your-web-push-public-key-here
WEB_PUSH_PRIVATE_KEY=your-web-push-private-key-here
WEB_PUSH_SUBJECT=mailto:admin@example.com
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY=your-web-push-public-key-here

# ---- Omnichannel messaging (optional) ----
MESSAGING_ENCRYPTION_KEY=replace-with-at-least-32-random-characters
META_APP_ID=your-meta-app-id
META_APP_SECRET=your-meta-app-secret
META_WHATSAPP_CONFIGURATION_ID=your-login-for-business-configuration-id
META_INSTAGRAM_CONFIGURATION_ID=your-instagram-login-configuration-id
META_WEBHOOK_VERIFY_TOKEN=replace-with-a-long-random-verify-token
META_GRAPH_API_VERSION=vXX.X

# ---- OAuth / social login (optional) ----
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
FACEBOOK_APP_ID=your-facebook-app-id
FACEBOOK_APP_SECRET=your-facebook-app-secret

# ---- Payment gateways (optional) ----
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
PAYPAL_CLIENT_ID=your-paypal-client-id
PAYPAL_CLIENT_SECRET=your-paypal-client-secret
PAYPAL_WEBHOOK_ID=your-paypal-webhook-id
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY=pk_test_xxx
PAYSTACK_SECRET_KEY=sk_test_xxx
NEXT_PUBLIC_RAZORPAY_KEY_ID=rzp_test_xxx
RAZORPAY_KEY_SECRET=your-razorpay-key-secret
RAZORPAY_WEBHOOK_SECRET=your-razorpay-webhook-secret
PESAPAL_MODE=sandbox
PESAPAL_CONSUMER_KEY=your-pesapal-consumer-key
PESAPAL_CONSUMER_SECRET=your-pesapal-consumer-secret
PESAPAL_IPN_ID=your-registered-pesapal-ipn-id
IOTEC_MODE=sandbox
IOTEC_CLIENT_ID=your-iotec-client-id
IOTEC_CLIENT_SECRET=your-iotec-client-secret
IOTEC_WALLET_ID=your-iotec-wallet-uuid

# ---- Object storage (optional) ----
STORAGE_ACCESS_KEY_ID=your-storage-access-key-id
STORAGE_SECRET_ACCESS_KEY=your-storage-secret-access-key
STORAGE_ACCOUNT_ID=your-r2-account-id
STORAGE_ENDPOINT=
STORAGE_REGION=auto
STORAGE_BUCKET=your-bucket-name
STORAGE_PUBLIC_URL=https://your-public-bucket-url.com

# ---- Analytics & thermal printing (optional) ----
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
NEXT_PUBLIC_FACEBOOK_PIXEL_ID=your-facebook-pixel-id
NEXT_PUBLIC_TIKTOK_PIXEL_ID=your-tiktok-pixel-id
PLAUSIBLE_API_KEY=your-plausible-api-key
QZ_TRAY_CERTIFICATE=""
QZ_TRAY_PRIVATE_KEY=""
KeyRequiredDescription
MONGODB_URIRequiredMongoDB connection string for the Storify database. Use the local example for development or an Atlas URI in production.
MONGODB_DB_NAMERequiredDatabase name used by app scripts, migrations, and admin creation commands.
MONGODB_MAX_POOL_SIZEOptionalConnections one app instance may hold open. Defaults to 50. Keep instances x pool size under your Mongo plan's connection limit.
RATE_LIMIT_STOREOptionalWhere API rate-limit counters live. Unset keeps them in MongoDB so limits hold across instances. Set to memory only for a single instance.
BETTER_AUTH_SECRETRequiredRandom 32+ character secret used by Better Auth to sign sessions. A production build refuses to start on an empty, short, or placeholder value.
BETTER_AUTH_URLRequiredCanonical app URL used by Better Auth for callbacks and trusted origins.
NEXT_PUBLIC_APP_URLRequiredPublic storefront URL used in links, metadata, emails, gateway callbacks, and client-side configuration. Pesapal IPN registration requires a public HTTPS value.
NEXT_PUBLIC_APP_NAMEOptionalFallback store name used in transactional emails and notifications until a store name is saved in Admin -> Settings -> General. It does not change the browser tab title or page metadata.
NEXT_PUBLIC_SUPPORT_EMAILOptionalSupport address shown on the access-denied page. Order emails and invoices use the store email from Admin -> Settings -> General instead.
DEMO_MODEOptionalSet to true on public demos. Visitors can still create products, edit orders, and upload images; deletes, settings edits, and test actions are refused per route.
NEXT_PUBLIC_ENABLE_PWA_IN_DEVOptionalEnables the PWA service worker during local development. Leave false unless you are testing installability.
ADMIN_NAMEOptionalFallback display name used by pnpm create-admin when the name argument is omitted.
ADMIN_PASSWORDOptionalFallback password used by pnpm create-admin when the password argument is omitted. Change it before any real deployment.
OPENAI_API_KEYOptionalPowers the AI Sales Agent and AI Studio. Without it, AI features stay unavailable. Usage is billed by OpenAI. Can also be saved in Admin -> Settings -> AI Configuration.
AI_HERO_BANNER_ENABLEDOptionalOpt-in switch for the admin home page hero banner generator. Defaults to disabled.
CRON_SECRETOptionalShared secret for the scheduled routes. Required if you use email delivery retries, vendor subscription billing, the messaging outbox, or escalations.
SMTP_HOSTOptionalSMTP server hostname. Required for password resets, staff invites, and order email unless configured in Admin -> Settings -> Email.
SMTP_PORTOptionalSMTP port, usually 587 for STARTTLS or 465 for implicit TLS.
SMTP_USEROptionalSMTP username for your email provider.
SMTP_PASSOptionalSMTP password or API key. Server-only; never expose it to the browser.
SMTP_FROMOptionalDefault From header, for example "Storify <no-reply@example.com>". The domain should have SPF, DKIM, and DMARC configured.
WEB_PUSH_PUBLIC_KEYOptionalPublic VAPID key for browser push subscriptions. Generate with pnpm push:keys.
WEB_PUSH_PRIVATE_KEYOptionalPrivate VAPID key used by the server to sign push payloads. Keep it secret.
WEB_PUSH_SUBJECTOptionalVAPID subject, usually a mailto: address or your production app URL.
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEYOptionalThe same public VAPID key, exposed to the browser so the service worker can subscribe.
MESSAGING_ENCRYPTION_KEYOptional32+ character key that encrypts stored channel tokens for WhatsApp, Messenger, Instagram, and Telegram. Never rotate it without re-encrypting existing connections.
META_APP_IDOptionalMeta developer app id used by WhatsApp embedded signup and Messenger connections.
META_APP_SECRETOptionalMeta app secret used to verify every inbound webhook signature before the payload is parsed.
META_WHATSAPP_CONFIGURATION_IDOptionalFacebook Login for Business configuration id for WhatsApp embedded signup. Without it, the manual WABA/phone/token form is the fallback.
META_INSTAGRAM_CONFIGURATION_IDOptionalOptional second configuration granting Instagram messaging permissions. Without it, the Instagram panel falls back to the manual Page-token form.
META_WEBHOOK_VERIFY_TOKENOptionalLong random value you also enter as the verify token in the Meta app webhook setup.
META_GRAPH_API_VERSIONOptionalGraph API version pinned for your Meta app, for example v21.0.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETOptionalGoogle OAuth credentials. Credentials alone do not enable the provider — the Admin -> Settings toggle decides whether it is active.
FACEBOOK_APP_ID / FACEBOOK_APP_SECRETOptionalFacebook OAuth credentials, also gated behind the admin panel toggle.
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRETOptionalStripe server credentials for checkout payments and vendor subscription billing. NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is the browser-side fallback.
PAYPAL_CLIENT_ID / PAYPAL_CLIENT_SECRET / PAYPAL_WEBHOOK_IDOptionalPayPal credentials and webhook id used for order capture and verification.
PAYSTACK_SECRET_KEYOptionalPaystack secret key. NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY is used by the browser checkout.
RAZORPAY_KEY_SECRET / RAZORPAY_WEBHOOK_SECRETOptionalRazorpay server credentials. NEXT_PUBLIC_RAZORPAY_KEY_ID is used by the browser checkout.
PESAPAL_MODE / PESAPAL_CONSUMER_KEY / PESAPAL_CONSUMER_SECRET / PESAPAL_IPN_IDOptionalPesapal API 3.0 credentials, sandbox or live mode, and the IPN id returned when you register the notification URL from admin settings.
IOTEC_MODE / IOTEC_CLIENT_ID / IOTEC_CLIENT_SECRET / IOTEC_WALLET_IDOptionalioTec Pay client-credentials and wallet id for Ugandan mobile money and card collections.
STORAGE_ACCESS_KEY_ID / STORAGE_SECRET_ACCESS_KEYOptionalS3-compatible credentials for Cloudflare R2 or AWS S3. Not needed when the Local storage provider is selected.
STORAGE_ENDPOINT / STORAGE_REGION / STORAGE_BUCKET / STORAGE_ACCOUNT_IDOptionalBucket coordinates. For R2 set the endpoint to https://<account-id>.r2.cloudflarestorage.com and the region to auto; for S3 set the region and leave the endpoint blank.
STORAGE_PUBLIC_URLOptionalPublic base URL or CDN domain used to serve uploaded files. CLOUDFLARE_R2_PUBLIC_URL is still honoured as a legacy fallback.
NEXT_PUBLIC_GA_ID / NEXT_PUBLIC_GTM_ID / NEXT_PUBLIC_FACEBOOK_PIXEL_ID / NEXT_PUBLIC_TIKTOK_PIXEL_IDOptionalClient-side analytics and pixel ids. Also configurable from Admin -> Settings -> Analytics.
PLAUSIBLE_API_KEYOptionalServer-side Plausible key used by the admin analytics dashboard.
QZ_TRAY_CERTIFICATE / QZ_TRAY_PRIVATE_KEYOptionalSigned QZ Tray certificate and key for direct thermal printing. The private key is read only by the server signing route and must never be public.

Generate BETTER_AUTH_SECRET fast

Run openssl rand -base64 32 in your terminal and paste the output as the value. Use the same command for CRON_SECRET, META_WEBHOOK_VERIFY_TOKEN, and MESSAGING_ENCRYPTION_KEY.

Two sources of configuration

Payments, OAuth, SMTP, storage, analytics, and AI credentials can be set here or in Admin -> Settings. The database value always wins, so you can boot from .env and later override individual fields from the UI. Messaging is the exception: MESSAGING_ENCRYPTION_KEY and the META_* variables are read from .env only, and the messaging settings page manages per-channel connections rather than these credentials.

Never rotate MESSAGING_ENCRYPTION_KEY in place

Channel tokens are encrypted with it. Rotating the key without re-encrypting stored connections makes every WhatsApp, Messenger, Instagram, and Telegram connection unreadable.

Chapter 06 · Database Setup

Connect MongoDB & seed data

Storify stores users, vendors, products, categories, orders, settings, payments, reviews, blog posts, inventory, conversations, subscriptions, and audit history in MongoDB. A fresh install creates its indexes automatically; existing databases upgrade with the migration scripts below.

  1. 1

    Create a MongoDB database

    Create a MongoDB Atlas cluster and database named storify, or prepare a self-hosted MongoDB instance.

    bash
    # MongoDB Atlas example
    MONGODB_URI=mongodb+srv://username:password@cluster0.mongodb.net/storify
    MONGODB_DB_NAME=storify
  2. 2

    Allow network access

    In MongoDB Atlas, add your local IP address for development and your hosting provider's outbound IPs for production.

    txt
    Atlas -> Network Access -> Add IP Address
  3. 3

    Seed demo data

    Loads demo settings, admin, vendors, customers, staff, categories, products, orders, coupons, collections, inventory locations, and sample store data.

    bash
    pnpm db:seed
  4. 4

    Reset only for testing

    Use reset only on a disposable database. It clears existing records before loading demo ecommerce data. db:full-reset does both in one command.

    bash
    pnpm db:reset
    # or, reset and reseed in one go
    pnpm db:full-reset
  5. 5

    Run migrations on an existing database

    Every migration has a :dry twin that reports what it would change without writing. Run the dry pass first, read the output, then run the real command. New installs can skip all of them.

    bash
    pnpm db:migrate:indexes:dry && pnpm db:migrate:indexes
All migration commands (each also has a :dry variant)
bash
pnpm db:migrate:indexes                   # product & review indexes
pnpm db:migrate:perf-indexes              # general performance indexes
pnpm db:migrate:dashboard                 # dashboard aggregation indexes
pnpm db:migrate:vendor-storefront-indexes # vendor storefront queries
pnpm db:migrate:account-status            # one-time account status upgrade
pnpm db:migrate:omnichannel               # messaging collections & indexes
pnpm db:migrate:telegram-keys             # scope Telegram message ids per chat
pnpm db:migrate:support-conversations     # legacy support tickets -> conversations
pnpm db:migrate:push                      # push subscription records
pnpm db:migrate:audit-indexes             # audit retention (90 days -> 24 months)
pnpm db:migrate:barcodes                  # unique barcode registry
pnpm db:migrate:vendor-billing            # vendor subscription billing backfill

Production: skip the seed

Never run pnpm db:seed, pnpm db:reset, or pnpm db:full-reset against a live store database. They insert demo records and accounts intended for evaluation.

Always dry-run first

pnpm db:migrate:barcodes:dry reports duplicate product or variant barcodes you must resolve before global uniqueness can be enforced. The Telegram migration reports colliding keys instead of guessing which message to keep.

Automatic index creation

Fresh installations build their indexes on boot unless MONGODB_AUTO_INDEX=false. On large production databases, disable it and create indexes during a maintenance window with the migration scripts.

Chapter 07 · Running Locally

Start the dev server

Use dev for local work and build/start for production validation. The storefront, admin, vendor, staff, POS, and API routes all run from the same Next.js app.

  1. 1

    Start in development

    Hot reload, fast refresh, and source maps. Defaults to port 3000.

    bash
    pnpm dev
  2. 2

    Run a production build

    Compiles, optimizes, and runs the Next.js production server. Use this before deploying changes.

    bash
    pnpm build
    pnpm start
  3. 3

    Check types and tests while you work

    Typecheck and the Vitest suite run independently of the dev server, and cover AI authoring, barcodes, stock policy, variants, storage, shipping labels, and the home page config.

    bash
    pnpm typecheck
    pnpm test
  4. 4

    Create a real admin

    For production, create your own admin account instead of relying on seed credentials. Password and name fall back to ADMIN_PASSWORD and ADMIN_NAME when omitted.

    bash
    pnpm create-admin admin@yourdomain.com "StrongPassword123!" "Store Admin"

Change demo passwords

If you use seeded data for a staging preview, change all seeded passwords before exposing the URL to anyone else.

Running a public demo

Set DEMO_MODE=true. Visitors can still create products, edit orders, and upload images, while deletes, settings changes, and connection tests are refused per route so the seeded store survives.

Chapter 08 · Settings Reference

Complete admin settings reference

Use this as the checklist for Admin -> Settings. It maps all twenty settings pages to the configuration each one controls, so you know exactly where every store behavior is managed.

General

Store name, description, email, phone, domain, and address, timezone, default language and currency, supported languages and currencies, and the store-wide country availability policy. All groups sit on one page as cards.

Branding

Logo, dark logo, and favicon, plus primary, secondary, and accent colors applied across the whole application, preset palettes including your own, theme mode with light as the default, contrast, RTL, collapsed sidebar, and navigation color.

Multi-Vendor Management

Enables marketplace mode and vendor permissions for products, orders, store settings, analytics, payouts, and POS access. Links through to Vendor Configuration for registration, approval, trials, required documents, and the default commission.

POS

POS availability for admins, vendors, and sellers, default POS location, receipt and printing behavior, smart grid, sounds, offline payments, payment methods, return rules, and the opt-in switch for selling vendor-owned stock on the register.

Inventory Locations

Creates active and default inventory locations used by admin stock, vendor stock, transfers, POS inventory, held orders, and fulfillment workflows.

Two-Factor Authentication

Opt-in 2FA for the store, and can require it for admins and vendors. Disabled by default so a fresh install is not locked behind an authenticator app.

OAuth / Social Login

Google and Facebook login with client IDs, app IDs, and saved secrets. The toggle here decides whether a provider is active — credentials present in .env never auto-enable it.

AI Configuration

Shared OpenAI key, text and image model selection, output size and quality, per-surface switches, staff and vendor access, daily usage limits, brand voice, tone, image style, brand kit colors and logo, and a connection test.

Security & Access Control

Email verification, vendor verification, session duration, login attempts, lockout duration, password policy, and rate-limit presets for admin, vendor, checkout, cart, coupon, and auth routes.

Payment Settings

Stripe, PayPal, Razorpay, Paystack, Pesapal, ioTec, and cash-on-delivery — public keys, saved secrets, webhook secrets, sandbox or live mode, Pesapal IPN registration, order status sync, and per-gateway connection tests.

Email Configuration (SMTP)

SMTP enablement, host, port, username, saved password, from email and name, SSL/TLS, email and vendor verification rules, delivery logs, and test email sending.

Notification Settings

Which events raise dashboard, email, browser push, and native device push notifications for admins, vendors, staff, and customers.

Omnichannel Messaging

Storefront live chat with its availability mode, weekly schedule, widget color, and offline message; the escalation toggle, interval, and email; and channel connections for WhatsApp, Messenger, Instagram, and Telegram with the webhook callback URL.

Order Settings

Order number prefix, tax rate, default shipping cost, free shipping threshold, vendor commission rate, and minimum withdrawal amount.

Shipping & Delivery

Shipping origin, processing days, estimated delivery display, shipping zones, flat, free-over, and subtotal-range rates, fallback rates, and local pickup.

SEO Settings

Meta title, meta description, meta keywords, and Open Graph image for storefront SEO. robots.txt is generated by the app itself and is not editable here.

Social / Links

Facebook, X/Twitter, Instagram, YouTube, LinkedIn, and TikTok URLs for the storefront footer and public metadata.

Analytics

Google Analytics 4, Google Tag Manager, Meta Pixel, TikTok Pixel, and Plausible, including a self-hosted Plausible domain and API URL.

Maintenance

A temporary maintenance screen, custom message, and optional allowed IPs so admins keep access during an update.

Storage

Selects Local, Cloudflare R2, or S3-compatible storage; stores account, endpoint, region, bucket, and access credentials, public CDN URL, upload path prefix, allowed MIME types, size limits, and opens the Media Library.

Settings route reference (replace en with your locale)
txt
/en/admin/settings/general
/en/admin/settings/appearance       # labelled "Branding" in the sidebar
/en/admin/settings/marketplace
/en/admin/settings/pos
/en/admin/settings/locations
/en/admin/settings/two-factor
/en/admin/settings/oauth
/en/admin/settings/ai
/en/admin/settings/security
/en/admin/settings/payment
/en/admin/settings/email
/en/admin/settings/notifications
/en/admin/settings/messaging
/en/admin/settings/orders
/en/admin/settings/shipping
/en/admin/settings/seo
/en/admin/settings/social
/en/admin/settings/analytics
/en/admin/settings/maintenance
/en/admin/settings/storage

# Marketplace surfaces that live outside Settings
/en/admin/vendors/configuration     # registration, approval, trials, commission
/en/admin/vendors/onboarding        # required documents + registration wizard
/en/admin/vendors/plans             # subscription plan catalog

Settings vs environment variables

Most store behavior is controlled from Admin -> Settings after deployment. Environment variables are still required for app bootstrapping, Better Auth, the database connection, cron authentication, and server-only fallback secrets.

Recommended launch order

Configure General, Storage, Payment, Email, Shipping, SEO, Analytics, Security, then AI, Messaging, POS, and Multi-Vendor. Test checkout, upload, email, chat, and login before going live.

Chapter 09 · Storage & Media

Configure media storage

Storify stores product images, 3D models, category thumbnails, blog images, chat attachments, and digital deliverables through one storage service. Pick Local disk, Cloudflare R2, or any S3-compatible provider from Admin -> Settings -> Storage.

  1. 1

    Choose a provider

    Local writes to the server's own disk and suits a single persistent VPS or container with a mounted volume. Cloudflare R2 and S3-compatible buckets suit serverless and multi-instance deployments. You can switch later; existing media keeps resolving through its original provider.

  2. 2

    Create a bucket (R2 or S3 only)

    Create a Cloudflare R2 or AWS S3 bucket for Storify uploads. Use a lowercase bucket name with numbers and hyphens only.

    txt
    storify-media
  3. 3

    Create scoped access credentials

    For R2, create an API token with Object Read & Write permission scoped to the bucket. For S3, create an IAM user or access key with least-privilege object access. Custom endpoints, path-style addressing, and region normalization are all supported.

  4. 4

    Connect a public media URL

    For production, connect a custom domain such as assets.yourdomain.com. For local testing, an R2 public development URL works. The Local provider serves files from the app itself and needs no public URL.

    txt
    https://assets.yourdomain.com
  5. 5

    Set limits and verify

    Set the upload path prefix, allowed MIME types, and max image, video, and 3D model sizes. Save the draft configuration, run the built-in provider test, then upload a product image and a 3D model from the product editor.

  6. 6

    Browse everything in the Media Library

    Admin -> Settings -> Storage opens a provider-aware Media Library with pagination, search, media-type filtering, upload, image, video, 3D, and document previews, URL copying, and single or bulk deletion.

Cloudflare R2 endpoint format
txt
https://<ACCOUNT_ID>.r2.cloudflarestorage.com    # region: auto

Keep write credentials server-only

Never expose storage access keys in NEXT_PUBLIC_* variables or client-side code. Only public media URLs should be browser-readable.

Image handling

Uploaded and reusable images are converted to WebP, and media dimensions are stored so the storefront can reserve layout space. Attachments sent on external channels — WhatsApp, Messenger, Instagram, Telegram — are the deliberate exception and keep their original format, because those providers reject WebP. Images in the in-app live chat are still converted.

Private files are separate

Digital product deliverables go to a private, scoped key prefix and are never referenced by a storefront payload. Customers reach them only through short-lived signed links from a paid order. See Products & Downloads.

Chapter 10 · Payment Gateways

Configure checkout payments

Storify supports Stripe, PayPal, Razorpay, Paystack, Pesapal, ioTec Pay, and cash-on-delivery. Enable only the gateways you want from Admin -> Settings -> Payment Settings; every credential there overrides its .env fallback.

  1. 1

    Add Stripe keys

    Enable Stripe, then enter the publishable key, secret key, and webhook secret. Saved secrets are hidden after save. Stripe also powers vendor subscription billing, so a marketplace with paid plans must configure it.

    Stripe fields
    txt
    Publishable Key
    Secret Key
    Webhook Secret
    Test connection
  2. 2

    Configure webhooks and callbacks

    Point each gateway at its deployed endpoint so orders are marked paid after gateway confirmation. All of these must be publicly reachable over HTTPS and must not sit behind authentication.

    bash
    https://yourdomain.com/api/payments/webhook            # Stripe (orders + vendor billing)
    https://yourdomain.com/api/payments/razorpay/webhook   # Razorpay
    https://yourdomain.com/api/payments/paystack/webhook   # Paystack
    https://yourdomain.com/api/payments/pesapal/ipn        # Pesapal IPN (GET + POST)
    https://yourdomain.com/api/payments/iotec/callback     # ioTec Pay
  3. 3

    Subscribe the Stripe events

    Order payments need the payment intent events. If you sell vendor subscription plans, subscribe the billing events to the same endpoint — without them, a paid checkout never activates the plan.

    Vendor billing events
    txt
    checkout.session.completed
    checkout.session.expired
    customer.subscription.created
    customer.subscription.updated
    customer.subscription.deleted
    invoice.paid
    invoice.payment_failed
    invoice.payment_action_required
  4. 4

    Add the other gateways

    Use sandbox or test keys first, then test each enabled gateway before switching to live mode. Pesapal and ioTec both cache their access tokens for the advertised lifetime, so checkout stays a single round trip.

    Gateway fields
    txt
    PayPal:   Client ID, Client Secret, Mode, Webhook ID
    Razorpay: Key ID, Key Secret, Webhook Secret
    Paystack: Public Key, Secret Key
    Pesapal:  Consumer Key, Consumer Secret, Mode, IPN ID
    ioTec:    Client ID, Client Secret, Wallet ID, Mode
    COD:      Instructions, minimum amount, maximum amount
  5. 5

    Register the Pesapal IPN

    Save the Pesapal consumer key, secret, and mode, set NEXT_PUBLIC_APP_URL to a public HTTPS domain (use a tunnel for sandbox development), then choose Register IPN in payment settings. Storify registers the IPN URL and stores the returned id.

  6. 6

    Switch to live mode

    Replace test keys with live keys, create production webhooks in the same mode as the keys, make a small real payment, then verify the order, transaction, and refund records.

Cash-on-delivery

COD does not require gateway keys. Enable it from payment settings if your store supports offline collection, and set the minimum and maximum order amounts it applies to.

Regional gateways

Pesapal covers East African cards and mobile money through hosted checkout. ioTec Pay covers Ugandan mobile money and cards, and adds Ugandan shilling support to the currency list.

Test and live modes must match

A webhook signed with a test secret will not validate against live keys. When a payment succeeds but the order stays pending, this mismatch is the first thing to check.

Chapter 11 · Email / SMTP

Wire up transactional email

Password resets, staff invites, order emails, abandoned checkout recovery, vendor billing notices, and customer notifications use the SMTP transport. Configure it in Admin -> Settings -> Email Configuration, or through the SMTP_* variables as a fallback.

  1. 1

    Save and test the transport

    Enter host, port, username, password, from email, and from name, then send a test email from the same page. Delivery logs on that page show what was attempted and what failed.

  2. 2

    Schedule the delivery retry job

    Failed sends are queued rather than dropped. Schedule the delivery route every five minutes with the CRON_SECRET bearer token so retries actually run — Vercel's bundled cron config already does this.

    bash
    curl -H "Authorization: Bearer $CRON_SECRET" \
      https://yourdomain.com/api/cron/email-deliveries
  3. 3

    Choose which events send mail

    Admin -> Settings -> Notification Settings decides which events reach admins, vendors, staff, and customers by email, dashboard notification, browser push, and native device push.

Resend

Simple for new stores. In Admin -> Settings -> Email Configuration, use smtp.resend.com as host and resend as username.

SendGrid

Good for higher send volume and mature sender reputation. Enter the SendGrid SMTP host, port, username, and API password in Email Configuration.

Postmark

Excellent for transactional email such as receipts, password reset, and order updates.

Generic SMTP

Works for testing and smaller shops. Use production-grade credentials before launch.

Verify your sender domain

Always set up SPF, DKIM, and DMARC for the From Email domain you configure in Admin -> Settings -> Email Configuration. Without domain authentication, store emails often land in spam.

Chapter 12 · AI Studio & Sales Agent

Set up AI authoring and the shopper assistant

Storify has two AI surfaces: a storefront AI Sales Agent that answers product, payment, and delivery questions, and a dashboard AI Studio that writes copy and generates images for products, variants, categories, collections, brands, blogs, hero slides, promotional cards, and review replies.

  1. 1

    Add an OpenAI API key

    Save the key in Admin -> Settings -> AI Configuration, or set OPENAI_API_KEY in .env as a fallback and restart. The settings page runs a connection test and reports whether OpenAI is reachable.

    bash
    OPENAI_API_KEY=your-openai-api-key-here
  2. 2

    Pick models and output settings

    Choose a text model (GPT-4.1 mini, GPT-4.1, GPT-5 mini, or GPT-5) and an image model (GPT Image 1 or GPT Image 1 mini), then set output size and quality. Cheaper models are the sensible default for bulk product copy.

  3. 3

    Turn on the surfaces you want

    Each AI surface has its own switch, so you can enable product descriptions without enabling blog authoring. Staff and vendor access are separate toggles, and daily usage limits cap spend per account.

  4. 4

    Set brand voice and brand kit

    Configure tone, brand instructions, image-style guidance, primary and secondary brand colors, and a logo. Generated copy and artwork follow them across every surface.

  5. 5

    Enable the hero banner studio (optional)

    The home page hero generator produces exact 1360 x 314 artwork and is opt-in because it consumes image credits quickly.

    bash
    AI_HERO_BANNER_ENABLED=true
  6. 6

    Configure the Sales Agent

    Open Admin -> AI Sales Agent to adjust prompt behavior, store context, suggested questions, and availability on the storefront.

Keep AI keys private

OPENAI_API_KEY must stay server-side. Never add it as NEXT_PUBLIC_OPENAI_API_KEY. AI routes enforce role and surface gates, rate limits, and daily usage accounting on the server.

Usage is billed by OpenAI

Every generation costs tokens or image credits at OpenAI's pricing. Set daily limits before giving staff or vendors access, and review usage in AI settings.

Deeper reference

The bundled /docs/AI_AUTHORING_OPERATING_LAYER.md documents every AI surface, its prompt contract, and its permission rules.

Chapter 13 · Omnichannel Messaging

Connect chat, WhatsApp, Messenger, Instagram & Telegram

Storefront live chat, WhatsApp, Facebook Messenger, Instagram Direct, and Telegram share one conversation model and one inbox for admins, vendors, and staff. Live chat works with no external setup; the social channels need a Meta app or a Telegram bot.

  1. 1

    Set the messaging secrets

    MESSAGING_ENCRYPTION_KEY encrypts every stored channel token, and CRON_SECRET authenticates the outbox and escalation jobs. Set both before connecting any channel.

    bash
    MESSAGING_ENCRYPTION_KEY=at-least-32-random-characters
    CRON_SECRET=a-long-random-value
  2. 2

    Turn on live chat

    Admin -> Settings -> Omnichannel Messaging enables storefront live chat and sets its availability mode, weekly schedule, widget color, offline message, and the escalation toggle, interval, and email. This alone needs no third-party account. The public contact form always creates a conversation in the same inbox — there is no toggle, and it works even with live chat switched off.

  3. 3

    Create and point a Meta app

    Configure a Meta developer app with WhatsApp and/or Messenger, then use the callback shown in the settings page as the webhook URL and META_WEBHOOK_VERIFY_TOKEN as the verify token. Subscribe the WhatsApp message and status events and the Messenger Page messaging events.

    bash
    https://yourdomain.com/api/webhooks/meta
  4. 4

    Connect WhatsApp

    With META_WHATSAPP_CONFIGURATION_ID set, owners connect through Facebook Login for Business (embedded signup); Storify verifies the selected phone belongs to the returned WABA and subscribes the app. The manual WABA, phone, and token form remains as a fallback.

  5. 5

    Connect Messenger and Instagram

    Messenger uses a Page token from the channel panel. Instagram Direct runs on the same Messenger Platform — set META_INSTAGRAM_CONFIGURATION_ID for the guided flow, otherwise use the manual Page-token form.

  6. 6

    Connect Telegram

    Create a bot with BotFather and paste its token into the Telegram channel panel. Storify registers the webhook for you.

    bash
    https://yourdomain.com/api/webhooks/telegram
  7. 7

    Schedule the outbox and escalations

    Outbound sends retry through the outbox route and unanswered conversations escalate through its own route. Both authenticate with CRON_SECRET; vercel.json already schedules them at one and five minutes.

    bash
    GET /api/cron/messaging-outbox        # every minute
    GET /api/cron/messaging-escalations   # every 5 minutes
  8. 8

    Run the messaging migrations

    Existing installations create the conversation collections and indexes with the omnichannel migration. Stores that used the old support tickets or connected Telegram before message ids were scoped per chat run the other two.

    bash
    pnpm db:migrate:omnichannel:dry && pnpm db:migrate:omnichannel
    pnpm db:migrate:support-conversations
    pnpm db:migrate:telegram-keys

The Meta webhook must be public

It has to be reachable over HTTPS and must not sit behind authentication. Every POST is signature-checked with META_APP_SECRET before the JSON is parsed, so an unsigned request is rejected anyway.

Account steps Meta owns

App review, business verification, permissions, and phone registration happen in your Meta account and cannot be completed from source code. WhatsApp templates need whatsapp_business_management; sending and receiving also needs whatsapp_business_messaging.

Deeper reference

The bundled /docs/OMNICHANNEL_MESSAGING.md covers channel capabilities, Meta and Instagram setup, template synchronization, vendor controls, and scaling the SSE stream beyond the default MongoDB poll.

Chapter 14 · Vendors & Subscriptions

Run a marketplace with optional paid plans

Multi-vendor mode adds vendor registration, approval, commissions, payouts, and vendor dashboards. On top of that, an optional subscription system lets you charge vendors for plans through Stripe, with per-plan commission rates and limits.

  1. 1

    Enable multi-vendor mode

    Admin -> Settings -> Multi-Vendor Management turns marketplace mode on and sets vendor permissions for products, orders, store settings, analytics, payouts, and POS access. In single-vendor mode Storify uses the default main store vendor.

  2. 2

    Configure vendor registration

    Admin -> Vendors -> Configuration controls whether registration is open, whether approval is manual, the plan toggle, plan trial length, and the default commission.

    txt
    /en/admin/vendors/configuration
  3. 3

    Shape the onboarding wizard

    Admin -> Vendors -> Onboarding owns the required verification documents and the registration wizard itself — rename and reorder steps, toggle required and hidden fields, add custom fields, and preview the result live.

    txt
    /en/admin/vendors/onboarding
  4. 4

    Build the plan catalog (optional)

    Admin -> Vendors -> Plans defines each plan's price, billing interval, commission rate, trial length, product and staff limits, and whether AI authoring is included. Plans stay entirely optional — leave the system off to run a commission-only marketplace.

    txt
    /en/admin/vendors/plans
  5. 5

    Wire Stripe billing

    Vendor plans synchronize to Stripe products and prices, then use Checkout and the customer portal for upgrades, downgrades, and cancellations. Configure Stripe in the same mode as your plans and subscribe the billing events listed in Payment Gateways.

  6. 6

    Schedule the billing job

    An hourly authenticated GET drives renewals, dunning retries before expiry, and expiry handling. Without it, a failed payment never retries and an expired plan never reverts its commission.

    bash
    curl -H "Authorization: Bearer $CRON_SECRET" \
      https://yourdomain.com/api/cron/vendor-subscriptions
  7. 7

    Backfill existing vendors

    Stores upgrading from a release before vendor billing run the backfill once so existing vendors get consistent subscription and commission records.

    bash
    pnpm db:migrate:vendor-billing:dry && pnpm db:migrate:vendor-billing

One commission authority

Commission resolves from the store, the vendor's plan, and the settings default in that order, and the result is projected onto the vendor record. Plan expiry reverts commission lazily at read time without revoking the vendor's access.

Deeper reference

The bundled /docs/vendor-subscription-stripe-checklist.md is the operational runbook: required deployment configuration, recovery when Stripe took payment but the local record is incomplete, dunning, plan changes, and diagnostics.

Chapter 15 · POS, Inventory & Labels

Set up the register, barcodes and printing

The POS register works on phones, tablets, and desktops for admins, vendors, and staff. It shares stock with the rest of the store through inventory locations, and drives barcode labels, receipts, and shipping labels.

  1. 1

    Create inventory locations

    Admin -> Settings -> Inventory Locations defines the active and default locations used by admin stock, vendor stock, transfers, POS inventory, held orders, and fulfillment. A register must have a location before it can sell counted stock.

  2. 2

    Configure POS access

    Admin -> Settings -> POS controls availability for admins, vendors, and sellers, the default location, receipt behavior, smart grid, sounds, offline payments, accepted payment methods, and return rules.

  3. 3

    Decide on vendor stock

    Selling vendor-owned stock on the shop's register is opt-in: it needs pos.allowVendorProducts, multi-vendor mode, and a POS location, because without a per-store count there is nothing proving the goods are on the shelf.

  4. 4

    Prepare the barcode registry

    Barcodes are globally unique across products and variants. Run the dry pass first, resolve every duplicate it reports, then apply the migration.

    bash
    pnpm db:migrate:barcodes:dry
    # resolve duplicates, then
    pnpm db:migrate:barcodes
  5. 5

    Assign identifiers

    Enter a barcode with its format and source, or use Generate for an internal check-digit-valid EAN-13. Auto-detection covers EAN-13, UPC-A, GTIN-14, and Code 128.

  6. 6

    Print barcode labels

    Select rows in Inventory and open Barcode labels: 40 x 25, 50 x 30, or 60 x 40 mm, 203 or 300 DPI, ZPL or TSPL, manual copies or on-hand quantity. Print through the browser, download the raw commands, or send directly to a QZ printer.

  7. 7

    Set up direct thermal printing (optional)

    Install QZ Tray on each terminal with the printer's normal driver, then add a signed certificate and key on the server. Restart the app after changing them; the private key is only ever read by the server-side signing route.

    bash
    QZ_TRAY_CERTIFICATE="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
    QZ_TRAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"

Held orders are per register

A parked sale belongs to its POS location, and resuming one re-reads every line against the live catalogue so stale prices and stock another register already sold are caught before checkout.

Browser printing checklist

Browser output uses exact millimetre page sizes. Disable browser headers and footers, print at 100% or Actual size, and calibrate the printer's media and gap sensor before a production run.

Deeper reference

The bundled /docs/BARCODE_AND_THERMAL_PRINTING.md covers supported identifiers, first deployment, the label studio, QZ setup, POS receipts, and 4 x 6 shipping labels.

Chapter 16 · Products & Downloads

Physical products, variants and digital files

Every product is either physical or digital, and that choice decides which sections of the form apply. Physical products carry weight, customs data, and counted stock; digital products carry private download files and never run out.

Format is chosen once

The physical or digital switch is locked after creation in both the form and the API. The two formats own different data, checkout paths, and stock semantics, and existing carts and orders reference the old shape. Create a new product to change format.

Stock policy

Digital products, products with Track quantity off, and products set to continue selling when out of stock are never blocked by the stock count. The same rule drives the buy box, cart, product cards, and inventory writes.

Digital deliverables

Files upload to a private, scoped storage prefix — up to 20 per product, 5 GB each — and are never referenced by a storefront payload. Buyers download them from a paid order through short-lived signed links.

Free samples

A digital product can also carry one public sample file, linked from the product page before purchase, the way a book preview works.

Variants and options

Options support text, color, image, integer, and decimal values with swatch or image presentation. Global variants and category-level templates merge into product variants while preserving product-owned options.

Rich media

Product media accepts images, 3D models, and embedded YouTube or Vimeo video, with a size guide covering product-line templates, regional sizing, and measurement instructions.

URL handles

A product's URL handle follows its title until you edit it, then stays fixed so a published link keeps working. Clear the field to resume automatic generation.

Digital files need durable storage

With the Local provider, digital deliverables live on the app server's disk. Make sure that path is on a mounted volume and included in your backups, or a redeploy will take the files with it.

Chapter 17 · PWA & Push

Installable app and notifications

Storify ships as an installable PWA with browser web push and native device push delivery, so order updates, messages, and stock alerts reach staff and customers outside the tab.

  1. 1

    Generate push keys

    Run the bundled command and copy the public and private pair into .env.

    bash
    pnpm push:keys
  2. 2

    Set web push variables

    Paste the generated VAPID values. The public key appears twice — the server signs with the private key, and the browser subscribes with the NEXT_PUBLIC_ copy.

    bash
    WEB_PUSH_PUBLIC_KEY=your-web-push-public-key-here
    WEB_PUSH_PRIVATE_KEY=your-web-push-private-key-here
    WEB_PUSH_SUBJECT=mailto:admin@example.com
    NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY=your-web-push-public-key-here
  3. 3

    Migrate existing subscriptions

    Stores upgrading from a release before native device push run the push migration once.

    bash
    pnpm db:migrate:push:dry && pnpm db:migrate:push
  4. 4

    Choose what gets pushed

    Admin -> Settings -> Notification Settings maps each event to in-app, email, and push per role. The single Push toggle covers browser web push and native device delivery together.

  5. 5

    Set your app icon

    The favicon in Admin -> Settings -> Branding is what the web manifest and install prompt use, so upload it there and reinstall the PWA to see the new icon. With nothing configured the manifest declares no icon and the platform falls back to its own.

  6. 6

    Test installability

    Build and serve over HTTPS, then install from Chrome or Edge. To exercise the service worker locally, set NEXT_PUBLIC_ENABLE_PWA_IN_DEV=true.

No bundled placeholder icons

Since 1.3.1 the package ships no default favicon or PWA icon set. With nothing configured, sidebars and the SEO preview fall back to a store-initial badge instead of advertising the platform.

Chapter 18 · Branding & Theme

Customize the storefront

Storify includes admin-managed store settings plus code-level brand defaults. Use admin settings for day-to-day changes and source files for packaged defaults.

  1. 1

    Update general store settings

    Open Admin -> Settings -> General to set store name, contact details, address, timezone, default and supported languages and currencies, and the country availability policy. All groups sit on one page as cards with a shared save bar.

  2. 2

    Upload logos and the favicon

    Admin -> Settings -> Branding holds the logo, dark logo, and favicon. They are served from settings, so a fresh install shows your mark in the browser tab, the web manifest, the service worker, sidebars, and the SEO preview. The favicon doubles as the PWA install icon — there is no separate app-icon upload.

  3. 3

    Set the color system

    The same Branding page controls primary, secondary, and accent colors, preset palettes including custom ones, theme mode, contrast, RTL, collapsed sidebar, and navigation color. Colors apply across the whole application, not just the storefront.

  4. 4

    Customize online store content

    Use Admin -> Online Store and Admin -> Content to manage menus, homepage sections, pages, blog posts, collections, and promotional content. The home page builder also sets desktop column counts for the New Arrivals and Top Articles grids.

  5. 5

    Change packaged defaults

    Edit config/branding.config.ts when you want the source package defaults to match your brand before seeding or deployment.

    config/branding.config.ts
    ts
    export const DEFAULT_STORE_NAME = "Storify";
    export const DEFAULT_CURRENCY = "USD";
    export const DEFAULT_PRIMARY_COLOR = "#2065D1";

Theme defaults to light

The storefront ships light-first with dark available. Currency is admin-driven and language is URL-driven, so a visitor's locale comes from the path segment rather than a stored preference.

Chapter 19 · Multi-language

Add or edit languages

Storify ships 18 locales with RTL-ready routing. Locale files live under /locales and are loaded with next-intl; the active list is declared in config/i18n.config.ts.

  1. 1

    Edit existing strings

    Open /locales/<locale>.json and edit values directly. Changes hot-reload in development. English is the complete reference file; other locales vary in coverage and fall back to English for any key they do not define, so check the languages you plan to ship.

  2. 2

    Add a new language

    Copy /locales/en.json to /locales/<your-locale>.json, translate the values, then register the locale and its metadata in config/i18n.config.ts.

    config/i18n.config.ts
    ts
    export const locales = [
      "en", "bn", "ar", "es", "fr", "de", "tr", "hi", "nl",
      "zh", "ja", "zu", "xh", "af", "sw", "ha", "yo", "ig",
    ] as const;
    
    export const defaultLocale: Locale = "en";
  3. 3

    Enable languages for the storefront

    Admin -> Settings -> General -> Supported Languages controls which of the installed locales visitors can actually pick, and Default Language sets the fallback.

  4. 4

    RTL support

    Arabic is included and switches layout direction automatically. Test menus, checkout, product pages, POS, and dashboards in RTL before launch.

  5. 5

    Currency settings

    Admin -> Settings -> General sets the default currency and the supported list. Prices format with the currency's own regional conventions while keeping Latin digits, and each order stores the currency it was placed in so historical totals never get relabelled.

Chapter 20 · Deploy to Vercel

One-click deploy (recommended)

Vercel is the fastest path to production for most Storify buyers. Prepare environment variables, MongoDB Atlas, storage, and payment webhooks before launch.

  1. 1

    Push to a GitHub repo

    Create a private GitHub repo and push the source. Vercel will pull from it.

    bash
    git init
    git add .
    git commit -m "Initial Storify setup"
    git remote add origin git@github.com:you/storify.git
    git push -u origin main
  2. 2

    Import the repo into Vercel

    vercel.com -> New Project -> Import. Vercel auto-detects Next.js.

  3. 3

    Add environment variables

    Project Settings -> Environment Variables. Paste the keys from .env.example. Set NEXT_PUBLIC_APP_URL and BETTER_AUTH_URL to your production URL, and add CRON_SECRET so the scheduled routes authenticate.

  4. 4

    Connect MongoDB Atlas

    Add Vercel's outbound access to MongoDB Atlas Network Access, then set MONGODB_URI in Vercel before deploying. Keep instances x MONGODB_MAX_POOL_SIZE under your Atlas plan's connection limit.

    bash
    MONGODB_URI=mongodb+srv://username:password@cluster0.mongodb.net/storify
  5. 5

    Pick a storage provider that fits

    Serverless filesystems are ephemeral, so use Cloudflare R2 or S3 on Vercel. The Local provider is for a VPS or container with a mounted volume.

  6. 6

    Confirm the cron jobs

    vercel.json already declares all four scheduled routes. Check them under Project -> Cron Jobs after the first deploy, and make sure CRON_SECRET is set in the same environment.

  7. 7

    Update gateway and channel webhooks

    Once the production domain is live, point Stripe, Razorpay, Paystack, Pesapal IPN, ioTec callback, the Meta webhook, and the Telegram webhook at it.

Chapter 21 · Deploy to VPS / Docker

Self-host on your own server

If you prefer your own server, Storify can run behind nginx or Caddy with a managed MongoDB connection or a self-hosted MongoDB container.

  1. 1

    Write a Dockerfile first

    The package ships source, not a container image, so there is no Dockerfile in the archive. Add a Node 20 multi-stage build that runs pnpm install and pnpm build, then pnpm start. Set output: "standalone" in next.config.ts if you want a slim runtime image.

    Dockerfile (minimal)
    bash
    FROM node:20-alpine
    WORKDIR /app
    RUN corepack enable
    COPY . .
    RUN pnpm install --frozen-lockfile && pnpm build
    EXPOSE 3000
    CMD ["pnpm", "start"]
  2. 2

    Build and start the stack

    With the Dockerfile in place, build the images and run them in the background.

    bash
    docker compose up -d --build
  3. 3

    Seed only for evaluation

    Load demo records only when you are setting up a test or evaluation instance.

    bash
    docker compose exec app pnpm db:seed
  4. 4

    Front with Caddy

    Use nginx or Caddy to terminate TLS and proxy to localhost:3000. Caddy can provision Let's Encrypt automatically. Raise the proxy's upload body limit if you accept large 3D models or digital files.

    Caddyfile
    nginx
    storify.yourdomain.com {
      reverse_proxy localhost:3000
    }
  5. 5

    Persist uploads if you use Local storage

    The Local provider writes public media under public/uploads and digital product deliverables to private-uploads, which sits outside public/ so it is never statically served. Mount both so they survive a rebuild, and include both in your backups.

    yaml
        volumes:
          - storify-uploads:/app/public/uploads
          - storify-private:/app/private-uploads
  6. 6

    Schedule the cron routes yourself

    There is no Vercel cron on a VPS. Add the four scheduled routes to system cron with the CRON_SECRET bearer token — see Scheduled Jobs for the exact intervals.

docker-compose.yml
yaml
services:
  app:
    build: .
    ports: ["3000:3000"]
    env_file: .env
    environment:
      MONGODB_URI: mongodb://storify:change-me@mongo:27017/storify?authSource=admin
      MONGODB_DB_NAME: storify
    depends_on: [mongo]
  mongo:
    image: mongo:7
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: storify
      MONGO_INITDB_ROOT_PASSWORD: change-me
    volumes:
      - mongodata:/data/db
    ports: ["27017:27017"]

volumes:
  mongodata:

Use a managed database when possible

Self-hosting MongoDB works, but you own backups, replication, and upgrades. MongoDB Atlas offloads that work.

Running more than one instance

Leave RATE_LIMIT_STORE unset so rate-limit counters live in MongoDB and a limit means the same thing everywhere. The in-memory store is only correct for exactly one instance.

Chapter 22 · Scheduled Jobs

Keep the background work running

Four routes must run on a schedule. They are ordinary authenticated GET endpoints, so any scheduler works: Vercel Cron, system cron, a container sidecar, or an external uptime scheduler.

messaging-outbox

Retries outbound channel messages whose immediate send failed. Skip it and a message that failed once is never delivered.

messaging-escalations

Emails whoever owns an unanswered conversation — the assigned agent, otherwise the vendor owner or their staff, falling back to the configured escalation address for platform-owned chats — once it passes the "Escalate after (minutes)" interval. That timer is wall-clock and does not pause outside business hours.

email-deliveries

Retries queued transactional email. Skip it and a temporary SMTP failure becomes a permanently lost password reset or order email.

vendor-subscriptions

Drives renewals, dunning retries before expiry, and plan expiry. Only needed if you sell vendor subscription plans.

vercel.json (bundled)
json
{
  "crons": [
    { "path": "/api/cron/email-deliveries",      "schedule": "*/5 * * * *" },
    { "path": "/api/cron/vendor-subscriptions",  "schedule": "0 * * * *" },
    { "path": "/api/cron/messaging-outbox",      "schedule": "* * * * *" },
    { "path": "/api/cron/messaging-escalations", "schedule": "*/5 * * * *" }
  ]
}
System cron equivalent for a VPS
bash
* * * * *   curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://yourdomain.com/api/cron/messaging-outbox
*/5 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://yourdomain.com/api/cron/messaging-escalations
*/5 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://yourdomain.com/api/cron/email-deliveries
0 * * * *   curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://yourdomain.com/api/cron/vendor-subscriptions

Set CRON_SECRET before scheduling

Each route compares the Authorization: Bearer header against the deployed CRON_SECRET. Use your scheduler's secret store rather than pasting the value into a command that lands in shell history or logs.

Chapter 23 · Custom Domain & SSL

Point your domain at Storify

Once your app is live, route a custom domain to it. Both Vercel and Caddy can provision SSL certificates for free.

  1. 1

    Add an A or CNAME record

    For Vercel, add a CNAME pointing to cname.vercel-dns.com. For your VPS, point an A record at the server IP.

  2. 2

    Verify in Vercel or Caddy

    Vercel auto-verifies once DNS propagates. Caddy reissues the certificate on the next request.

  3. 3

    Update application URLs

    Switch NEXT_PUBLIC_APP_URL and BETTER_AUTH_URL to https://yourdomain.com and redeploy. These are used in emails, auth callbacks, and public metadata.

  4. 4

    Update external integrations

    Update payment webhooks and the Pesapal IPN registration, PayPal return and cancel URLs, the Meta and Telegram webhook callbacks, OAuth redirect URIs, the storage public URL, and email sender links to use the production domain.

Re-register the Pesapal IPN after a domain change

The stored IPN id points at the old URL. Open Admin -> Settings -> Payment Settings and choose Register IPN again, otherwise Pesapal notifications keep going to the previous domain.

Chapter 24 · Admin & Roles

Set up store roles

After your first deploy, create real users and assign scoped roles for admin, vendors, staff/sellers, and customers.

  1. 1

    Create the production admin

    Use the create-admin script to create or upgrade a user to admin with a strong password.

    bash
    pnpm create-admin admin@yourdomain.com "StrongPassword123!" "Store Admin"
  2. 2

    Configure multi-vendor mode

    Use Admin -> Settings -> Multi-Vendor Management to enable or disable marketplace mode. In single-vendor mode, Storify uses the default main store vendor.

  3. 3

    Approve vendors

    Review vendor applications, approve or reject sellers, configure commission rates, assign subscription plans, and monitor payouts from the admin dashboard.

  4. 4

    Invite staff and sellers

    Create staff users for POS, inventory, orders, products, messaging, and customer workflows. Role-based permissions keep admin-only areas protected.

  5. 5

    Scope staff to what they own

    A staff member can be limited to a vendor, a POS location, or a region. That scope is applied by every list, detail loader, and API route alike, so a scoped staff member cannot reach an out-of-scope order by typing its id into the URL.

  6. 6

    Set messaging permissions

    Inbox access is its own permission. Grant it to the staff who answer conversations, and use conversation assignment, claim, and unassign to divide the work.

Do not share admin accounts

Shared logins break accountability. Create separate admin, staff, and vendor accounts for every real user — the order timeline and audit log record who did what.

Account status applies to everyone

Suspension and deactivation are enforced for every role, including admins and staff. Databases created before this run pnpm db:migrate:account-status once.

Chapter 25 · Security & Hardening

Lock the store down before launch

Storify defaults to safe rather than convenient: providers stay off until you enable them, two-factor is opt-in, and secrets are checked at startup. This is the pre-launch pass to make sure nothing was left open.

Secret preflight

A production build refuses to start on an empty, placeholder, or short BETTER_AUTH_SECRET. Generate real values for it, CRON_SECRET, MESSAGING_ENCRYPTION_KEY, and META_WEBHOOK_VERIFY_TOKEN.

Password policy and 2FA

Set the password policy in Security & Access Control. Two-factor is opt-in and can be required for admins and vendors once your team is enrolled.

OAuth stays off until you say so

Google and Facebook credentials in .env never auto-enable a provider. The admin toggle is the authority, so an admin can always turn a provider off.

Shared rate limiting

Limits are counted in MongoDB by default so they hold across instances. Tune the admin, vendor, checkout, cart, coupon, and auth presets in Security & Access Control.

Media delivery

Local media is protected against path traversal and uploaded SVG execution, trusted remote domains are centralized, and the chat media proxy is sandboxed rather than relaying provider content types on your origin.

Audit history

Order and admin actions are recorded for 24 months, long enough to outlive a card chargeback window. Existing databases apply the new retention with pnpm db:migrate:audit-indexes.

Pre-launch checklist

Rotate every seeded password, create a personal admin account, set the password policy, enable 2FA for admins, confirm gateway keys and webhooks are in live mode, and verify DEMO_MODE is false.

Chapter 26 · Updates & Migrations

Stay current and recoverable

Keep Storify updated while protecting your database, uploaded media, custom translations, and brand customizations. Releases from 1.1 onward ship migrations that must run once against an existing database.

  1. 1

    Back up first

    Take a database backup and, if you use Local storage, a copy of the upload directory before merging anything. Every migration below has a dry-run twin, but a backup is the only real undo.

  2. 2

    Download the latest build

    Download updates from your CodeCanyon account and read the changelog for the versions between yours and the new one before merging.

  3. 3

    Diff and merge

    Use git or your favorite diff tool to merge new files. Review /locales, /config, /public, .env.example, and any customized components carefully — new releases add environment keys as well as code.

  4. 4

    Install and rebuild

    Install dependencies, then verify before deploying.

    bash
    pnpm install
    pnpm typecheck
    pnpm test
    pnpm build
  5. 5

    Run the migrations for the versions you skipped

    Run each dry pass, read its output, then apply. Running a migration twice is safe; skipping one leaves the feature it supports half-configured.

    Upgrading to 1.3.1 from an earlier release
    bash
    # 1.1 -> product & review indexes
    pnpm db:migrate:indexes
    
    # 1.2 -> barcodes (resolve duplicates reported by the dry run first)
    pnpm db:migrate:barcodes:dry
    pnpm db:migrate:barcodes
    
    # 1.3 -> performance, omnichannel, vendor billing, push, account status
    pnpm db:migrate:perf-indexes
    pnpm db:migrate:dashboard
    pnpm db:migrate:vendor-storefront-indexes
    pnpm db:migrate:omnichannel
    pnpm db:migrate:support-conversations   # only if you used legacy support tickets
    pnpm db:migrate:telegram-keys           # only if Telegram was connected before 1.3
    pnpm db:migrate:vendor-billing
    pnpm db:migrate:push
    pnpm db:migrate:account-status
    
    # 1.3.1 -> audit retention 90 days to 24 months
    pnpm db:migrate:audit-indexes
  6. 6

    Re-check settings after upgrading

    New releases add settings pages. After 1.3.1, review AI Configuration, Omnichannel Messaging, Notification Settings, the country availability policy, and your favicon on the Branding page — icons now come from settings rather than bundled files, so an install with nothing configured shows no icon at all.

  7. 7

    Schedule database backups

    Use MongoDB Atlas automated backups or set up nightly mongodump on a VPS.

    bash
    # Cron: nightly backup at 02:00
    0 2 * * * mongodump --uri="$MONGODB_URI" --archive=/backups/storify-$(date +\%F).archive --gzip
  8. 8

    Back up uploaded media

    On R2 or S3, enable bucket lifecycle and versioning where possible and keep provider credentials safe. On Local storage, back up the upload directory — it holds digital deliverables as well as images.

Mongo will not change an existing TTL index

The audit retention change is the clearest example: without pnpm db:migrate:audit-indexes, an upgraded database keeps expiring order history at 90 days no matter what the new code says.

Chapter 27 · Troubleshooting

Fix common issues

Most install problems fall into the same handful of buckets. Walk through these before opening a support ticket.

MONGODB_URI connection refused

Check username, password, database name, and Atlas Network Access allowlist. For self-hosted MongoDB, confirm authSource.

AI features unavailable

Check the key in Admin -> Settings -> AI Configuration (or OPENAI_API_KEY), run the connection test, and confirm the specific surface, plus staff or vendor access, is switched on and within its daily limit.

Payment succeeds but order stays pending

Confirm the gateway webhook URL and secret are configured in the same test or live mode as your keys, and that the endpoint is publicly reachable without authentication.

Vendor paid but the plan never activated

Check that /api/payments/webhook is subscribed to the vendor billing events, that the signing secret matches, and that /api/cron/vendor-subscriptions runs hourly with a valid CRON_SECRET.

Channel messages never arrive

Verify the Meta webhook URL and verify token, that META_APP_SECRET matches the app signing the request, and that MESSAGING_ENCRYPTION_KEY has not changed since the connection was saved.

Outbound messages or emails stall

Both retry through cron routes. Confirm messaging-outbox, messaging-escalations, and email-deliveries are scheduled and returning 200 rather than 401 from a stale CRON_SECRET.

Emails landing in spam

Set up SPF, DKIM, and DMARC for your sender domain. Resend and SendGrid have step-by-step UIs for this.

Product images or 3D models do not show

Check the storage provider settings, public media URL, bucket permissions, file MIME type, and next.config.ts image remotePatterns. After switching providers, confirm the public URL was saved rather than left on a stale draft.

A product reports out of stock incorrectly

Digital products and products with Track quantity off carry no stock count by design. If a physical product is wrong, check Continue selling when out of stock and the per-location inventory rather than the aggregate.

Vendor products missing from POS

Selling vendor-owned stock is opt-in. It needs pos.allowVendorProducts enabled, multi-vendor mode on, and a POS location selected on the register.

A country is missing at checkout

Admin -> Settings -> General holds the country availability policy. If it is set to an allow list, only those countries appear anywhere an address is entered, and the server rejects anything else.

Barcode migration reports duplicates

That is the dry run doing its job. Resolve each duplicate product or variant barcode in the catalog, then re-run the dry pass until it is clean before applying the migration.

pnpm install fails

Delete node_modules only, keep pnpm-lock.yaml, run corepack enable, then run pnpm install again.

Production build refuses to start

The startup preflight rejects an empty, placeholder, or under-32-character BETTER_AUTH_SECRET. Generate a real value with openssl rand -base64 32 and redeploy.

Deletes refused with a demo message

DEMO_MODE is true. Set it to false on a real store; the message shown in admin tables is the server's own refusal, not a generic failure.

Updates·Last updated August 4, 2026
↑ Back to top