Open-source Shopify app that turns your products into scheduled social media posts across 13 platforms — Instagram, TikTok, X, Facebook, LinkedIn, YouTube, Threads, Pinterest, Bluesky, Reddit, Telegram, Google Business, Snapchat — powered by Zernio's social APIs.
Shopify Admin ──▶ This App ──▶ Zernio API ──▶ 13 social platforms
Live at store.zernio.com · Free · MIT licensed · No install fee, no per-post charge.
Manual publishing
- Browse your Shopify catalog with per-product post counts
- One-click "Share to social" → composer with pre-filled caption, images, and platform selector
- Per-platform overrides — write one shared caption, override per platform with its own text and images, with live character-count badges (280 for X, 2200 for Instagram, 3000 for LinkedIn, etc.)
- Schedule immediately or at a specific date/time in your shop's timezone
Bulk scheduling
- Multi-select products and bulk-schedule with cadence (1 / 15 min / 30 min / 1 hr / 4 hr / 12 hr / 24 hr)
- Preview the full timeline before committing
Templates
- Reusable caption templates with mustache variables:
{{title}},{{price}},{{url}},{{description}} - Scoped to specific platforms and accounts
- Optional delay or fixed time-of-day for auto-publish scheduling
Auto-publish triggers (opt-in, template-gated)
- On product created
- On price drop (
compare_at_price>price, deduped 1 hr per product) - On back-in-stock (any variant 0 → positive, deduped 24 hr per product)
- Safety net: triggers only fire when you've configured a template with at least one target account — no accidental broadcast to every connected account
UTM tracking
- One toggle appends
utm_source=zernio&utm_medium=social&utm_campaign=<platform>to every storefront URL in post content
Live post history
- Filters by status, trigger, platform, date range
- Per-platform status dots
- Inline expand with full details, retry CTA, deep link into Zernio
Compliance
- AES-256-GCM encryption for stored API keys
- Mandatory GDPR webhooks (
customers/data_request,customers/redact,shop/redact) shop/redactfully cascades throughPostLog,PostTemplate,InventorySnapshot
- Shopify Partner account with a development store
- Zernio account + API key (get one)
- Node.js 20.19+ or 22.12+
- PostgreSQL (Neon free tier works great)
git clone https://github.com/zernio-dev/zernio-shopify.git
cd zernio-shopify
npm install
cp .env.example .env
# edit .env (see Environment Variables below)
npx prisma db push # create tables
npm run dev # starts Shopify CLI dev server + tunnelPress P to open the app URL, click Install on your development store, paste your Zernio API key.
| Variable | Description | Required |
|---|---|---|
SHOPIFY_API_KEY |
Shopify app API key (Partner Dashboard) | Yes |
SHOPIFY_API_SECRET |
Shopify app secret | Yes |
SCOPES |
Shopify access scopes — read_products,read_inventory |
Yes |
SHOPIFY_APP_URL |
Deployed app URL (e.g. https://store.zernio.com) |
Yes |
DATABASE_URL |
PostgreSQL connection string | Yes |
ENCRYPTION_KEY |
64-char hex for API key encryption | Yes |
SHOP_CUSTOM_DOMAIN |
Custom domain for Shopify Plus stores | No |
Generate an encryption key:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"- Framework: React Router v7 (Shopify's official template)
- UI: Polaris Web Components (
<s-page>,<s-section>,<s-stack>,<s-empty-state>, etc.) — no React Polaris, no custom CSS framework - Database: PostgreSQL via Prisma
- Auth: Shopify App Bridge + session tokens
- Zernio: thin typed REST client in
app/lib/zernio-client.ts
app/
lib/
auto-post.server.ts # shared auto-publish flow (products + inventory webhooks)
encryption.server.ts # AES-256-GCM
shopify-products.server.ts # GraphQL queries
utm.server.ts # UTM injection
zernio-client.ts # Typed Zernio REST client
routes/
app.tsx # layout + <s-app-nav>
app._index.tsx # dashboard / onboarding
app.products.tsx # product grid + multi-select
app.compose.tsx # composer with per-platform overrides
app.posts.tsx # history + filters + detail
app.templates._index.tsx # template list
app.templates.$id.tsx # template editor
app.bulk-schedule.tsx # bulk scheduling flow
app.settings.tsx # connection, defaults, auto-publish, UTM
api.*.tsx # XHR endpoints (create-post, upsert/delete-template, bulk-create-posts, verify-key, update-settings, zernio-webhook)
webhooks.*.tsx # Shopify webhooks (products, inventory, compliance, app-uninstalled, scopes-update)
_index/ # public landing at store.zernio.com
shopify.server.ts # Shopify app config
db.server.ts # Prisma singleton
prisma/
schema.prisma # Session, ShopConfig, PostTemplate, PostLog, InventorySnapshot
shopify.app.toml # scopes + webhook subscriptions
- Merchant installs → paste Zernio API key → app encrypts and stores
- App reads Shopify products via Admin GraphQL
- Merchant composes a post (or sets up a template) → app calls
POST /v1/postson Zernio withmetadata: { source: "shopify", productId, shopDomain } - Zernio handles platform publishing and fires back a webhook to
/api/zernio-webhookso thePostLog.statusstays current - Product lifecycle webhooks (
products/create,products/update,inventory_levels/update) trigger the corresponding auto-publish template — only if a matching active template exists
- Import the repo in Vercel
- Attach a PostgreSQL database via Neon integration
- Set environment variables
- Deploy (the build script runs
prisma generateautomatically) - Update
application_urlinshopify.app.toml shopify app deployto push scopes + webhook subscriptions to Shopify
docker build -t zernio-shopify .
docker run -p 3000:3000 --env-file .env zernio-shopify- Never run auto-post with
publishNow: truein tests — it will fan out to every connected account. Use a scoped template withautoPublishDelay: 999999or setpublishNow: falsewith a far-futurescheduledFor. - Every
deleteManythat includes a shop string MUST validate it first. An unvalidatedwhere: { shop: undefined }becomes "delete every row across every shop." Seewebhooks.compliance.tsxfor theisValidShoppattern. - Auto-publish requires a template with non-empty
accountIds. The fallback-to-every-account path was removed. New trigger types should follow the same pattern. - Polaris web components don't auto-space their children. Wrap multiple siblings inside
<s-section>/<s-banner>/<s-empty-state>in an<s-stack direction="block" gap="base">so buttons don't crash into prose.
See CONTRIBUTING.md. PRs welcome — repo is MIT.
- Zernio — social APIs for developers
- Zernio API Docs
- Shopify App Development