Skip to content

Shopify App — Internal Code Documentation

The WEARFITS Try-On Shopify App (wearfits-tryon) enables virtual shoe try-on for Shopify merchants. It is a full-stack embedded application running inside Shopify Admin, supplemented by multiple Shopify Extensions for the admin UI and storefront. This document covers the internal architecture, data model, integrations, and deployment workflow.


Architecture

The application follows the standard Shopify-Remix (React Router v7) pattern with a clear separation of concerns across HTTP routes, service layer, and data access.

graph TD
    subgraph "Routes (HTTP Layer)"
        AR[app/_index.jsx - Dashboard]
        AW[app/wizard.jsx - Wizard]
        AT[app/tryons.jsx - Management]
        AS[app/settings.jsx - Customization]
        AP[api/sync-batches.jsx - Sync Cron]
        AQ[api/report-quality-issue.jsx - Support]
    end

    subgraph "Services (Business Logic)"
        SCL[shopCleanup.server.ts]
        SIB[shopifyBilling.server.ts]
        SWA[wearfitsApi.server.ts]
        SIA[imageAnalysis.server.ts]
        SWH[hubspotIntegration.server.ts]
    end

    subgraph "Models (Data Access)"
        DB[app/db.server.ts - Prisma Client]
    end

    AR & AW & AT & AS & AP & AQ -- calls --> SCL & SIB & SWA & SIA & SWH
    SCL & SIB & SWA & SIA & SWH -- orm --> DB

Monorepo Structure

The repository is organised as an npm workspace:

.
├── extensions/                    # Shopify App Extensions
│   ├── admin-block/               # Inline card on product detail pages
│   ├── enable-shoes-tryon/        # Admin Action modal launcher
│   └── wearfits-tryon-button/     # Theme App Extension (storefront injection)
├── wearfits-tryon/                # Main full-stack application (App Home)
│   ├── app/
│   │   ├── routes/                # Page routes and API endpoints
│   │   ├── services/
│   │   │   ├── wearfits/          # Batch submission and status polling
│   │   │   ├── shopifyBilling.server.ts
│   │   │   ├── imageAnalysis.server.ts
│   │   │   └── hubspotIntegration.server.ts
│   │   ├── components/            # Shared Polaris web components
│   │   ├── hooks/                 # Custom React hooks (e.g., usePolarisModal)
│   │   ├── db.server.ts           # Prisma client initialisation
│   │   └── shopify.server.ts      # Shopify API setup and auth contexts
│   ├── prisma/
│   │   ├── schema.prisma
│   │   └── migrations/
│   └── tests/                     # Vitest unit tests
├── shopify.app.toml               # Global app configuration
└── package.json                   # Workspace orchestration

Database Schema

The application uses PostgreSQL via Prisma. The schema tracks shop state, onboarding progress, product processing, and support tickets.

