Skip to content

Client Onboarding

End-to-end runbook for bringing a new client from zero to live on LayerKick. Designed to be walked through with Claude — steps are marked as human (requires manual action) or AI (Claude handles it).

Gather this information about the client:

FieldExampleNotes
Client nameBare BabeDisplay name
Slugbrbb4-letter abbreviation
Shopify domainbare-babe.myshopify.comThe .myshopify.com domain
Theme ID154775126243Numeric ID from Shopify admin → Themes
Store password(if applicable)For password-protected stores

You’ll also need:

  • Cloudflare API token with KV + D1 + Workers permissions
  • Shopify app credentials (client ID, client secret) — from the LayerKick Shopify app in Partners
  • Access to the WorkOS dashboard for user invites

Step 1 — Insert client into production D1 [AI]

Section titled “Step 1 — Insert client into production D1 [AI]”

Create the client row in the layerkick-config D1 database with status onboarding:

Terminal window
eval "$(mise env)"
npx wrangler d1 execute layerkick-config --remote \
--command="INSERT INTO clients (
slug, name, status, myshopify_domain, admin_store, shopify_origin,
app_client_id, app_client_secret, templates_json, features_json, ab_json
) VALUES (
'<slug>', '<Client Name>', 'onboarding',
'<store>.myshopify.com', '<store>.myshopify.com', 'https://<store>.myshopify.com',
'<shopify_app_client_id>', '<shopify_app_client_secret>',
'[]', '{}', '{}'
)"

Templates start as [] — everything proxies to Shopify until templates are explicitly enabled.

Terminal window
npx wrangler d1 execute layerkick-config --remote \
--command="INSERT INTO domains (hostname, slug) VALUES ('<slug>.layerkick.com', '<slug>')"

The client needs to authorize the LayerKick app on their Shopify store:

  1. Send the client the install URL: https://app.layerkick.com?shop=<store>.myshopify.com
  2. They authorize the app — this saves access_token to D1 automatically
  3. Verify the token was saved:
    Terminal window
    npx wrangler d1 execute layerkick-config --remote \
    --command="SELECT access_token IS NOT NULL as has_token FROM clients WHERE slug = '<slug>'" --json

If doing this yourself (you have Shopify admin access), just click through the OAuth flow directly.


Step 4 — Download the Shopify theme [AI]

Section titled “Step 4 — Download the Shopify theme [AI]”

Pull the live theme’s Liquid files into the transpiler’s theme directory:

Terminal window
eval "$(mise env)"
pnpm pull:theme --slug=<slug> --theme-id=<themeId>

This downloads all .liquid files (sections, snippets, blocks, layout, templates, config) into packages/transpiler/themes/<slug>/<themeId>/liquid/.

Create (or update) the repo-root .mise.local.toml:

[env]
CLIENT_SLUG = "<slug>"
THEME_ID = "<themeId>"
# WT_PORT_OFFSET = "50" # if using a worktree

Then load into the shell:

Terminal window
eval "$(mise env)"

Step 6 — Create client config directory [AI]

Section titled “Step 6 — Create client config directory [AI]”
Terminal window
mkdir -p clients/<slug>

Create clients/<slug>/ssr.json with an empty config to start:

{}

Transpile all Liquid to TS and generate the component registry:

Terminal window
pnpm build:shim

This takes ~2-3 minutes. Generates registry.ts that maps section/snippet names to render functions.


Terminal window
pnpm dev --reset

This starts the renderer (port 8787) + dashboard (port 3000) + liquid watcher, and seeds local D1/KV from scratch.

Step 9 — Seed local KV with Shopify data [AI]

Section titled “Step 9 — Seed local KV with Shopify data [AI]”

Build a fetch manifest by scanning transpiled sections for metafield access patterns, then seed:

Terminal window
pnpm seed:kv --slug=<slug>

If specific metafields are needed:

