netlify-cli
GitHub Actions workflows that deploy to Netlify with netlify-cli, or running 'netlify dev' / 'netlify functions:serve' locally. Covers monorepo 'Projects detected' errors, pnpm workspace deploys, deploying pre-built directories, capturing deploy URLs, GitHub secrets setup, netlify.toml inheritance with branch deploys, isolating sub-site deploys, netlify dev with --filter, pnpm 10.x trust store errors, CLI crash workarounds. Keywords: netlify deploy, github actions netlify, netlify-cli, monorepo deploy, pnpm workspace netlify, netlify dev, netlify functions serve.
Best use case
netlify-cli is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
GitHub Actions workflows that deploy to Netlify with netlify-cli, or running 'netlify dev' / 'netlify functions:serve' locally. Covers monorepo 'Projects detected' errors, pnpm workspace deploys, deploying pre-built directories, capturing deploy URLs, GitHub secrets setup, netlify.toml inheritance with branch deploys, isolating sub-site deploys, netlify dev with --filter, pnpm 10.x trust store errors, CLI crash workarounds. Keywords: netlify deploy, github actions netlify, netlify-cli, monorepo deploy, pnpm workspace netlify, netlify dev, netlify functions serve.
Teams using netlify-cli should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/netlify-cli/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How netlify-cli Compares
| Feature / Agent | netlify-cli | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
GitHub Actions workflows that deploy to Netlify with netlify-cli, or running 'netlify dev' / 'netlify functions:serve' locally. Covers monorepo 'Projects detected' errors, pnpm workspace deploys, deploying pre-built directories, capturing deploy URLs, GitHub secrets setup, netlify.toml inheritance with branch deploys, isolating sub-site deploys, netlify dev with --filter, pnpm 10.x trust store errors, CLI crash workarounds. Keywords: netlify deploy, github actions netlify, netlify-cli, monorepo deploy, pnpm workspace netlify, netlify dev, netlify functions serve.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Netlify CLI for GitHub Actions
Use this skill when writing or debugging GitHub Actions workflows that deploy to Netlify using `netlify-cli`. This skill contains critical knowledge about common pitfalls and solutions.
## Key Flags Reference
```bash
netlify deploy \
--dir=<path> # Directory to deploy (required)
--site=<site-id> # Netlify site ID
--auth=<token> # Auth token (or use NETLIFY_AUTH_TOKEN env var)
--prod # Deploy to production (default: draft)
--build # Opt-in to running build before deploy (deploy does NOT build by default)
--filter=<package> # Select package in monorepo - CRITICAL for pnpm workspaces
--functions=<folder> # Override functions directory (useful to skip functions with empty dir)
--alias=<name> # Custom subdomain for draft deploys
--message=<msg> # Deployment message
--json # Output as JSON (for programmatic URL extraction)
```
**IMPORTANT**: There is NO `--no-build` flag in netlify-cli@19+. `netlify deploy` does NOT build by default. Use `--build` only if you want to opt-in to building.
## Critical: Monorepo "Projects Detected" Error
When deploying from a **pnpm workspace** or **monorepo**, you will encounter:
```
Error: Projects detected: package-a, package-b. Configure the project you want to work with and try again.
```
### Solution
Use the `--filter` flag to select the target package:
```bash
netlify deploy \
--dir=./doc/build \
--site=$NETLIFY_SITE_ID \
--auth=$NETLIFY_AUTH_TOKEN \
--filter=<package-name> \
--prod
```
- `--filter=<package-name>`: Selects the package (use the `name` from package.json)
- `netlify deploy` does NOT build by default, so no extra flag is needed to skip building
## Critical: netlify.toml Inheritance Problem
**The CLI always reads `netlify.toml` from the project root**, regardless of the `--dir` flag. This means:
- **Redirects** defined in the root `netlify.toml` apply to ALL deploys (including branch/alias deploys)
- **Functions** from the root config are bundled into every deploy
- **Circular proxy**: If root `netlify.toml` has a redirect like `/doc/* -> https://doc--mysite.netlify.app/doc/:splat` with `force = true`, the branch deploy will proxy to itself, causing 404s
### Solution: Deploy from an Isolated Directory
To prevent the root `netlify.toml` from affecting a sub-site deploy, deploy from a separate directory with its own minimal `netlify.toml`:
```yaml
- name: Prepare deploy directory
run: |
mkdir -p deploy-output/doc
cp -r doc/build/* deploy-output/doc/
mkdir -p deploy-output/.empty-functions
cat > deploy-output/netlify.toml << 'TOML'
[build]
publish = "."
TOML
- name: Deploy to Netlify
working-directory: deploy-output
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
netlify deploy \
--dir=. \
--functions=.empty-functions \
--site=$NETLIFY_SITE_ID \
--auth=$NETLIFY_AUTH_TOKEN \
--alias=doc \
--message="Deploy: ${{ github.sha }}"
```
Key points:
- `working-directory: deploy-output` makes the CLI find the local `netlify.toml` instead of the root one
- `--functions=.empty-functions` overrides the root's functions directory to avoid bundling errors
- The local `netlify.toml` has no redirects, so no circular proxy issues
- `--filter` is not needed when deploying from an isolated directory (no monorepo detection)
## Critical: basePath / baseUrl Wrapping for Proxied Deploys
When a sub-site is proxied from the main site under a path prefix (e.g., `mysite.com/doc/` -> `doc--mysite.netlify.app/doc/`), the build output must be nested in a matching subdirectory.
**Problem**: `netlify deploy --dir=doc/build` deploys files at root `/`. But if the framework (e.g., Docusaurus with `baseUrl: '/doc/'`) generates asset references like `/doc/assets/main.js`, they will 404 because the actual file is at `/assets/main.js`.
**Solution**: Wrap the build output in the correct subdirectory before deploying:
```yaml
- name: Prepare deploy directory
run: |
mkdir -p deploy-output/doc
cp -r doc/build/* deploy-output/doc/
- name: Deploy
run: netlify deploy --dir=deploy-output ...
```
Now `doc--mysite.netlify.app/doc/assets/main.js` resolves correctly.
## Standard Single-Project Deploy
For non-monorepo projects:
```yaml
- name: Install Netlify CLI
run: npm install -g netlify-cli
- name: Deploy to Netlify
run: |
netlify deploy \
--dir=./out \
--site=${{ secrets.NETLIFY_SITE_ID }} \
--auth=${{ secrets.NETLIFY_AUTH_TOKEN }} \
--prod \
--message="Deploy from GitHub Actions - ${{ github.sha }}"
```
## Capturing Deploy URL
### Without --json (simpler, recommended)
```yaml
- name: Deploy to Netlify
id: deploy
run: |
OUTPUT=$(netlify deploy \
--dir=./build \
--site=${{ secrets.NETLIFY_SITE_ID }} \
--auth=${{ secrets.NETLIFY_AUTH_TOKEN }} \
--prod \
--message="Deploy: ${{ github.sha }}" 2>&1)
echo "$OUTPUT"
DEPLOY_URL=$(echo "$OUTPUT" | grep -o 'https://[^ ]*netlify.app' | head -1)
echo "deploy-url=$DEPLOY_URL" >> $GITHUB_OUTPUT
```
### With --json (for more data)
```yaml
- name: Deploy to Netlify
id: deploy
run: |
OUTPUT=$(netlify deploy \
--dir=./build \
--site=${{ secrets.NETLIFY_SITE_ID }} \
--auth=${{ secrets.NETLIFY_AUTH_TOKEN }} \
--prod \
--json)
DEPLOY_URL=$(echo "$OUTPUT" | jq -r '.deploy_url')
echo "deploy-url=$DEPLOY_URL" >> $GITHUB_OUTPUT
```
## Complete GitHub Action Example (Monorepo Sub-Site)
Deploys a Docusaurus sub-site from a monorepo as a branch deploy, isolated from the main site's config:
```yaml
name: Deploy Documentation
on:
push:
branches: [doc]
concurrency:
group: doc-deploy
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install documentation dependencies
working-directory: doc
run: pnpm install --frozen-lockfile
- name: Build Docusaurus documentation
working-directory: doc
run: pnpm run build
- name: Install Netlify CLI
run: npm install -g netlify-cli@19
- name: Prepare deploy directory
run: |
mkdir -p deploy-output/doc
cp -r doc/build/* deploy-output/doc/
mkdir -p deploy-output/.empty-functions
cat > deploy-output/netlify.toml << 'TOML'
[build]
publish = "."
TOML
- name: Deploy to Netlify (branch deploy)
working-directory: deploy-output
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
netlify deploy \
--dir=. \
--functions=.empty-functions \
--site=$NETLIFY_SITE_ID \
--auth=$NETLIFY_AUTH_TOKEN \
--alias=doc \
--message="Documentation deploy: ${{ github.sha }}"
```
## Common Errors and Solutions
### Error: "Projects detected"
- **Cause**: Monorepo with multiple package.json files
- **Solution**: Add `--filter=<package-name>` flag to `netlify deploy`
### Error: "Projects detected" with `netlify link`
- **Cause**: `netlify link` does NOT support `--filter` flag and fails in monorepos
- **Solution**: **Don't use `netlify link`** in monorepos. It's unnecessary when using `--site` flag with `netlify deploy`. The `--site` flag directly specifies the target site, making explicit linking redundant.
### Error: "unknown option '--no-build'"
- **Cause**: `--no-build` does not exist in netlify-cli@19+
- **Solution**: Remove the flag. `netlify deploy` does NOT build by default. Use `--build` only when you want to opt-in to building.
### Error: "Could not resolve @netlify/blobs" (or other function bundling errors)
- **Cause**: The CLI picks up functions from the root `netlify.toml` config, even when deploying a sub-site that doesn't need functions
- **Solution**: Use `--functions=<empty-dir>` to override. Create an empty directory and point to it:
```bash
mkdir -p .empty-functions
netlify deploy --functions=.empty-functions ...
```
### Error: Circular redirect / 404 on branch deploy
- **Cause**: Root `netlify.toml` has a redirect like `/doc/* -> https://doc--mysite.netlify.app/doc/:splat` with `force = true`. On the branch deploy itself, this redirect proxies to itself.
- **Solution**: Deploy from an isolated directory with its own `netlify.toml` that has no redirects. Use `working-directory` in the GitHub Actions step so the CLI finds the local config instead of the root one.
### Error: "Build directory not found"
- **Cause**: Wrong `--dir` path or build artifacts not downloaded
- **Solution**: Verify path and ensure artifacts are downloaded in CI
### Error: "Invalid character in header"
- **Cause**: Whitespace in auth token
- **Solution**: Trim the token: `export NETLIFY_AUTH_TOKEN=$(echo "$TOKEN" | tr -d '[:space:]')`
## Setup Requirements
### GitHub Secrets Needed
1. `NETLIFY_SITE_ID` - From Site settings -> General -> Site details -> API ID
2. `NETLIFY_AUTH_TOKEN` - From User settings -> Applications -> Personal access tokens
### Disable Netlify Auto-Build
When using GitHub Actions for deployment, disable Netlify's built-in CI:
1. Go to Site Settings -> Build & deploy
2. Set Build command to empty or `echo "Disabled"`
3. Or create `netlify.toml`:
```toml
[build]
command = "echo 'Build disabled - deploying from GitHub Actions'"
publish = "out"
```
## Using nwtgck/actions-netlify Instead
For simpler cases without monorepo issues:
```yaml
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v3.0
with:
publish-dir: ./out
production-deploy: true
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: 'Deploy - ${{ github.sha }}'
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 5
```
Note: This action may not handle monorepo scenarios well - use CLI directly for those cases.
## Local Development with `netlify dev`
### Basic Usage (Monorepo)
In a monorepo, `netlify dev` requires `--filter` to select the project. Without it, the CLI enters an interactive project selection prompt that blocks non-interactive environments:
```bash
# This works - selects the correct package
netlify dev --functions=netlify/functions --offline --filter my-package
# This gets stuck at interactive prompt
netlify dev --functions=netlify/functions --offline
```
A typical `package.json` script:
```json
{
"netlify:dev": "PNPM_DISABLE_TRUST_STORE=true pnpm --package=netlify-cli dlx netlify dev --functions=netlify/functions --offline --filter my-package"
}
```
### What `netlify dev` Does
- Starts your framework's dev server (e.g., Next.js on port 34434)
- Starts a Netlify Functions server
- Runs a proxy on **port 8888** that routes:
- Page requests → framework dev server
- `/.netlify/functions/*` → functions server
- Applies `netlify.toml` redirect rules (e.g., `/api/products` → `/.netlify/functions/get-products`)
- Injects `.env` variables into the environment
### pnpm 10.x Trust Store Error with `dlx`
On pnpm 10.x, `pnpm dlx netlify-cli` may fail with:
```
ERR_PNPM_TRUST_DOWNGRADE High-risk trust downgrade for "@netlify/edge-bundler@14.9.5"
```
The `PNPM_DISABLE_TRUST_STORE=true` env var may **not** work with `pnpm dlx` even though `pnpm config list` shows `trust-store=false`. This is a pnpm 10.x behavior where `dlx` creates its own install context.
**Workarounds**:
1. **Install netlify-cli globally** instead of using `pnpm dlx`:
```bash
npm install -g netlify-cli
netlify dev --functions=netlify/functions --offline --filter my-package
```
2. **Use npx** (if the global install exists):
```bash
npx netlify-cli dev --functions=netlify/functions --offline --filter my-package
```
### `netlify dev` Crash Workaround
`netlify dev` may crash with "Netlify CLI has terminated unexpectedly" after the proxy starts (observed in v23.14.0+). The proxy on port 8888 starts successfully but terminates immediately.
**Workaround: Run functions separately**
Use `netlify functions:serve` to run just the functions server on **port 9999**, then run your framework dev server separately:
```bash
# Terminal 1: Start functions server (port 9999)
netlify functions:serve --functions=netlify/functions --offline --filter my-package
# Terminal 2: Start Next.js dev server (port 34434)
pnpm dev
```
**Limitation**: Without the `netlify dev` proxy, API rewrites from `netlify.toml` (e.g., `/api/products` → `/.netlify/functions/get-products`) won't work. The functions are accessible directly at:
```
http://localhost:9999/.netlify/functions/<function-name>
```
For frameworks with `output: 'export'` (static site generation), Next.js `rewrites()` cannot be used. In this case, test the functions directly at the port 9999 URL.
### `netlify functions:serve` vs `netlify dev`
| Feature | `netlify dev` | `netlify functions:serve` |
|---|---|---|
| Port | 8888 (proxy) | 9999 |
| Framework dev server | Auto-started | Must start separately |
| API rewrites (`netlify.toml`) | Applied | Not applied |
| `.env` injection | Yes | Yes |
| Stability | May crash (v23+) | Stable |
| `--filter` flag | Supported | Supported |Related Skills
zudoesa-articlify
Convert conversation context into an esa article via the zudoesa-writer subagent. ONLY invoke when the user explicitly asks — NEVER proactively propose. Triggers: 'write esa article', 'esa記事', 'esaに書いて', 'articlify for esa', or /zudoesa-articlify. Gathers context, creates a writing brief, delegates to the writer subagent.
zudoesa-apply-voice
Apply Takazudo's esa writing voice and vocabulary rules to text. Use when: (1) User wants to write/rewrite text in Takazudo's esa style, (2) User says 'apply voice', 'esa voice', 'esa文体で', 'esa風に書いて', '文体を適用', (3) User provides text to transform to esa style. Reads writing-style.md and vocabulary-rule.md from takazudo-esa-writing repo and applies the rules.
zudocg-articlify
Convert conversation context into a CodeGrid article via the zudocg-writer subagent. ONLY invoke when the user explicitly asks — NEVER proactively propose. Triggers: 'write codegrid article', 'CodeGrid記事', 'codegridに書いて', 'articlify for codegrid', or /zudocg-articlify. Gathers context, creates a writing brief, delegates to the writer subagent.
zudocg-apply-voice
Apply Takazudo's CodeGrid writing voice and vocabulary rules to text. Use when: (1) User wants to write/rewrite text in Takazudo's CodeGrid style, (2) User says 'apply voice', 'codegrid voice', 'codegrid文体で', 'codegrid風に書いて', '文体を適用', (3) User provides text to transform to CodeGrid style. Reads writing-style.md and vocabulary-rule.md from takazudo-codegrid-writing repo and applies the rules.
zpaper-articlify
Convert conversation context into a zpaper blog article via the zpaper-writer subagent. ONLY invoke when the user explicitly asks — NEVER proactively propose. Triggers: 'write zpaper article', 'zpaper記事', 'zpaperに書いて', 'articlify for zpaper', or /zpaper-articlify. Gathers context, creates a writing brief, delegates to the writer subagent.
zpaper-apply-voice
Apply Takazudo's zpaper blog writing voice and vocabulary rules to text. Use when: (1) User wants to write/rewrite text in Takazudo's zpaper style, (2) User says 'apply voice', 'zpaper voice', 'zpaper文体で', 'zpaper風に書いて', 'ブログ文体を適用', (3) User provides text to transform to zpaper style. Reads writing-style.md and vocabulary-rule.md from the zpaper repo and applies the rules.
xlsx
Spreadsheet creation, editing, and analysis. Use when working with .xlsx, .xlsm, .csv, .tsv files for: (1) Creating spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modifying existing spreadsheets while preserving formulas, (4) Data analysis and visualization, (5) Recalculating formulas.
x
Facade for development workflows. Routes on two axes: plan-first vs implement-now (escalates to /big-plan -a when the request needs research / decomposition / has unclear scope — the appended -a makes the plan chain into implementation in-session), then single vs multi on the ready-to-build fast paths (/x-as-pr single-topic, /x-wt-teams multi-topic parallel). Use when: (1) User says '/x' followed by dev instructions, (2) User wants to start development without choosing the workflow skill, (3) User says 'dev', 'implement', or 'build' with a task. Default option: -v (verify-ui). Review-loop (-l) is opt-in — without -l the downstream skill runs a single /deep-review pass. Forwards -a (autonomy/auto-chain) and -m (merge at the end + cleanup + CI watch) through every route; auto-fix of raised findings (-f) and issue-raising (-ri) are downstream defaults, with -nf/--no-fix and -nori/--no-raise-issues as the forwarded opt-outs. -a and -m are orthogonal — full hands-off end-to-end is -a -m.
x-wt-teams
Parallel multi-topic development using git worktrees, base branches, and Claude Code agent teams. Use when: (1) User wants to work on multiple related features in parallel, (2) User mentions 'worktree', 'base branch', 'parallel development', 'split into topics', or 'multi-topic'. FULLY AUTONOMOUS — creates worktrees, spawns teams, coordinates everything. Also supports Super-Epic child mode for [Epic] issues from /big-plan with '**Super-epic:** #N' markers (targets the super-epic base branch instead of main).
x-as-pr
Start a development workflow as a draft PR. Creates a NEW branch from the current branch, empty start commit, draft PR targeting the current branch, then implements. ALWAYS creates a new branch by default — produces a nested PR-on-PR when the current branch already has one. Use when: (1) User says 'dev as pr', (2) User wants a PR-first workflow before coding, (3) User passes -s/--stay to reuse the current branch instead of nesting, (4) User passes a GitHub issue URL to implement, (5) User passes --make-issue/--issue to create an issue first. Logs progress via issue comments when an issue is linked.
watch-ci
Watch GitHub PR CI checks in the background and notify on completion. Use when: (1) User wants to monitor CI/CD status, (2) User says 'watch CI', 'check CI', 'monitor checks', or 'wait for CI', (3) User wants to know when checks pass or fail. Runs a background gh polling shell loop (NOT a subagent — near-zero token cost), sends macOS notification on completion. Also handles merged PRs by watching the target branch CI.
w-update-wording-rule
Add or update wording rules (表記ルール) in the w repo's vocabulary-rule.md files. Use when: (1) User says 'add wording rule', 'update wording rule', '表記ルール追加', (2) User wants to add a kanji/hiragana usage rule, (3) User provides a rule like 'X should be Y' with examples.