Passthrough Copy
Passthrough mappings copy directories and files into the output directory without processing them:
passthrough:
# Internal directory
- from: "vendor/js"
to: "js/vendor" # -> _site/js/vendor/
# Monorepo sibling package
- from: "../design-system/dist/elements"
to: "elements" # -> _site/elements/
from paths are resolved relative to the project root. Relative paths that reach outside the project (like ../) and absolute paths are supported but not recommended – the referenced files must exist in the build environment, and paths that work locally can break in CI where the repo is checked out in isolation.
Glob Patterns
The from field accepts glob patterns (**, {a,b}, [chars]) for fine-grained file selection:
passthrough:
# Only .js and .css files
- from: "elements/**/*.{js,css}"
to: "assets/packages/elements"
# Only .woff2 font files
- from: "../shared-assets/fonts/**/*.woff2"
to: "assets/fonts"
The output path preserves directory structure relative to the glob root (the longest static prefix before any metacharacter). For elements/**/*.js, the glob root is elements. A file at elements/rh-button/rh-button.js copies to _site/assets/packages/elements/rh-button/rh-button.js.
Exclude Patterns
The exclude array filters out files using gitignore-style patterns:
passthrough:
# Copy everything except demos and sourcemaps
- from: "elements"
to: "assets/packages/elements"
exclude:
- "*.html" # any .html file at any depth
- "demo/" # entire demo/ directory tree
- "**/*.map" # any .map file at any depth
Exclude patterns follow gitignore normalization rules:
| Pattern | Normalized Form | Matches |
|---|---|---|
*.html |
**/*.html |
foo.html, sub/bar.html |
demo/ |
demo/** |
demo/index.html, demo/sub/file.js |
demo/*.html |
demo/*.html |
demo/foo.html (not demo/sub/bar.html) |
**/*.map |
**/*.map |
foo.map, sub/bar.map |
Patterns without / match filenames at any depth. Patterns ending with / match entire directory trees. Patterns containing / match against the relative path as-is.
Combining Glob and Exclude
passthrough:
- from: "elements/**/*.{js,css}"
to: "assets/packages/elements"
exclude:
- "*.min.js" # skip minified JS
- "*.test.js" # skip test files
Managed Directory Protection
If a passthrough from: path resolves to a managed directory (content, layouts, assets, static, data, plugins, .alloy), it is silently ignored. Those directories are already processed by the pipeline. This check respects custom paths set in structure:.
Watch Directories
Watch directories register external directories for pipeline-triggering rebuilds during dev and serve modes. Use watch: when a directory contains files your plugins or templates depend on, where a change should trigger a page rebuild rather than a simple file copy:
watch:
- from: "elements"
type: content
- from: "shared-layouts"
type: layout
- from: "external-data"
type: data
The type field determines what kind of rebuild occurs:
| Type | Effect |
|---|---|
content |
Content change – rebuild affected pages |
layout |
Layout change – rebuild pages using affected layouts |
data |
Data change – rebuild pages that could be affected |
A directory should not appear in both watch: and passthrough:. If it does, the watch classification takes precedence and the passthrough recopy behavior is skipped.
Output Path Conflicts
If two sources target the same output path, the build fails immediately:
[alloy] ERROR Output path conflict detected:
_site/elements/button.js is claimed by:
1. static/elements/button.js
2. passthrough "../design-system/dist/elements" -> "elements"
Resolve by renaming one source or adjusting the passthrough "to" path.
Build aborted.
There is no priority system. Conflicts must be resolved explicitly.
Build vs Dev Mode
alloy build: passthrough files are copied to _site/. File copies use a bounded worker pool for performance.
alloy dev and alloy serve: passthrough files are copied to _site/ during the initial build. The file watcher monitors passthrough source directories. On change, only the modified file is recopied – not the entire directory.