erDiagram
    Session {
        string id PK
        string shop
        string accessToken
    }
    ShopSettings {
        string id PK
        string shop UK
        string plan
        int productLimit
        boolean tryOnEnabled
        boolean wizardCompleted
        int wizardStep
    }
    WizardState {
        string id PK
        string shop UK
        int currentStep
        boolean syncCompleted
        boolean categoriesSelected
    }
    ProductCategory {
        string id PK
        string shop
        string categoryName
        int productCount
        int modelsReady
        boolean tryOnEnabled
        int priority
    }
    Product {
        string id PK
        string shop
        string shopifyProductId UK
        string title
        string status
        boolean qualifiesForTryOn
        string tryOnViewerUrl
    }
    ProductImage {
        string id PK
        string productId FK
        string url
        boolean isShoeImage
        int suitabilityScore
    }
    WearFitsBatch {
        string id PK
        string shop
        string wearfitsBatchId UK
        string status
        int totalItems
    }
    WearFitsJob {
        string id PK
        string shop
        string wearfitsJobId UK
        string status
        string tryOnViewerUrl
        string batchId FK
        string productId FK
    }
    QualityTicket {
        string id PK
        string shop
        string productId FK
        string issueType
        string hubspotTicketId
        string status
    }

    ShopSettings ||--o| WizardState : "tracks"
    Product ||--o{ ProductImage : "contains"
    Product ||--o{ WearFitsJob : "queued_as"
    WearFitsBatch ||--o{ WearFitsJob : "groups"
    Product ||--o{ QualityTicket : "reported_issues"

Key Entity Notes

  • ShopSettings.plan — the active Shopify billing plan (bronze, silver, gold). Determines productLimit.
  • Product.tryOnViewerUrl — the live URL written to the wearfits.tryon_url metafield on the Shopify product.
  • WearFitsJob.status — mirrors the status returned by the WearFits API: pending, processing, completed, failed.
  • ProductImage.suitabilityScore — assigned by the AI image analysis step; used to determine category eligibility during onboarding.
  • QualityTicket.hubspotTicketId — reference to the corresponding ticket in HubSpot for tracking.

Integrations

1. Shopify Admin API

  • Authentication: Managed by @shopify/shopify-app-react-router.
  • Protocol: GraphQL via Shopify's Admin API.
  • Key Operations: Product fetching, category synchronisation, theme embed validation.
  • Metafield Writes: The app writes two metafields per product after 3D model generation:
  • wearfits.tryon_url — URL to the try-on viewer
  • wearfits.enabled — boolean flag read by the storefront extension

2. WearFits API

  • Purpose: Generates 3D models from shoe packshot images.
  • Client: app/services/wearfitsApi.server.ts
  • Protocol: Batch submission model — images are uploaded in a batch, a batch_id is returned, and a background cron job polls for completion.
  • Try-On URL Pattern: https://dev.wearfits.com/tryon?object=... (strictly enforced).

3. OpenRouter (AI Image Analysis)

  • Purpose: Classifies images as shoe or non-shoe and scores suitability for AR try-on.
  • Client: app/services/imageAnalysis.server.ts
  • Logic: High-priority categories have a sample of 3 products analysed. If the sample passes, the entire category is considered eligible. This avoids running AI on every product during onboarding.

4. HubSpot CRM

  • Purpose: Syncs quality issue reports from merchants into HubSpot support tickets.
  • Auth: API key-based.
  • Client: app/services/hubspotIntegration.server.ts
  • Mapping: Each report is linked to the Shopify product and includes the current try-on viewer URL for context.

Key Flows

Onboarding Wizard

The wizard runs once per shop after installation and guides the merchant through category selection and AI validation.

sequenceDiagram
    participant M as Merchant
    participant W as Wizard Page
    participant S as Shopify API
    participant AI as AI Service (OpenRouter)

    M->>W: App installed
    W->>S: Fetch Products & Categories
    S-->>W: Categories list
    M->>W: Select shoe categories
    W->>AI: Validate sample products (3 per high-priority category)
    AI-->>W: Validation result (pass / fail per category)
    M->>W: Confirm eligible categories & priority order
    W-->>M: Redirect to Dashboard

3D Model Generation (Batch Flow)

Model generation is driven by a background cron job hitting /api/sync-batches. The flow is asynchronous — product status is updated once the WearFits API reports completion.

sequenceDiagram
    participant C as Cron Job
    participant SB as /api/sync-batches
    participant WAPI as WearFits API
    participant DB as PostgreSQL
    participant SH as Shopify Admin API

    C->>SB: POST /api/sync-batches (CRON_SECRET)
    SB->>DB: Find pending products (no active job)
    DB-->>SB: Product list with images
    SB->>WAPI: Create Batch (upload images)
    WAPI-->>SB: batch_id
    SB->>DB: Create WearFitsBatch + WearFitsJob records (status: processing)

    Note over SB, WAPI: Subsequent cron runs
    SB->>WAPI: GET batch status (batch_id)
    WAPI-->>SB: Job results (completed / failed)
    SB->>DB: Update WearFitsJob.status + tryOnViewerUrl
    SB->>SH: Write wearfits.tryon_url + wearfits.enabled metafields
    SB->>DB: Update Product.tryOnViewerUrl

Plan Limits

Plan limits are defined as constants in shopifyBilling.server.ts:

Plan Product Limit
Bronze 10
Silver 100
Gold 250

These values are fixed in code and enforced during sync batch creation. Changing a shop's plan updates ShopSettings.plan and ShopSettings.productLimit accordingly.


Tech Stack

Component Technology
Core framework React Router v7 (Remix)
Language TypeScript / JavaScript
Runtime Node.js v20+
Database PostgreSQL with Prisma ORM
UI components Shopify Polaris (web components, not React Polaris)
Styling Vanilla CSS / CSS Modules
Build tool Vite
Testing Vitest (65 tests)
App Home hosting Vercel
Extensions deployment Shopify CLI

Deployment

App Home (Vercel)

The App Home is hosted on Vercel. The Vercel project has Root Directory = wearfits-tryon configured, so the deploy command is run from the repository root:

cd shopify-wearfits  # repo root
vercel deploy --prod --yes

If the release includes schema changes, run migrations before or during deploy:

cd wearfits-tryon
npx prisma migrate deploy

Migrations are idempotent and safe to run even when the schema has not changed.

Extensions (Shopify CLI)

Extensions are deployed independently via Shopify CLI:

shopify app deploy

Important: Extension deploys take effect in production immediately. There is no staging for extensions.

Database

Production uses Prisma Accelerate (cloud PostgreSQL proxy). Local development connects directly to a local PostgreSQL instance.


Local Development

Prerequisites

  1. PostgreSQL running locally with a wearfits database:

    brew services start postgresql@14
    createdb wearfits
    

  2. .env configured in wearfits-tryon/ with DATABASE_URL pointing to local Postgres (see .env.example).

Starting the App

Always run from wearfits-tryon/:

cd wearfits-tryon
npm run dev

This starts the Shopify CLI, opens a Cloudflare tunnel, and launches the React Router dev server. The embedded preview URL opens the app inside Shopify Admin.

Use the Preview URL (not the raw tunnel URL) for day-to-day testing — it provides the correct Shopify Admin context.

Embedded Testing URL

https://admin.shopify.com/store/<store>/apps/<app-id>?dev-console=show

Test store: shoes-tryon.myshopify.com

Git Hooks (Required)

git config core.hooksPath .githooks

The pre-push hook runs npm run lint and npm run typecheck. The push is aborted on failure.

CLI Commands

App Home (wearfits-tryon/)

Command Description
npm run dev Start full app (Dashboard + Extensions)
npm run setup Run Prisma generate + migrations
npm run build Build for production
npm run check Lint + typecheck + tests in one command
npm run prisma studio Inspect database via Prisma Studio

Root Workspace (Extensions only)

Command Description
npm run dev Start Shopify CLI dev server (extensions only)
npm run build Build all extensions
npm run deploy Deploy extensions to Shopify Partners

Testing

Tests are located in wearfits-tryon/tests/ and run with Vitest.

cd wearfits-tryon
npm test              # Run all 65 tests once
npm run test:watch    # Watch mode
npm run check         # Lint + typecheck + tests

Test coverage includes: - Wizard state management and step transitions - Category validation logic and priority ordering - Plan limit enforcement - Try-on enable / disable / remove flows - Quality ticket creation - Storefront customisation options


Key Files Reference

File Purpose
app/routes/app.wizard.jsx Post-installation 5-step onboarding wizard
app/routes/app._index.jsx Main dashboard with metrics and batch processing
app/routes/app.tryons.jsx Try-on management and product status table
app/routes/app.settings.jsx Storefront customisation settings
app/routes/api.sync-batches.jsx Cron endpoint for batch submission and polling
app/services/wearfitsApi.server.ts WearFits API client
app/services/shopifyBilling.server.ts Plan management and limit constants
app/services/imageAnalysis.server.ts AI image classification via OpenRouter
app/services/hubspotIntegration.server.ts HubSpot quality ticket creation
prisma/schema.prisma Database schema

Common Gotchas

  • Navigation: Use App Bridge navigation inside embedded apps — do not use standard <a> links or window.location.
  • Webhooks: Use app-specific webhooks defined in shopify.app.toml, not shop-specific webhooks.
  • Database Tables: Run npm run setup if you encounter "table does not exist" errors.
  • Polaris: This app uses Polaris web components, not the React Polaris library.
  • WSL2 Tunnel Fallback: If the Cloudflare tunnel fails on WSL2, use ngrok via npm run tunnel / npm run dev:tunnel.