---
title: "Filtering and Git-Based Filtering"
description: "Run tasks for specific apps with --filter, build with dependencies, filter by path, use git-based filtering for changed code, and optimize CI."
canonical_url: "https://vercel.com/academy/production-monorepos/filtering-git-based"
md_url: "https://vercel.com/academy/production-monorepos/filtering-git-based.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-12T01:55:31.723Z"
content_type: "lesson"
course: "production-monorepos"
course_title: "Production Monorepos with Turborepo"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Filtering and Git-Based Filtering

# Filtering and git-based filtering

Your CI builds everything every time. If you change one line in apps/web, CI rebuilds packages/ui, packages/utils, and apps/snippet-manager too - wasting time. You need to run tasks only for changed packages and their dependents.

Turborepo's `--filter` flag lets you scope tasks to specific packages. Combined with git-based filtering (`--filter=[main]`), CI automatically detects changed packages since the last commit and only runs necessary tasks.

## Outcome

Use `--filter` to run selective tasks and enable git-based filtering in CI for incremental builds.

## Fast track

1. Learn `--filter` syntax for targeting packages
2. Use `--filter=[main]` for changed packages
3. Update CI to use git-based filtering
4. See massive time savings on incremental changes

## Hands-on exercise 7.2

Implement selective task execution with filtering.

**Requirements:**

1. Run tasks for single package: `--filter @geniusgarage/web`
2. Run tasks with dependencies: `--filter=@geniusgarage/web...`
3. Use git-based filtering: `--filter=[main]`
4. Update GitHub Actions to use git-based filtering
5. Test with small change to verify selective builds

**Implementation hints:**

- `--filter` selects specific packages
- `...` includes dependents (packages that depend on this one)
- `^...` includes dependencies (packages this one depends on)
- `[main]` compares against main branch for changed packages

## Filter syntax

### Filter single package

```bash
# Build only apps/web
turbo build --filter @geniusgarage/web
```

Output:

```
• Packages in scope: @geniusgarage/web
• Running build in 1 package

@geniusgarage/web:build: cache miss, executing

Tasks:    1 successful, 1 total
```

Only apps/web builds!

### Filter with dependencies

```bash
# Build apps/web and everything it depends on
turbo build --filter @geniusgarage/web...
```

Output:

```
• Packages in scope: @geniusgarage/ui, @geniusgarage/web
• Running build in 2 packages

@geniusgarage/ui:build        ✓
@geniusgarage/web:build       ✓

Tasks:    3 successful, 3 total
```

Builds web + its dependencies (ui, config).

### Filter dependents

```bash
# Build packages/ui and everything that depends on it
turbo build --filter @geniusgarage/ui...
```

Output:

```
• Packages in scope: @geniusgarage/ui, @geniusgarage/web, @geniusgarage/snippet-manager
• Running build in 3 packages
```

Builds ui + apps that use it (web, app).

## Git-based filtering

### Filter changed packages

```bash
# Build only packages changed since main branch
turbo build --filter=[main]
```

If you changed packages/ui:

```
• Packages in scope: @geniusgarage/ui, @geniusgarage/web, @geniusgarage/snippet-manager
• Running build in 3 packages (changed: ui, dependents: web, app)
```

Turborepo automatically includes dependents!

### How it works

```bash
# What changed?
git diff main...HEAD --name-only
packages/ui/src/button.tsx

# Turbo includes:
# - packages/ui (changed)
# - apps/web (depends on ui)
# - apps/snippet-manager (depends on ui)
```

## Try it

### 1. Test filtering locally

Make a change to packages/ui:

```tsx title="packages/ui/src/button.tsx"
// Add comment
export function Button() {
  // Changed!
```

Run with git filter:

```bash
git add .
git commit -m "test: change button"
turbo build --filter=[HEAD^]
```

Output:

```
• Packages in scope: @geniusgarage/ui, @geniusgarage/web, @geniusgarage/snippet-manager
```

Only ui + dependents build!

### 2. Update CI with git filtering

Update `.github/workflows/ci.yml`:

```yaml title=".github/workflows/ci.yml" {38-40}
name: CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Fetch all history for git filtering

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: Cache Turborepo
        uses: actions/cache@v3
        with:
          path: node_modules/.cache/turbo
          key: ${{ runner.os }}-turbo-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-turbo-

      - name: Install dependencies
        run: pnpm install

      - name: Run build, lint, and test (changed packages only)
        run: turbo build lint test --filter=[origin/main]
```

**Key changes:**

- `fetch-depth: 0` - Fetch full git history (needed for comparison)
- `--filter=[origin/main]` - Compare against remote main branch

### 3. Test incremental CI

Make a small change:

```bash
echo "# Update" >> apps/web/README.md
git add .
git commit -m "docs: update web readme"
git push
```

CI output:

```
• Packages in scope: @geniusgarage/web
• Running build in 1 package
• Skipped: @geniusgarage/snippet-manager, @geniusgarage/ui, etc.

Tasks:    1 successful, 1 total
Time:     23s (was 2m 45s!)
```

**2m 45s → 23s** on incremental change!

## Filter patterns

**Exact package:**

```bash
--filter @geniusgarage/web
```

**Package + dependencies:**

```bash
--filter @geniusgarage/web...
```

**Package + dependents:**

```bash
--filter ...@geniusgarage/ui
```

**Multiple packages:**

```bash
--filter @geniusgarage/web --filter @geniusgarage/snippet-manager
```

**All apps:**

```bash
--filter "./apps/*"
```

**All packages:**

```bash
--filter "./packages/*"
```

**Changed since main:**

```bash
--filter=[main]
```

## CI optimization strategy

**Branch builds (PRs):**

```yaml
# Only changed packages
run: turbo build lint test --filter=[origin/main]
```

**Main branch builds:**

```yaml
# Full build (no filter)
run: turbo build lint test
```

Conditional example:

```yaml
- name: Run tasks
  run: |
    if [ "${{ github.ref }}" == "refs/heads/main" ]; then
      turbo build lint test
    else
      turbo build lint test --filter=[origin/main]
    fi
```

## Commit

```bash
git add .github/workflows/ci.yml
git commit -m "ci: add git-based filtering for incremental builds"
```

## Done-when

Verify filtering works:

- [ ] Ran `--filter @geniusgarage/web` and saw only web build
- [ ] Ran `--filter @geniusgarage/web...` and saw web + dependencies
- [ ] Ran `--filter ...@geniusgarage/ui` and saw ui + dependents
- [ ] Ran `--filter=[HEAD^]` and saw only changed packages
- [ ] Updated CI with fetch-depth: 0
- [ ] Updated CI with --filter=\[origin/main]
- [ ] Tested incremental change and saw selective builds
- [ ] Understood ... suffix for dependencies and dependents
- [ ] Understood \[branch] syntax for git-based filtering

## What's Next

Filtering speeds up CI, but cache is still local to each machine. Next lesson: **Remote Caching** - you'll configure Vercel remote cache so your team shares build artifacts across machines, enabling instant cache hits even on fresh clones.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
