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

Start free trialAdd as a preferred source on Google

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

Sources & further reading

Primary sources referenced in this article. We link to official platform documentation and standards bodies rather than secondary summaries so you can verify claims directly.

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