Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,42 @@ jobs:
uses: shopify/lighthouse-ci-action@v1
with:
store: ${{ secrets.SHOP_STORE }}
access_token: ${{ secrets.SHOP_ACCESS_TOKEN }}
client_id: ${{ secrets.SHOP_CLIENT_ID }}
client_secret: ${{ secrets.SHOP_CLIENT_SECRET }}
lhci_github_app_token: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
lhci_min_score_performance: 0.9
lhci_min_score_accessibility: 0.9
```

## Authentication

Authentication is done with [Custom App access tokens](https://shopify.dev/apps/auth/admin-app-access-tokens).
### Dev Dashboard App (recommended — required for apps created after Jan 2026)

1. [Create an app via the Shopify Dev Dashboard](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard).
2. When creating the app version, configure these required access scopes:
- `read_products`
- `write_themes`
3. Install the app on your store.
4. Copy the `client_id` and `client_secret` from the app credentials.
5. Add the following to your repository's [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository):
- `SHOP_CLIENT_ID`: the client ID
- `SHOP_CLIENT_SECRET`: the client secret
- `SHOP_STORE`: Shopify store `<store>.myshopify.com` URL

Tokens are fetched automatically at the start of each action run and are valid for 24 hours — well beyond the duration of a typical run.

```yml
- uses: shopify/lighthouse-ci-action@v1
with:
store: ${{ secrets.SHOP_STORE }}
client_id: ${{ secrets.SHOP_CLIENT_ID }}
client_secret: ${{ secrets.SHOP_CLIENT_SECRET }}
```

### Legacy Custom App (for apps created before Jan 2026)

> [!IMPORTANT]
> As of January 1, 2026 Shopify no longer allows creating new custom apps. Existing custom apps continue to work with this method.

1. [Create the app](https://help.shopify.com/en/manual/apps/custom-apps#create-and-install-a-custom-app).
2. Click the `Configure Admin API Scopes` button.
Expand All @@ -46,19 +73,33 @@ Authentication is done with [Custom App access tokens](https://shopify.dev/apps/
- `SHOP_ACCESS_TOKEN`: the Admin API access token
- `SHOP_STORE`: Shopify store `<store>.myshopify.com` URL

```yml
- uses: shopify/lighthouse-ci-action@v1
with:
store: ${{ secrets.SHOP_STORE }}
access_token: ${{ secrets.SHOP_ACCESS_TOKEN }}
```

## Configuration

The `shopify/lighthouse-ci-action` accepts the following arguments:

* `access_token` - (required) see [Authentication](#authentication)
**Authentication (one method required):**
* `client_id` - Client ID for a Dev Dashboard app (use with `client_secret`)
* `client_secret` - Client secret for a Dev Dashboard app (use with `client_id`)
* `access_token` - Legacy custom app access token (for apps created before Jan 2026)

**Store:**
* `store` - (required) Shopify store Admin URL, e.g. `my-store.myshopify.com`.
* `password` - (optional) For password protected shops
* `product_handle` - (optional) Product handle to run the product page Lighthouse run on. Defaults to the first product.
* `theme_root` - (optional) The root folder for the theme files that will be uploaded. Defaults to `.`
* `collection_handle` - (optional) Collection handle to run the product page Lighthouse run on. Defaults to the first collection.
* `pull_theme` - (optional) The ID or name of a theme from which the settings and JSON templates should be used. If not provided Lighthouse will be run against the theme's default settings.
* `lhci_min_score_performance` - (optional, default: 0.6) Minimum performance score for a passed audit (must be between 0 and 1).
* `lhci_min_score_accessibility` - (optional, default: 0.9) Minimum accessibility score for a passed audit

**Optional:**
* `password` - For password protected shops
* `product_handle` - Product handle to run the product page Lighthouse run on. Defaults to the first product.
* `theme_root` - The root folder for the theme files that will be uploaded. Defaults to `.`
* `collection_handle` - Collection handle to run the collection page Lighthouse run on. Defaults to the first collection.
* `pull_theme` - The ID or name of a theme from which the settings and JSON templates should be used. If not provided Lighthouse will be run against the theme's default settings.
* `lhci_min_score_performance` - (default: 0.6) Minimum performance score for a passed audit (must be between 0 and 1).
* `lhci_min_score_accessibility` - (default: 0.9) Minimum accessibility score for a passed audit

For the GitHub Status Checks on PR. One of the two arguments is required:

Expand Down
10 changes: 8 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ branding:
description: 'Run Lighthouse CI on Shopify themes directly from GitHub'
inputs:
access_token:
description: 'Custom app access token'
description: 'Legacy custom app access token (for apps created before Jan 2026)'
# required: false because you can still authenticate with private
# app credentials
# app credentials, or with client_id/client_secret
required: false
client_id:
description: 'Client ID for Dev Dashboard app (use with client_secret instead of access_token)'
required: false
client_secret:
description: 'Client secret for Dev Dashboard app (use with client_id instead of access_token)'
required: false
store:
description: '<domain>.myshopify.com URL'
Expand Down
62 changes: 56 additions & 6 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
[[ -n "$INPUT_PULL_THEME" ]] && export SHOP_PULL_THEME="$INPUT_PULL_THEME"

# Authentication creds
export SHOP_ACCESS_TOKEN="$INPUT_ACCESS_TOKEN"
[[ -n "$INPUT_CLIENT_ID" ]] && export SHOP_CLIENT_ID="$INPUT_CLIENT_ID"
[[ -n "$INPUT_CLIENT_SECRET" ]] && export SHOP_CLIENT_SECRET="$INPUT_CLIENT_SECRET"

# Authentication creds (deprecated)
[[ -n "$INPUT_APP_ID" ]] && export SHOP_APP_ID="$INPUT_APP_ID"
Expand Down Expand Up @@ -74,9 +75,8 @@ api_request() {
1> "$out" \
2> "$err"
fi
set -e

local exit_code="$?"
set -e
local errors="$(cat "$out" | jq '.errors')"

if [[ $exit_code != '0' ]]; then
Expand All @@ -93,6 +93,42 @@ api_request() {
cat "$out"
}

fetch_access_token() {
local token_response_file
token_response_file="$(mktemp)"
local token_error_file
token_error_file="$(mktemp)"

# Redirect stderr to prevent client_secret from appearing in logs on failure.
# set +e mirrors the pattern in api_request — prevents set -e from killing
# the script before our custom error message can run.
set +e
curl -sS -f -X POST \
"https://${SHOPIFY_SHOP}/admin/oauth/access_token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${SHOP_CLIENT_ID}" \
--data-urlencode "client_secret=${SHOP_CLIENT_SECRET}" \
1> "$token_response_file" \
2> "$token_error_file"
local exit_code="$?"
set -e

if [[ $exit_code != '0' ]]; then
log "Failed to fetch access token. Verify your client_id and client_secret are correct and that your Dev Dashboard app has the required scopes: read_products, write_themes."
return 1
fi

local token
token="$(jq -r '.access_token' "$token_response_file")"
if [[ -z "$token" || "$token" == "null" ]]; then
log "Failed to fetch access token: response did not contain a valid token. Verify your Dev Dashboard app has the required scopes: read_products, write_themes."
return 1
fi

echo "$token"
}

cleanup() {
if [[ -n "${theme+x}" ]]; then
step "Disposing development theme"
Expand Down Expand Up @@ -125,12 +161,26 @@ YAML
export CI=1
export SHOPIFY_SHOP="${SHOP_STORE#*(https://|http://)}"

if [[ -n "$SHOP_ACCESS_TOKEN" ]]; then
export SHOPIFY_PASSWORD="$SHOP_ACCESS_TOKEN"
# Resolve access token — prefer client_id/client_secret (Dev Dashboard apps),
# fall back to static access_token (legacy custom apps)
if [[ -n "${SHOP_CLIENT_ID:-}" && -n "${SHOP_CLIENT_SECRET:-}" ]]; then
if [[ -n "${INPUT_ACCESS_TOKEN:-}" ]]; then
log "WARNING: Both access_token and client_id/client_secret were provided. Using client_id/client_secret."
fi
SHOP_ACCESS_TOKEN="$(fetch_access_token)"
export SHOP_ACCESS_TOKEN
elif [[ -n "${SHOP_CLIENT_ID:-}" || -n "${SHOP_CLIENT_SECRET:-}" ]]; then
log "Error: Both client_id and client_secret are required for Dev Dashboard app authentication."
exit 1
elif [[ -n "${INPUT_ACCESS_TOKEN:-}" ]]; then
export SHOP_ACCESS_TOKEN="$INPUT_ACCESS_TOKEN"
else
export SHOPIFY_PASSWORD="$SHOP_APP_PASSWORD"
log "Error: Authentication required. Provide either (client_id + client_secret) for a Dev Dashboard app, or access_token for a legacy custom app."
exit 1
fi

export SHOPIFY_PASSWORD="$SHOP_ACCESS_TOKEN"

export SHOPIFY_FLAG_STORE="$SHOPIFY_SHOP"
export SHOPIFY_CLI_THEME_TOKEN="$SHOPIFY_PASSWORD"
export SHOPIFY_CLI_TTY=0
Expand Down
Loading