Admin API vs GraphQL Storefront Comparison

Use Case Admin API Storefront API Recommendation
Custom inventory dashboard ✓ Full read/write ✗ No Admin API
Headless storefront ✗ Server-only ✓ Client or server Storefront API
Order management system ✓ Full CRUD ✗ No Admin API
Product catalog display ✓ Works ✓ Optimized Storefront API
Analytics & reporting ✓ Most data ✗ Limited Admin API
Real-time price updates ✓ Can do ✓ Faster Storefront API + caching

Why Build a Custom Shopify Dashboard

Shopify's native admin dashboard is feature-rich. But it's not designed for specific workflows. If you manage inventory across 5 warehouses, Shopify admin doesn't show you warehouse-level analytics. If you need custom reporting on customer LTV or cohort retention, you're stuck with native reports or third-party tools.

Custom dashboards solve this. They're built for your specific business logic.

A $5M Shopify Plus store that built a custom dashboard saw:

  • 40% faster order fulfillment (warehouse managers see real-time orders in their dashboard, not Shopify admin)
  • 30% fewer inventory mistakes (custom dashboard flags low stock across warehouses automatically)
  • 25% improvement in staff utilization (dashboard automates labor allocation decisions)

The investment: 200-400 developer hours. ROI: Positive within 6 months.

Architecture: Admin API + React Frontend

A custom Shopify dashboard has three layers:

Layer 1: Admin API (Backend)

Shopify's GraphQL Admin API is the data backbone. It exposes:

  • Orders, products, inventory, fulfillment
  • Customers, customer segments, customer journey
  • Analytics (revenue, orders, traffic by source)
  • Financial reports (payments, fees, settlements)

Authentication: OAuth 2.0 with a private app (for staff) or public app (for merchants).

Layer 2: Backend Server (Optional)

For simple dashboards, query the Admin API directly from React. For complex dashboards, add a Node.js/Python backend to:

  • Cache frequently-accessed data (reduce API calls)
  • Aggregate data across multiple Shopify calls (e.g., orders + customer data in one response)
  • Enforce permission control (only managers see financial reports)
  • Batch webhook processing (listen for order changes, update dashboard in real-time)

Layer 3: React Frontend

A React single-page app displays dashboard UI. Libraries:

  • Polaris: Shopify's design system, pre-built components for Shopify dashboards
  • Recharts or Chart.js: Charting and visualization
  • TanStack Query: Caching and syncing data fetches
  • Zustand or Jotai: State management

Step 1: Set Up Admin API Access

Create a private app in Shopify admin (Settings > Apps and Integrations > App and Integration Settings > Develop Apps):

  1. Click "Create an app"
  2. Name it: "Custom Dashboard"
  3. Select "Admin API" scope
  4. Request scopes: read_orders, read_products, read_inventory, read_customers, read_analytics
  5. Generate credentials (API key + access token)

Store credentials in .env:

SHOPIFY_STORE_URL=yourstore.myshopify.com
SHOPIFY_ADMIN_API_KEY=...
SHOPIFY_ADMIN_ACCESS_TOKEN=...

Step 2: Create Backend Server (Node.js)

Set up Express.js with Shopify GraphQL client:

npm install express shopify-app-express dotenv @apollo/client graphql

Create server.js:

import express from "express";
import { createClient } from "graphql-request";
import dotenv from "dotenv";

dotenv.config();

const app = express();

// Initialize Shopify GraphQL client
const client = createClient({
url: https://${process.env.SHOPIFY_STORE_URL}/admin/api/2024-01/graphql.json,
headers: {
"X-Shopify-Access-Token": process.env.SHOPIFY_ADMIN_ACCESS_TOKEN
}
});

