Skip to main content
Environment variables are critical for configuring the Shopify Subscriptions Reference App. This guide covers all required and optional environment variables.

Required Environment Variables

Never commit environment variables containing secrets to version control. Use .env files locally and secure secret management in production.

Shopify App Configuration

1

SHOPIFY_API_KEY

Your Shopify app’s API key (Client ID).Where to find it: Shopify Partner Dashboard → Apps → Your App → App Credentials
SHOPIFY_API_KEY="your_api_key_here"
This is a public value that can be safely exposed to the frontend.
2

SHOPIFY_API_SECRET

Your Shopify app’s API secret (Client Secret).Where to find it: Shopify Partner Dashboard → Apps → Your App → App Credentials
SHOPIFY_API_SECRET="your_api_secret_here"
This is a sensitive secret. Never expose it to the frontend or commit it to version control.
3

SCOPES

Comma-separated list of OAuth scopes your app requires.
SCOPES="customer_read_customers,customer_read_orders,customer_read_own_subscription_contracts,customer_write_customers,customer_write_own_subscription_contracts,read_all_orders,read_locales,read_locations,read_themes,write_customer_payment_methods,write_customers,write_metaobject_definitions,write_metaobjects,write_orders,write_own_subscription_contracts,write_products,write_translations,read_analytics"
These scopes match those defined in shopify.app.toml. Keep them in sync.
4

SHOPIFY_APP_URL

The public URL where your app is hosted.
# Production
SHOPIFY_APP_URL="https://your-app.example.com"

# Development (set by Shopify CLI)
SHOPIFY_APP_URL="https://your-tunnel.cloudflare.com"
In development, the Shopify CLI automatically sets this using a tunnel URL. In production, use your actual domain.

Database Configuration

1

DATABASE_URL

Your database connection string.
DATABASE_URL="file:./prisma/data.db"
Use SSL connections in production: ?sslmode=require for PostgreSQL

Optional Environment Variables

Application Configuration

# Node environment
NODE_ENV="production"  # Options: production, development, test

# Application port (default: 3000)
PORT="3000"

# Frontend dev server port (default: 8002)
FRONTEND_PORT="8002"

# Custom shop domain (if applicable)
SHOP_CUSTOM_DOMAIN="custom-shop.example.com"

# Maintenance mode flag
MAINTENANCE_MODE="false"

# Git commit hash (for tracking deployments)
GITHUB_SHA="abc123"

Google Cloud Platform (Production Background Jobs)

If using Google Cloud Tasks for background job processing:
# GCP Project ID
GCP_PROJECT_ID="your-project-id"

# GCP Region
GCP_REGION="us-central1"

# Cloud Tasks queue prefix
CLOUD_TASKS_QUEUE_PREFIX="subscriptions-app"

# Service account email
GCP_SERVICE_ACCOUNT_EMAIL="[email protected]"

# Cloud Tasks callback URL
CLOUD_TASKS_CALLBACK_URL="https://your-app.example.com/internal/jobs/run"

# Cloud Tasks audience (for authentication)
CLOUD_TASKS_AUDIENCE="https://your-app.example.com/"
Google Cloud configuration is optional. The app can use inline job processing without GCP.

App Configuration

# App GID (Global ID) - if required by your setup
APP_GID="gid://shopify/App/12345678"

# App URL for extensions (used in buyer-facing extensions)
APP_URL="https://your-app.example.com"

Environment File Structure

Development (.env.development)

Create a .env.development file for local development:
# Shopify Configuration
SHOPIFY_API_KEY="dev_api_key"
SHOPIFY_API_SECRET="dev_api_secret"
SCOPES="customer_read_customers,customer_read_orders,customer_read_own_subscription_contracts,customer_write_customers,customer_write_own_subscription_contracts,read_all_orders,read_locales,read_locations,read_themes,write_customer_payment_methods,write_customers,write_metaobject_definitions,write_metaobjects,write_orders,write_own_subscription_contracts,write_products,write_translations,read_analytics"
SHOPIFY_APP_URL="https://your-dev-tunnel.cloudflare.com"

# Database
DATABASE_URL="file:./prisma/data.db"

# Application
NODE_ENV="development"
PORT="3000"
FRONTEND_PORT="8002"

Testing (.env.test)

Create a .env.test file for running tests:
# Shopify Configuration (test credentials)
SHOPIFY_API_KEY="test_api_key"
SHOPIFY_API_SECRET="test_api_secret"
SCOPES="customer_read_customers,customer_write_customers"
SHOPIFY_APP_URL="http://localhost:3000"

# Database (in-memory or test database)
DATABASE_URL="file::memory:"

# Application
NODE_ENV="test"

Production (.env.production)

Never create a .env.production file in your repository. Use your hosting provider’s environment variable management instead.
Configure these variables in your production hosting environment:
# Shopify Configuration
SHOPIFY_API_KEY="prod_api_key"
SHOPIFY_API_SECRET="prod_api_secret"  # Store as secret
SCOPES="customer_read_customers,..."   # Full scope list
SHOPIFY_APP_URL="https://your-app.example.com"

# Database
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"  # Store as secret

# Application
NODE_ENV="production"
PORT="3000"

