Developer reference

Building a Custom Shopify App for Product Options

Off-the-shelf apps cover 90% of personalization needs. The other 10% — proprietary print-file specs, custom brand-approval workflows, non-standard POD integrations, ERP handshakes — is where a custom Shopify app earns its cost. Here is the modern 2026 architecture: Remix + Prisma + Polaris, theme app extensions for the storefront UI, Cart Transform Functions for pricing, checkout extensions for post-purchase, and Shopify-hosted deployment.

Last updated: August 23, 2026~13 min readBy the Print It My Way team

Build a custom app or use an existing one?

Before you build: audit whether Print It My Way, Bold Options, Infinite Options, or Zakeke already handles your workflow. Custom apps take 3-6 months to build and 30-60 hrs/month to maintain. If your only "custom" requirement is a specific printer's file spec, integrate that spec through an existing app's webhook rather than building from scratch. Custom is warranted when: (1) you have proprietary IP you don't want in a third-party app's database, (2) you need integrations no app supports (weird ERP, custom fulfillment routing across 5+ partners), or (3) you're a Shopify agency selling a productized service.

The modern 2026 stack

App Router: Remix (Shopify's official framework)
Language:   TypeScript
DB:         Prisma + Postgres (or Cloudflare D1 for edge)
UI:         Polaris (admin UI kit)
Storefront: Theme App Extensions (block-based UI)
Pricing:    Cart Transform Functions (server-side JS)
Checkout:   Checkout UI Extensions
Hosting:    Shopify Hydrogen/Oxygen or Vercel or Fly.io

Bootstrapping with the CLI

npm create @shopify/app@latest -- --template remix
cd my-personalization-app
npm run dev

The CLI scaffolds a Remix app with authentication, webhook subscription, App Bridge, Polaris, and Prisma schema pre-wired. Login as a partner, connect to a development store, and iterate.

Theme App Extensions for storefront UI

Theme App Extensions replace the old "hidden product with special metafields" hack. You ship a JS + Liquid block that merchants add to their product page via the theme editor. Your app renders the personalization option selector inside the block.

extensions/personalizer-block/
├── shopify.extension.toml
├── blocks/personalizer.liquid
├── assets/personalizer.js
└── locales/en.default.json

Your personalizer.js reads the product's variant metafields (personalization spec, DPI, max chars) and renders the appropriate input fields. On add-to-cart, it attaches personalization data as line-item properties.

Cart Transform Function for pricing

// extensions/pricing/src/index.ts
export function run(input) {
  const operations = [];
  for (const line of input.cart.lines) {
    const propMap = Object.fromEntries((line.attributes ?? []).map(a => [a.key, a.value]));
    let extra = 0;
    if (propMap["Custom text"]) extra += 3.99;
    if (propMap["Photo"]) extra += 6.99;
    if (extra > 0) {
      operations.push({
        update: {
          cartLineId: line.id,
          price: { adjustment: { fixedPricePerUnit: {
            amount: (line.cost.amountPerQuantity.amount + extra).toFixed(2)
          }}}
        }
      });
    }
  }
  return { operations };
}

Checkout UI Extensions for post-purchase

Show a personalization proof, upsell related items, or capture additional print instructions during Shopify's checkout flow. Extension runs in a sandboxed iframe with access to cart + buyer identity via the Checkout Extension API.

Database schema for personalization data

// prisma/schema.prisma
model Shop {
  id            String   @id
  domain        String   @unique
  accessToken   String
  plan          String   @default("free")
  createdAt     DateTime @default(now())
  optionSets    OptionSet[]
}

model OptionSet {
  id          String @id @default(cuid())
  shopId      String
  productGid  String  // gid://shopify/Product/...
  spec        Json    // { options: [...], pricing: {...} }
  createdAt   DateTime @default(now())
  shop        Shop @relation(fields: [shopId], references: [id])
  @@index([shopId, productGid])
}

Hosting

Hosting optionBest forFree tier?Notes
Shopify OxygenHydrogen apps onlyYes (Plus)Edge, integrated CI, Shopify-owned
VercelNext.js / RemixYes (hobby)Best DX; edge functions; usage-based pricing
Fly.ioAny Docker appYes (small tier)Regions worldwide, cheap DB
Cloudflare WorkersEdge-first, JS-onlyYesD1 for DB, KV for sessions
Render / RailwayTraditional web appsYes (small tier)Simple Postgres + web

App Store listing (public apps)

If distributing publicly: Partners → Apps → Create app → follow the App Store checklist (screenshots, video demo, GDPR compliance webhooks, uninstall webhook, mandatory data-request webhook, privacy policy, support email). Review takes 3-8 weeks; the top rejection reason is missing GDPR webhooks (customers/data_request, customers/redact, shop/redact).

Billing (App Subscription API)

Use Shopify's App Subscription API for recurring pricing. Shopify handles the payment flow inside the admin; you charge $X/month and Shopify remits (minus their revenue share, currently 15% up to $1M/year then 20%).

Frequently asked questions

What's the modern stack for a custom Shopify personalization app?

Remix + TypeScript + Prisma + Postgres + Polaris for the admin UI, Theme App Extensions for the storefront UI, Cart Transform Functions for pricing, Checkout UI Extensions for checkout, hosted on Oxygen/Vercel/Fly.

Should I build a custom app or use an existing one?

Existing apps first — Print It My Way, Bold Options, Infinite Options, Zakeke cover 90% of personalization needs. Build custom only when you have proprietary IP, need unsupported integrations, or run an agency selling a productized service.

How do I bootstrap a Shopify app project?

npm create @shopify/app@latest -- --template remix — Shopify's CLI scaffolds a Remix app with auth, webhooks, App Bridge, Polaris, and Prisma pre-wired.

Where does the personalization storefront UI live in a custom app?

In a Theme App Extension — a Liquid block + JS bundle the merchant adds to their product page via the theme editor. Replaces the old hidden-product hack and gives you first-class control over the option selector UI.

How do I price personalization add-ons in a custom app?

Cart Transform Function — a server-side JavaScript function that runs at checkout and modifies line-item prices based on cart attributes. Ships as an extension inside your app.

Where should I host my custom Shopify app?

Vercel or Fly.io for most cases — best DX, generous free tier. Shopify Oxygen if you're building on Hydrogen (Plus stores). Cloudflare Workers if you need edge-first and JS-only.

What GDPR webhooks does my Shopify app need?

Three mandatory webhooks: customers/data_request, customers/redact, shop/redact. Missing any of them is the #1 App Store rejection reason.

How does Shopify's revenue share on app billing work?

Shopify takes 15% of app subscription revenue up to $1M/year per app, then 20% above that. Payment flow happens inside Shopify admin; you receive net-of-share monthly.

Related reading

Try it free on Shopify

Print It My Way's permanent Free plan is built on this exact stack — Remix + Cart Transform + Theme App Extensions — so you get production-grade personalization without building from scratch.

Install Print It My Way