// API route: Fetch orders summary
app.get("/api/orders-summary", async (req, res) => {
const query = query { orders(first: 100) { edges { node { id name createdAt totalPriceSet { shopMoney { amount } } fulfillmentStatus } } } } ;

try {
const data = await client.request(query);
res.json(data.orders.edges);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// API route: Fetch inventory
app.get("/api/inventory", async (req, res) => {
const query = query { inventoryLevels(first: 50) { edges { node { id quantities(names: ["available"]) { name quantity } location { name } } } } } ;

try {
const data = await client.request(query);
res.json(data.inventoryLevels.edges);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

app.listen(3001, () => console.log("Server running on :3001"));

Step 3: Build React Frontend

Create a React app with Vite:

npm create vite@latest dashboard -- --template react
cd dashboard
npm install @shopify/polaris recharts @tanstack/react-query axios

Create src/App.jsx:

import React, { useState, useEffect } from "react";
import { Page, Layout, Card, VerticalStack } from "@shopify/polaris";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from "recharts";
import { useQuery } from "@tanstack/react-query";

const apiClient = axios.create({
baseURL: "http://localhost:3001/api"
});

export default function Dashboard() {
// Fetch orders summary
const { data: orders, isLoading: ordersLoading } = useQuery({
queryKey: ["orders"],
queryFn: async () => {
const res = await apiClient.get("/orders-summary");
return res.data;
},
refetchInterval: 60000 // Refetch every 60 seconds
});

// Fetch inventory
const { data: inventory } = useQuery({
queryKey: ["inventory"],
queryFn: async () => {
const res = await apiClient.get("/inventory");
return res.data;
}
});

return (
<Page title="Custom Dashboard">
<Layout>
<Layout.Section>
<Card>
<VerticalStack gap="5">
<h2>Orders Today</h2>
{ordersLoading ? (
<p>Loading...</p>
) : (
<p>{orders?.length || 0} orders</p>
)}
</VerticalStack>
</Card>
</Layout.Section>

    &lt;Layout.Section&gt;
      &lt;Card&gt;
        &lt;VerticalStack gap="5"&gt;
          &lt;h2&gt;Inventory by Location&lt;/h2&gt;
          {inventory?.map((inv) =&gt; (
            &lt;div key={inv.node.id}&gt;
              &lt;p&gt;
                {inv.node.location.name}:{" "}
                {inv.node.quantities[0].quantity} units
              &lt;/p&gt;
            &lt;/div&gt;
          ))}
        &lt;/VerticalStack&gt;
      &lt;/Card&gt;
    &lt;/Layout.Section&gt;
  &lt;/Layout&gt;
&lt;/Page&gt;

);
}

Step 4: Add Real-Time Updates with Webhooks

Listen for Shopify events (order created, inventory changed) and push updates to dashboard via WebSocket.

Set up webhook receiver in server.js:

app.post("/webhooks/orders/create", (req, res) => {
  const order = req.body;

// Broadcast to all connected WebSocket clients
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: "ORDER_CREATED",
order: order
}));
}
});

res.status(200).send("OK");
});

In React, listen to WebSocket:

useEffect(() => {
  const ws = new WebSocket("ws://localhost:3001");

ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "ORDER_CREATED") {
// Update dashboard in real-time
queryClient.invalidateQueries({ queryKey: ["orders"] });
}
};

return () => ws.close();
}, []);

Real-World Example: A $8M Shopify Plus Apparel Store

Threads & Co. sells premium apparel, $8M annual revenue, 3 warehouses, 250 SKUs. Shopify admin didn't show warehouse-level inventory or fulfillment speed.

Custom Dashboard Features:

  • Warehouse View: Each warehouse sees its orders, inventory, and fulfillment queue
  • KPI Dashboard: Real-time orders, revenue, average fulfillment time
  • Inventory Forecast: Predicts stock-outs 7 days ahead (based on sales velocity)
  • Staff Performance: Each warehouse manager sees their fulfillment speed vs. team average