# Optional: Google Cloud (if using Cloud Tasks)
GCP_PROJECT_ID="your-project-id"
GCP_REGION="us-central1"
CLOUD_TASKS_QUEUE_PREFIX="prod-subscriptions"
GCP_SERVICE_ACCOUNT_EMAIL="[email protected]"
CLOUD_TASKS_CALLBACK_URL="https://your-app.example.com/internal/jobs/run"
CLOUD_TASKS_AUDIENCE="https://your-app.example.com/"

# Monitoring
GITHUB_SHA="${GITHUB_SHA}"  # Automatically set in CI/CD

Security Best Practices

1

Use .gitignore

Ensure your .gitignore excludes environment files:
.env
.env.*
.env.local
.env.production
!.env.example
2

Create .env.example

Provide a template without secrets:
# .env.example
SHOPIFY_API_KEY="your_api_key"
SHOPIFY_API_SECRET="your_api_secret"
SCOPES="customer_read_customers,..."
SHOPIFY_APP_URL="https://your-app.example.com"
DATABASE_URL="postgresql://user:password@host:5432/database"
NODE_ENV="production"
3

Use Secret Management

For production, use secure secret management:
  • Vercel: Environment Variables in project settings
  • Railway: Project Variables
  • Heroku: Config Vars
  • AWS: Systems Manager Parameter Store or Secrets Manager
  • Google Cloud: Secret Manager
  • Azure: Key Vault
Most platforms encrypt secrets at rest and in transit automatically.
4

Rotate Secrets Regularly

Best practices for secret rotation:
  • Rotate API secrets every 90 days
  • Update database passwords periodically
  • Use temporary credentials when possible
  • Revoke compromised credentials immediately
5

Limit Secret Access

Control who can access secrets:
  • Restrict production secret access to essential team members
  • Use role-based access control (RBAC)
  • Audit secret access logs
  • Never share secrets via email or chat

Environment Variable Loading

The app uses dotenv-cli to load environment variables:

Development

# Loads .env.development
pnpm dev
# Equivalent to:
# dotenv -c development pnpm shopify app dev

Testing

# Loads .env.test
pnpm test
# Equivalent to:
# dotenv -c test vitest

Production

# Uses environment variables from hosting provider
NODE_ENV=production pnpm start

Accessing Environment Variables

Server-Side (Remix Loaders/Actions)

// app/routes/example.tsx
export async function loader() {
  const apiKey = process.env.SHOPIFY_API_KEY;
  const dbUrl = process.env.DATABASE_URL;
  
  // Use environment variables
  return json({ success: true });
}

Client-Side (Browser)

Environment variables are NOT automatically available in the browser. Only expose non-sensitive values.
// app/root.tsx
export async function loader() {
  return json({
    ENV: {
      SHOPIFY_API_KEY: process.env.SHOPIFY_API_KEY, // Safe to expose
      // NEVER expose:
      // SHOPIFY_API_SECRET
      // DATABASE_URL
    },
  });
}

// Use in React components
export default function App() {
  const { ENV } = useLoaderData<typeof loader>();
  
  return (
    <AppBridgeProvider apiKey={ENV.SHOPIFY_API_KEY}>
      {/* Your app */}
    </AppBridgeProvider>
  );
}

Configuration Module

The app uses a centralized configuration module in config/index.ts:
import type {Configuration, NodeEnv} from './types';
import {NODE_ENV, SessionStorageConfig} from './types';

const environment: NodeEnv = process.env.NODE_ENV;
const isProduction = environment === NODE_ENV.PRODUCTION;
const isDevelopment = environment === NODE_ENV.DEVELOPMENT;
const isTest = environment === NODE_ENV.TEST;

export const config: Configuration = {
  environment: process.env.NODE_ENV as NodeEnv,
  shopify: {
    sessionStorage: isTest
      ? SessionStorageConfig.Memory
      : SessionStorageConfig.Prisma,
  },
  // ... other configuration
};
Import and use the config:
import {config, env} from '~/config';

if (env.isProduction) {
  // Production-specific logic
}

Validating Environment Variables

Add validation to catch configuration errors early:
// app/utils/validateEnv.server.ts
export function validateEnv() {
  const required = [
    'SHOPIFY_API_KEY',
    'SHOPIFY_API_SECRET',
    'SCOPES',
    'SHOPIFY_APP_URL',
    'DATABASE_URL',
  ];
  
  const missing = required.filter(key => !process.env[key]);
  
  if (missing.length > 0) {
    throw new Error(
      `Missing required environment variables: ${missing.join(', ')}`
    );
  }
}

// Call in app/entry.server.tsx
import {validateEnv} from '~/utils/validateEnv.server';
validateEnv();

Troubleshooting

Variables Not Loading

Issue: Environment variables are undefined Solutions:
  • Check file naming (.env.development, not .env-development)
  • Restart development server after changes
  • Verify dotenv-cli is loading correct environment
  • Check for typos in variable names

Wrong Environment Loaded

Issue: Development variables used in production Solutions:
  • Verify NODE_ENV is set correctly
  • Check hosting provider environment configuration
  • Don’t commit .env.production to repository
  • Use hosting provider’s environment variable manager

Secrets Exposed

Issue: Accidentally committed secrets Actions:
  1. Rotate all exposed secrets immediately
  2. Remove secrets from Git history
  3. Update .gitignore to prevent future commits
  4. Review access logs for unauthorized access

Next Steps

After configuring environment variables:
  1. Deploy to production
  2. Test your configuration with pnpm dev
  3. Verify all required variables are set

Build docs developers (and LLMs) love