Built for Shopify · self-assessment · v2026-05
Built for Shopify scorecard
A public record of how BulkFlow meets every category of the Built for Shopify requirements. Each row links to the implementation surface — code, settings, or UX — so a reviewer can verify the claim without asking.
32 Met 4 N/A
Last reviewed against BFS criteria on 2026-05-25.
1. Performance
Loads inside the Shopify Admin via App Bridge v4
Embedded with
@shopify/app-bridge/actions v4. Session tokens authenticated server-side via backend/core.py::_try_session_token_user.MET
Initial render < 3s on a cold load
React lazy-loads every route. Code-split by page in
App.js. Initial paint hits the AppBridge boot frame, then suspends until /billing/me resolves.MET
No layout shift after data load
Skeleton placeholders fill the exact final dimensions (
components/Primitives.jsx::TableSkeleton / CardSkeleton) so rows don't jump when real data lands.MET
Lighthouse score ≥ 70 across categories
Verified against production
bulkflow.app. Re-run after every release; failures block the deploy.MET
2. App admin (embedded)
No custom login screen for merchants
Merchants authenticate via Shopify session tokens only. Custom
/login exists exclusively for SuperAdmin internal access on the raw domain — never linked from any merchant surface.MET
No 'Sign out' button inside the embedded frame
Layout.jsx hides the SignOut button when window.parent !== window. Merchants sign out via Shopify.MET
Native look-and-feel matching Polaris
Light-mode UI, Polaris palette tokens 1:1 in
index.css. Reviewed against the Polaris design system documentation.MET
Same brand experience inside & outside the iframe
/welcome (public) and the embedded admin share the same component library, type system, and brand colors.MET
3. App Bridge
App Bridge v4 (latest)
Pinned in
frontend/package.json. We do NOT use the legacy AppBridge React shim.MET
Session token authentication
Every authenticated API call uses a fresh JWT minted by AppBridge, validated server-side against the Shopify-issued public keys.
MET
Resource picker / toast / contextual save bar where appropriate
Toast surfaces (
sonner) match Polaris timing. Contextual save bar present on Settings + bulk-edit modals.MET
4. Online store
Theme app extension
BulkFlow is an admin-only operations app. We do not write to the storefront theme — variant visibility is driven by Shopify's native inventory rules.
N/A
Web Pixel
N/A
5. Privacy & data protection
Mandatory GDPR webhooks registered
customers/data_request, customers/redact, shop/redact all wired in backend/routers/phase2.py. Tested in test_iter24_gdpr_webhooks.py.MET
Data scoped per shop
Every Mongo query funnels through
tenant_scope(shop_id) in backend/core.py. Cross-tenant access is impossible at the query layer. Verified in test_multi_tenant_isolation.py.MET
No customer PII collected
We process Shopify webhook payloads for line-item math (qty × pack_size) only. Customer names/emails/addresses are explicitly stripped before persistence. Confirmed in Privacy Policy §2.
MET
Data deletion on uninstall
shop/redact deletes every BulkFlow row tagged with the merchant's shop_id within 48 hours. The job is idempotent.MET
Public privacy policy
/privacy — plain-English, no jargon.
MET
6. Security
HMAC-verified webhooks
Every webhook handler validates the
X-Shopify-Hmac-Sha256 header before reading the body. Bad HMAC → 401, no further processing.MET
HTTPS-only
Enforced at the ingress layer. No HTTP fallback path exists.
MET
Secrets stored in environment, not code
All API keys/tokens (Shopify client_secret, Resend, MongoDB, etc.) live in
backend/.env — never committed.MET
Audit log of every inventory-changing action
movements + audit_logs collections track every adjustment with actor identity (Shopify staff ID + email, pulled from the session token), timestamp, source (shopify_session_token / legacy_jwt), and target. Exportable as CSV.MET
7. Billing
Shopify-managed billing only
All paid plans use
appSubscriptionCreate via the Shopify Billing API. BulkFlow never stores a credit card. Cancel from Shopify admin → Apps.MET
Trial handled by Shopify, not custom timer
trialDays is passed to the GraphQL mutation; Shopify enforces the trial. Verified in backend/routers/billing.py at billing_subscribe.MET
Free tier with clear upgrade prompts
15-SKU Free tier with live Shopify push-sync included. SKU-cap enforcement returns a 402 with
current_plan + next_plan so the upgrade modal can pitch the right tier. Tested in test_full_tier_gate_audit.py.MET
Public pricing matrix
/welcome#pricing renders the same matrix that drives backend gating (
FEATURE_MATRIX_GROUPS in billing.py). Drift between marketing and code is impossible without a test failure.MET
8. Accessibility
WCAG 2.1 AA conformance target
See full statement at /accessibility.
MET
Keyboard-navigable end-to-end
Every interactive element is a real
<button> or <a>. Modals trap focus. Tooltips reveal on focus, not just hover.MET
Touch targets ≥ 44×44 px
Audit Pass 2 (2026-05-25) added the
.row-action utility class to dense table-row icon buttons across Items / Movements / Orders / Transfers. Form controls + standard buttons already enforced via Primitives.jsx sizing.MET
Screen-reader friendly
Charts have
aria-label. Icons inside icon-only buttons carry aria-label. Status icons (✓/✗) in the pricing matrix sit inside SVGs with semantic role.MET
9. Support & trust
Public support URL with response SLA
/support —
support@bulkflow.app, 1 business day response.MET
Public roadmap
/roadmap — what's shipping next, what shipped, what we explicitly won't build.
MET
In-app feedback widget
Every admin page renders a
FeedbackWidget in the bottom-right; messages route to the same inbox as support@bulkflow.app.MET
10. App listing & marketing
App listing in 1+ languages
Single-language launch (English). Translations queued post-launch.
N/A
App store assets capture pass
Hero screenshots, 60-second journey video, and listing copy live outside the codebase. The /demo?clean=1 route exists specifically for asset capture.
N/A