Implementation:

  • Backend: Node.js + Shopify Admin API (300 lines)
  • Frontend: React + Polaris (500 lines)
  • WebSocket for real-time order updates
  • Deployment: Vercel (frontend) + AWS EC2 (backend)

Results:

  • Fulfillment time dropped from 2.1 days to 1.3 days
  • Inventory accuracy improved 28% (staff respond faster to low stock)
  • Warehouse utilization increased 18% (dashboard shows bottlenecks)
  • Development cost: $15K (outsourced developer), ROI: Positive within 4 months

Common Mistakes

Mistake 1: Over-Fetching Data

Don't fetch all orders and products on page load. Use pagination and filters:

query {
  orders(first: 50, query: "status:unfulfilled") {
    # Only unfulfilled orders, limit 50
  }
}

Mistake 2: Not Caching

Every API call costs latency and Shopify API rate limits. Implement caching:

// Cache orders for 5 minutes
const cachedOrders = cache.get("orders");
if (cachedOrders && Date.now() - cachedOrders.time < 300000) {
  return cachedOrders.data;
}

Mistake 3: Ignoring Rate Limits

Shopify Admin API has rate limits (2 calls/second for standard plans). Batch requests and implement exponential backoff:

async function fetchWithRetry(query, retries = 3) {
  try {
    return await client.request(query);
  } catch (error) {
    if (error.message.includes("Rate limited") && retries > 0) {
      await new Promise(r => setTimeout(r, 1000 * (4 - retries)));
      return fetchWithRetry(query, retries - 1);
    }
    throw error;
  }
}

Performance Tips

Data Fetching:

  • Use GraphQL aliases to batch multiple queries into one request
  • Implement pagination (fetch 50 items at a time, not 1,000)
  • Cache frequently-accessed data (KPIs, inventory) server-side

Frontend:

  • Lazy-load components (don't render all charts on page load)
  • Implement infinite scroll for large data lists
  • Use React Query for automatic cache invalidation

Infrastructure:

  • Deploy backend close to Shopify (AWS us-east-1 region)
  • Use CDN for frontend assets (Vercel, Netlify handle this automatically)

Ready to Build Your Custom Dashboard?

Custom Shopify dashboards solve workflows that off-the-shelf tools can't. Stop copying data into spreadsheets. Build a real-time operations dashboard.

Contact tenten.co/contact to architect your custom dashboard.

Explore more Shopify API development techniques to ensure your dashboard is secure and performant.


Editorial Note: Custom dashboards are the hidden productivity leverage in scaling Shopify operations. Teams that invest 200 hours in custom dashboards save 1,000+ hours annually in manual data work.

Frequently Asked Questions

Should I use Shopify's native dashboard or build custom?

Use native for standard reporting (revenue, orders by source). Build custom if you have workflow-specific needs (warehouse management, custom metrics, multi-location coordination, or complex automation).

How do I keep my dashboard real-time without hammering the API?

Use webhooks for high-frequency events (order creation, inventory changes). Update dashboard immediately on webhook, refresh summary data every 60 seconds via API. This balances freshness and rate limits.

What's the development cost for a custom dashboard?

Simple dashboard (3-4 pages, basic charts): 100-150 hours ($10K-15K outsourced). Mid-complexity (warehouse management, KPI tracking, 8-10 pages): 250-350 hours ($20K-30K). Enterprise (multi-location, real-time sync, complex logic): 500+ hours ($40K-60K).

Can I use Shopify's built-in dashboard customization instead of building custom?

Shopify's dashboard customization (custom cards, metrics) is limited. It's sufficient for simple KPI monitoring but can't handle workflow-specific needs (warehouse routing, custom inventory logic). Custom React dashboards offer unlimited flexibility.

How often should my dashboard refresh data?

Real-time critical data (orders, fulfillment status): 30-60 second refresh. Summary metrics (daily revenue, KPIs): 5-10 minute refresh. Historical data (trends, monthly reports): hourly refresh. Use webhooks for event-driven updates on critical data.