Terminal window
pnpm seed:kv --slug=<slug> --manifest='{"products":{"metafields":[{"namespace":"<ns>","key":"<key>"}]}}'

Check that pages render at http://localhost:8787:

  • Homepage (/)
  • A collection page (/collections/<handle>)
  • A product page (/products/<handle>)
  • A content page (/pages/<handle>)

At this stage everything proxies to Shopify — confirm the proxy path works before deploying.


Build and deploy the theme worker, promoting it as the production deploy:

Terminal window
eval "$(mise env)"
pnpm deploy:theme <slug> --promote

This creates a worker named <slug>-<adj>-<noun> (e.g., brbb-silver-pine), uploads the bundle + KV data, and sets it as the production pointer in D1.

Deploy the dispatch (edge router) worker and set up the custom domain:

Terminal window
pnpm deploy:dispatch <slug>

This creates <slug>-dispatch, sets up the <slug>.layerkick.com route, and configures service bindings to the theme worker.

Seed production KV with all Shopify data (products, collections, pages, blogs, menus):

Terminal window
pnpm hydrate:kv --slug=<slug>

Register Shopify webhooks so product/collection/theme changes auto-invalidate KV:

Terminal window
pnpm register:webhooks --slug=<slug>
Terminal window
curl -sI "https://<slug>.layerkick.com/" | grep -E "x-rendered-by|HTTP/"

Should show x-rendered-by: layerkick-proxy — meaning all traffic is proxied to Shopify through our edge. The site should look identical to the Shopify store.


Terminal window
npx wrangler d1 execute layerkick-config --remote \
--command="UPDATE clients SET status = 'ready' WHERE slug = '<slug>'"

Step 17 — Invite client to dashboard [Human]

Section titled “Step 17 — Invite client to dashboard [Human]”
  1. Go to the WorkOS Dashboard → Users → Send Invitation
  2. Enter the client’s email address
  3. Once they sign up, get their WorkOS user ID (user_...)
  4. Grant them access to their store:
    Terminal window
    npx wrangler kv put "user-store:<user_id>" '["<slug>"]' \
    --binding LAYERKICK_KV --preview false
  5. They can now log in at https://app.layerkick.com and see their dashboard

Let the client know:

  • Their dashboard is live at https://app.layerkick.com
  • Their preview site is at https://<slug>.layerkick.com
  • All traffic is currently proxied through to Shopify (no rendering changes yet)

Once parity is verified for a template type, enable it for rendering:

Terminal window
# 1. Check current whitelist
npx wrangler d1 execute layerkick-config --remote \
--command="SELECT templates_json FROM clients WHERE slug = '<slug>'" --json \
| jq '.[0].results[0].templates_json | fromjson'
# 2. Add template (use full suffixed name)
npx wrangler d1 execute layerkick-config --remote \
--command="UPDATE clients SET templates_json = '[\"index\"]' WHERE slug = '<slug>'"
# 3. Deploy theme worker (if code changed)
pnpm deploy:theme <slug> --promote
# 4. Re-seed KV for affected content type
pnpm hydrate:kv --slug=<slug> --only=pages
# 5. Purge CDN cache
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/c50da9d954b9ef97d2b53f4db0e21d1c/purge_cache" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-d '{"hosts":["<slug>.layerkick.com"]}' | jq .success
# 6. Verify (wait 60s for D1 cache TTL)
curl -sI "https://<slug>.layerkick.com/" | grep x-rendered-by
# Should show: layerkick (NOT layerkick-proxy)

Use the full template name with suffix — e.g., "product.ge-optimized-color" not "product". Base templates like "page", "index", "article" don’t have suffixes.


PhaseStepsWhoDuration
Registration1-3AI + Human (OAuth)~10 min
Theme & Build4-7AI~5 min
Local Dev8-10AI + Human (verify)~5 min
Deploy11-15AI + Human (verify)~10 min
Client Access16-18AI + Human (invite)~5 min
Template EnablePer templateAI~5 min each