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). DeterminesproductLimit.Product.tryOnViewerUrl— the live URL written to thewearfits.tryon_urlmetafield 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 viewerwearfits.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_idis 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:
If the release includes schema changes, run migrations before or during 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:
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
-
PostgreSQL running locally with a
wearfitsdatabase: -
.envconfigured inwearfits-tryon/withDATABASE_URLpointing to local Postgres (see.env.example).
Starting the App
Always run from wearfits-tryon/:
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
Test store: shoes-tryon.myshopify.com
Git Hooks (Required)
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 orwindow.location. - Webhooks: Use app-specific webhooks defined in
shopify.app.toml, not shop-specific webhooks. - Database Tables: Run
npm run setupif 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.