Skip to content

Build your first OpenShop app

In this lesson, we will create an embedded Shopify app and run syncOrders. The flow will read orders from a development store and pass them to a sample warehouse provider. The sample logs the records; it does not send them to a real warehouse.

Have these ready:

  • Node.js 26 and pnpm 11. Check with node --version and pnpm --version.
  • Docker with its daemon running. We will use a PostgreSQL 17 container.
  • Shopify CLI installed and available as shopify.
  • A Shopify development store and an account allowed to create and install an app on it.
  • Basic familiarity with TypeScript and running commands in a terminal.

Use a development store throughout this lesson. An empty order list is fine. If you already run PostgreSQL on port 5432, use a dedicated database there and substitute its connection URL in the database steps below.

Terminal window
docker run --name openshop-postgres \
-e POSTGRES_DB=openshop \
-e POSTGRES_USER=openshop \
-e POSTGRES_PASSWORD=openshop \
-p 5432:5432 \
-d postgres:17-alpine

Wait until this command reports that PostgreSQL is accepting connections:

Terminal window
docker exec openshop-postgres pg_isready -U openshop -d openshop

On later sessions, reuse the container with docker start openshop-postgres. The username and password above are local development values.

Terminal window
pnpm dlx openshop init my-app
cd my-app
pnpm install

The CLI prints Created my-app. Keep this terminal in my-app for the remaining commands. The generated project pins the OpenShop version that created it.

Open these three files:

File What it contains
openshop.app.ts The registered warehouse provider
flows/syncOrders.ts The sample flow with fetch-orders and send-to-warehouse steps
openshop.config.ts The flow registry and a sample cron schedule

There is also a drizzle/ directory containing the initial database migration. The project structure reference describes the other files when you need them.

Create .env in my-app with this content:

DATABASE_URL=postgresql://openshop:openshop@localhost:5432/openshop

Append a new encryption key once:

Terminal window
printf 'ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env

Keep this key for subsequent sessions. The generated .gitignore excludes .env. openshop dev reads it automatically.

Migration commands use the process environment, so export the database URL in this terminal before applying the initial migration:

Terminal window
export DATABASE_URL=postgresql://openshop:openshop@localhost:5432/openshop
pnpm run db:migrate
pnpm run db:status

The status output should report no pending migration. If the database connection fails, check that the container is ready and that the exported URL matches it.

Replace openshop.config.ts with:

import { app } from '#app'
import { syncOrders } from '#flows/syncOrders'
export default app.defineConfig({
flows: { syncOrders },
crons: [],
})

We will run the flow from the admin UI after saving its provider credentials. This removes the generated schedule for now. The flow guide adds a schedule that explicitly targets installed shops.

Terminal window
shopify auth login
shopify app config link

Follow the prompts to select your organization and create or select a development app. Check that the linked Shopify app configuration requests read_products and read_orders, as the template does. Then start development:

Terminal window
pnpm run shopify

Follow Shopify CLI’s prompts to select your development store. This command starts the tunnel and OpenShop together. OpenShop generates GraphQL types, synchronizes the local database schema, and starts the API, admin UI, scheduler, and worker. Leave it running.

Open the preview URL printed by Shopify CLI (or press p) and install the app. You should see the OpenShop home page inside Shopify admin. Opening http://localhost:3000 directly does not provide the Shopify session the embedded UI needs.

In Providers, open warehouse and save:

Field Development value
API URL https://example.com
API Key development-only

Run the provider check. It should succeed: the generated checker only verifies that both values are present. The generated push method only logs a message; these placeholder credentials never contact example.com.

In Flows, open syncOrders, trigger a run, and enter:

{ "limit": 10 }

Open the run. It should reach completed and show two completed steps: fetch-orders and send-to-warehouse. Its logs should include Orders synced. The count may be zero on a new development store.

If it fails, open the failed step and its logs. A Shopify authorization error usually means the app installation or read_orders permission needs attention; see troubleshooting. A run that stays pending needs a running worker; in this lesson, pnpm run shopify starts it for you.

In a second terminal, from my-app, check the project:

Terminal window
pnpm run lint

This generates GraphQL types, checks TypeScript, and runs ESLint.

You now have an installed app, saved provider configuration, and a completed background job with inspectable steps. Continue with Build a checkpointed flow to write a small flow of your own and watch it resume after a pause.

When you finish the session, stop Shopify CLI with Ctrl+C. You can stop the local database with docker stop openshop-postgres and start the same container next time without recreating it.