← Files ClerkARCHIVED FILE
skills/clerk-cli/references/recipes.md
11.6 KB · Oct 4, 2026 · 12:23 UTC
# Clerk CLI - Recipes
Copy-pasteable patterns for common tasks. Treat these as starting points; confirm exact paths and parameters with `clerk api ls <keyword>` and `clerk <command> --help`, since the Clerk API evolves.
## Discovery first
```sh
clerk api ls # everything Backend API exposes
clerk api ls users # filter by keyword
clerk api ls --platform # Platform API (account-level)
```
The bundled catalog is cached locally for 1 hour. There is no force-refresh flag - once the TTL expires the next `clerk api ls` re-fetches automatically; on fetch failure the CLI falls back to the stale cache and prints a warning.
## Users
```sh
# List users (preferred; curated flags). --limit defaults to 100 (max 250).
# JSON output is `{ data: [...], hasMore }` so callers can paginate without /users/count.
clerk users list
clerk users list --limit 50 --offset 0 --order-by -created_at
# Count users (no curated subcommand; use the raw API)
clerk api /users/count
# Fetch a user (no curated subcommand; use the raw API)
clerk api /users/user_abc123
# Search by email
clerk users list --email-address alice@example.com
# Open a user's profile in the dashboard
clerk users open user_abc123
clerk users open user_abc123 --print # print the URL instead of opening
# Create a user (preferred; curated flags)
clerk users create \
--email alice@example.com \
--password 'SuperSecret123!' \
--first-name Alice \
--last-name Doe \
--yes
# Equivalent raw BAPI call. Use only when curated flags don't cover a field.
clerk api /users -d '{
"email_address": ["alice@example.com"],
"password": "SuperSecret123!",
"first_name": "Alice",
"last_name": "Doe"
}'
# Update (PATCH merges)
clerk api /users/user_abc123 -X PATCH -d '{"first_name":"Alicia"}'
# Ban / unban
clerk api /users/user_abc123/ban -X POST
clerk api /users/user_abc123/unban -X POST
# Lock / unlock
clerk api /users/user_abc123/lock -X POST
clerk api /users/user_abc123/unlock -X POST
# Delete (PREVIEW FIRST)
clerk api /users/user_abc123 -X DELETE --dry-run
clerk api /users/user_abc123 -X DELETE --yes
```
### Test users (development only)
For test accounts you need to sign into without real email or SMS delivery, Clerk provides two magic patterns that both verify with the fixed OTP `424242`. Use them on development instances; production rejects them.
**By email.** Any address with the `+clerk_test` subaddress is recognized as a test email. The domain portion is arbitrary.
```sh
# Create a test user with a test email (dev instance)
# `skip_password_checks` isn't a curated flag, so pass the body via `-d`.
clerk users create -d '{
"email_address": ["demo+clerk_test@example.com"],
"password": "TestPass123!",
"skip_password_checks": true
}' --yes
```
**By phone.** Any US fictional phone number in the `+1 (XXX) 555-0100` through `+1 (XXX) 555-0199` range is recognized as a test phone. Pass the E.164 form.
```sh
# Create a test user with a test phone (dev instance)
clerk users create -d '{
"phone_number": ["+12015550100"],
"password": "TestPass123!",
"skip_password_checks": true
}' --yes
```
When signing in as either user in a browser or Playwright, enter `424242` at the OTP prompt.
These patterns only apply to development instances. In production, Device Trust blocks sign-in regardless of suffix or number, and using real-looking test addresses is highly discouraged. Test addresses and numbers do not count against the dev-instance monthly caps (20 SMS, 100 emails). See [Clerk's test emails and phones reference](https://clerk.com/docs/guides/development/testing/test-emails-and-phones) for the full contract.
## Organizations
```sh
# List
clerk api /organizations
clerk api '/organizations?limit=20&query=acme'
# Fetch
clerk api /organizations/org_abc123
# Create
clerk api /organizations -d '{"name":"Acme","created_by":"user_abc123"}'
# Update
clerk api /organizations/org_abc123 -X PATCH -d '{"name":"Acme Inc."}'
# Members
clerk api /organizations/org_abc123/memberships
clerk api /organizations/org_abc123/memberships -d '{"user_id":"user_xyz","role":"org:member"}'
clerk api /organizations/org_abc123/memberships/user_xyz -X PATCH -d '{"role":"org:admin"}'
clerk api /organizations/org_abc123/memberships/user_xyz -X DELETE --dry-run
# Invitations
clerk api /organizations/org_abc123/invitations -d '{"email_address":"new@acme.com","role":"org:member"}'
```
If organization endpoints return `organization_not_enabled_in_instance`, enable the feature first with the dedicated toggle:
```sh
# Inspect org settings
clerk api /instance/organization_settings
# Preview, then enable organizations for this instance
clerk enable orgs --dry-run
clerk enable orgs --yes
```
For org settings the toggle flags don't cover, fall back to `clerk config patch --json '{"organization_settings":{...}}'`. Deeper org workflows (roles, memberships, components) live in the `clerk-orgs` skill.
## Sessions
```sh
# List active sessions for a user
clerk api '/sessions?user_id=user_abc123&status=active'
# Revoke a session
clerk api /sessions/sess_abc123/revoke -X POST
```
## Impersonation (sign in as a user)
Impersonation goes through `clerk impersonate` (alias `imp`): it creates an actor token stamped `cli:<your-email>` so every impersonation session is traceable. Requires `clerk auth login`.
```sh
# Print the sign-in URL for a user (agent-safe: no browser, no prompt)
clerk imp user_abc123 --print
# Resolve by exact email instead of user ID
clerk imp alice@example.com --print
# Short-lived token, no confirmation prompt
clerk imp user_abc123 --yes --expires-in 900
# Revoke a pending actor token (the id is printed at creation - capture it then)
clerk imp revoke act_abc123
```
To mint a one-time **sign-in token** instead - for building custom token sign-in flows, signing in *as* the user with no actor audit trail - use the raw API:
```sh
clerk api /sign_in_tokens -d '{"user_id":"user_abc123"}'
```
## Invitations (top-level, not org-scoped)
```sh
clerk api /invitations
clerk api /invitations -d '{"email_address":"new@example.com","redirect_url":"https://example.com/welcome"}'
clerk api /invitations/inv_abc123/revoke -X POST
```
## JWT templates
```sh
clerk api /jwt_templates
clerk api /jwt_templates/jtmp_abc123
clerk api /jwt_templates -d '{
"name": "supabase",
"claims": {"aud": "authenticated", "role": "authenticated"},
"lifetime": 60
}'
```
## Webhooks (local testing)
`listen` talks only to the Svix relay and `verify` is pure local HMAC - neither needs auth or a linked project.
```sh
# 1. Mint a token and open a pinned tunnel that forwards deliveries to your handler.
# The command prints a relay inbox URL (https://webhooks.clerk.com/in/c_.../).
clerk webhooks listen --token "$(clerk webhooks token)" --forward-to http://localhost:3000/api/webhooks
# 2. Add that relay URL as a webhook endpoint in the Clerk Dashboard.
# Real events now stream to your terminal and forward to your local handler.
# svix-* headers are preserved, so verifyWebhook() in your handler still
# verifies against that endpoint's signing secret.
# 3. Capture events for replay/verification (agent mode emits NDJSON automatically)
clerk webhooks listen --forward-to http://localhost:3000/api/webhooks --json > events.ndjson
# 4. Verify a saved delivery offline against the endpoint's signing secret
clerk webhooks verify --secret whsec_... --delivery @event.json
```
Pin the token (`--token`) whenever you want the inbox URL to survive across machines and restarts - otherwise the relay URL can change and the Dashboard endpoint needs re-pointing.
## Instance configuration
Prefer the dedicated `config` commands over raw `api` calls - they handle confirmation, dry-run, and formatting.
```sh
# Pull the current dev config
clerk config pull
clerk config pull --output config.dev.json
# Pull production
clerk config pull --instance prod --output config.prod.json
# Look at the schema to know what's available
clerk config schema --keys session sign_in social
# PATCH: surgical updates
clerk config patch --json '{"session":{"lifetime":3600}}' --dry-run
clerk config patch --json '{"session":{"lifetime":3600}}' --yes
# PUT: replace everything (destructive - always --dry-run first)
clerk config put --file config.prod.json --dry-run
clerk config put --file config.prod.json --instance prod --yes
```
## Environment variables
```sh
# Pull dev keys into .env.local (auto-detects framework and key names)
clerk env pull
# Pull production keys
clerk env pull --instance prod
# Target a specific file
clerk env pull --file .env
```
`env pull` merges into the existing file: existing Clerk keys are updated in place; new ones are appended under a `# Clerk` header; everything else is preserved.
## Applications (Platform API)
```sh
# List your apps
clerk apps list
clerk apps list --json
# Fetch one (raw API)
clerk api /v1/platform/applications/app_abc123 --platform
```
## Scripting patterns
### Save large responses to a file before reading them
`users list`, `apps list`, `config pull`, and most `clerk api` GETs can return responses ranging from kilobytes to megabytes. Reading the full payload into an LLM-driven session burns context for no benefit. Persist the response, then query just the slice you need:
```sh
# Persist once, query as many times as you need.
clerk users list --json --limit 250 > /tmp/users.json
jq '.data | length' /tmp/users.json # count rows on the page
jq '.hasMore' /tmp/users.json # any more pages?
jq '.data[0] | keys' /tmp/users.json # learn the shape of one record
jq '.data[] | {id, email_addresses}' /tmp/users.json # project to relevant fields only
```
If `jq` is not on `PATH`, fall back to Python or Node, which most environments have:
```sh
python3 -c 'import json; d=json.load(open("/tmp/users.json")); print(len(d["data"]), d["hasMore"])'
node -e 'const d=require("/tmp/users.json"); console.log(d.data.length, d.hasMore)'
```
Only `cat`/`head` the file when you genuinely need the raw structure for one-off debugging.
### Pipe to `jq`
For small responses (or one-shot lookups), inline piping to `jq` is fine:
```sh
# Get a list of user IDs from the current page (the page envelope is `{ data, hasMore }`)
clerk users list --json | jq -r '.data[] | .id'
# Count banned users on the current page
clerk users list --json | jq '[.data[] | select(.banned)] | length'
# Walk every page until hasMore is false. Save each page to its own file so you
# can inspect them independently without re-fetching.
offset=0
while :; do
page="/tmp/users-${offset}.json"
clerk users list --json --limit 250 --offset "$offset" > "$page"
jq -r '.data[] | .id' "$page"
[ "$(jq -r '.hasMore' "$page")" = "true" ] || break
offset=$((offset + 250))
done
```
### Read body from stdin
```sh
echo '{"first_name":"Bob"}' | clerk api /users/user_abc123 -X PATCH
jq -n '{email_address:["c@d.co"]}' | clerk api /users
```
### Loop safely
```sh
# Always --dry-run first across the whole set. `users list` paginates;
# bump --limit (max 250) and walk pages with --offset until .hasMore is false.
for id in $(clerk users list --json --limit 250 | jq -r '.data[] | .id'); do
clerk api /users/$id -X PATCH -d '{"public_metadata":{"migrated":true}}' --dry-run
done
# Re-run without --dry-run once the previews look right
```
### Target multiple instances
```sh
# Copy config from dev to staging for review
clerk config pull --instance dev --output /tmp/dev-config.json
clerk config patch --instance ins_staging --file /tmp/dev-config.json --dry-run
```
## When in doubt
```sh
clerk api ls <keyword> # find the right endpoint
clerk <command> --help # authoritative flag list
clerk doctor --json # health check
```
SHA-256: f7d7709a004a53f84082423c49ffe2e87466f0befd72a2eae003d0a6d65eef87