> For the complete documentation index, see [llms.txt](https://docs.slingdata.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.slingdata.io/concepts/build/structure.md).

# Structure

A Sling Build project is a directory with SQL model files, optional seed files, and an optional `sling_build.yml` configuration. The first folder is the schema. The file name is the table name.

## Project Layout

By default, Sling uses a **flat structure**. Models and seeds are together in the same directories:

```
my_project/
├── sling_build.yml              # project config (optional)
├── utils.macros.sql             # global macros
├── raw.sql                      # model → public.raw
├── staging/
│   ├── stg_orders.sql           # model → staging.stg_orders
│   ├── stg_customers.sql        # model → staging.stg_customers
│   ├── country_codes.csv        # seed  → staging.country_codes
│   └── helpers.macros.sql       # scoped macros (staging only)
├── marts/
│   └── core/
│       ├── dim_customers.sql    # model → marts.dim_customers
│       └── fct_orders.sql       # model → marts.fct_orders
└── seeds/
    └── status_map.json          # seed  → seeds.status_map
```

## Naming Rules

The folder structure sets the schema. The file name sets the table name:

* **1st folder level** = schema name
* **File name** = model name and table name
* **Nested folders** = organization only. They do not change the table name
* **Root-level files** = `public` schema (default)

| File Path                        | Schema    | Model name     | Full Table Name         |
| -------------------------------- | --------- | -------------- | ----------------------- |
| `raw.sql`                        | public    | raw            | `public.raw`            |
| `staging/stg_orders.sql`         | staging   | stg\_orders    | `staging.stg_orders`    |
| `staging/country_codes.csv`      | staging   | country\_codes | `staging.country_codes` |
| `marts/core/dim_customers.sql`   | marts     | dim\_customers | `marts.dim_customers`   |
| `marts/core/fct_orders.sql`      | marts     | fct\_orders    | `marts.fct_orders`      |
| `analytics/plausible/events.sql` | analytics | events         | `analytics.events`      |
| `seeds/status_map.json`          | seeds     | status\_map    | `seeds.status_map`      |

The **model name** is the file name without the extension. It does not change in dev mode. `ref()`, selectors, and `@name` accept the model name or the prod table name (`analytics.events`).

{% hint style="info" %}
Model names must be unique across the project. Two `events.sql` files in different folders are an error:

```
duplicate model name 'events': found in both 'analytics/plausible/events.sql' and 'analytics/stripe/events.sql'
```

Rename one file, or use one file with a different name.
{% endhint %}

## File Classification

| Extension     | Classification | Notes                                                |
| ------------- | -------------- | ---------------------------------------------------- |
| `.sql`        | Model          | Compiled and executed against target                 |
| `.macros.sql` | Macro file     | Gives reusable Jinja macros, not executed as a model |
| `.csv`        | Seed           | Loaded into target with Sling task infrastructure    |
| `.json`       | Seed           | Loaded into target with Sling task infrastructure    |
| `.parquet`    | Seed           | Loaded into target with Sling task infrastructure    |
| Other         | Ignored        | Files with other extensions are skipped              |

## `sling_build.yml`

The project configuration file sets the target connection, dev mode settings, variables, and defaults.

```yaml
# Required: target database connection name
target: MY_POSTGRES

# Optional: dev mode configuration
# When present, dev mode is active by default (override with --prod)
dev:
  target: MY_PG_DEV      # optional, falls back to top-level target
  schema: dev_${USER}    # required for dev mode

# Optional: dbt-compatible directory structure
# dbt_project: true      # shorthand: models/ + seeds/ dirs
# dbt_project:           # custom paths:
#   models_path: models
#   seeds_path: seeds

# Optional: Jinja template variables
vars:
  start_date: '2024-01-01'
  environment: production

# Optional: default settings for all models
defaults:
  mode: full-refresh
  tags: []
```

### Top-Level Keys

| Key            | Type        | Default               | Description                                               |
| -------------- | ----------- | --------------------- | --------------------------------------------------------- |
| `target`       | string      |                       | Target database connection name (required)                |
| `dev`          | object      |                       | Dev mode settings. Presence activates dev mode by default |
| `dev.target`   | string      | (top-level target)    | Optional separate connection for dev mode                 |
| `dev.schema`   | string      |                       | Schema for dev mode (required when `dev` is present)      |
| `dev.database` | string      | (`defaults.database`) | Database for dev mode. Three-part dialects only           |
| `dbt_project`  | bool/object | `false`               | Enable dbt-compatible directory structure                 |
| `vars`         | map         | `{}`                  | Variables available in Jinja templates                    |
| `defaults`     | object      |                       | Default model settings. See below                         |

### `defaults` Keys

These apply to every model. Model front-matter overrides them.

| Key              | Type           | Default         | Description                                                      |
| ---------------- | -------------- | --------------- | ---------------------------------------------------------------- |
| `mode`           | string         | `full-refresh`  | Default materialization mode                                     |
| `schema`         | string         |                 | Default schema override                                          |
| `database`       | string         |                 | Default database override. Three-part dialects only              |
| `tags`           | list           | `[]`            | Tags applied to all models. Additive with model tags             |
| `unique_key`     | string or list |                 | Default merge key(s)                                             |
| `update_key`     | string         |                 | Default incremental watermark column                             |
| `merge_strategy` | string         | `delete+insert` | Default merge strategy                                           |
| `enabled`        | bool           | `true`          | Set `false` to disable models by default                         |
| `hooks`          | object         |                 | Default start/end hooks. Additive with model hooks, parent first |
| `drop_cascade`   | bool           | `false`         | Add `CASCADE` to `DROP` statements                               |

{% hint style="info" %}
`engine`, `range`, `rewrite`, and `tests` are model-only keys. You cannot set them in `defaults`.
{% endhint %}

## Database (Three-Part Names)

Some dialects address an object as `database.schema.table`. Set the `database` key to use the three-part form. Folders never set the database.

```yaml
# sling_build.yml
target: MY_SNOWFLAKE

defaults:
  database: ANALYTICS_DB     # optional; applies to all models

dev:
  schema: dev_${USER}
  database: SCRATCH_DB       # optional; falls back to defaults.database
```

A model can override the database in its front-matter:

```sql
/**
schema: finance
database: FIN_DB
**/
select ...
```

Resolution order for `database`:

1. Model front-matter `database:`.
2. `dev.database` in dev mode.
3. `defaults.database` from the effective config.
4. Empty. The name stays two-part.

Supported dialects: Snowflake, BigQuery, Databricks, Trino, DuckDB (and DuckLake, MotherDuck), SQL Server, Azure SQL, Azure Synapse, and Fabric.

Other dialects fail at compile time:

```
model 'dim_customers': database 'FIN_DB' is set but postgres does not support database.schema.table names
```

## Create sling\_build.yml

Write `sling_build.yml` in the model folder and set `target:` to a connection name. For a full Sling project, run `sling init`. That command writes `models/sling_build.yml`.

You can also pass `--target` on `sling build run` and use no file.

## DBT-Compatible Structure

Set `dbt_project: true` to use separate `models/` and `seeds/` directories:

```yaml
target: MY_POSTGRES
dbt_project: true
```

```
my_project/
├── sling_build.yml
├── models/
│   ├── staging/
│   │   └── stg_orders.sql       # → staging.stg_orders
│   └── marts/
│       └── fct_orders.sql       # → marts.fct_orders
└── seeds/
    └── staging/
        └── country_codes.csv    # → staging.country_codes
```

For custom paths:

```yaml
dbt_project:
  models_path: sql
  seeds_path: data
```

## Dev & Prod Mode

Dev and prod modes isolate development work from production data. Dev mode sends all models into a single schema. Prod mode uses the folder-based schema mapping.

| Mode               | Schema Behavior                                     | When Active                                    |
| ------------------ | --------------------------------------------------- | ---------------------------------------------- |
| **Prod** (default) | Folder-based schemas (1st folder = schema)          | No `dev` block in yml, or `--prod` flag        |
| **Dev**            | All models in a single dev schema, same table names | `dev` block present in yml, or `--schema` flag |

### Configuring Dev Mode

Add a `dev` block to your `sling_build.yml`. When present, dev mode is active by default.

```yaml
target: MY_POSTGRES

dev:
  schema: dev_${USER}           # required — all models go here
  target: MY_PG_DEV             # optional — use a different connection

defaults:
  mode: full-refresh
```

* `dev.schema` is mandatory when `dev` is present
* `dev.target` is optional. If you do not set it, Sling uses the top-level `target`

### Variables in sling\_build.yml

`sling_build.yml` expands `${VAR}` and `${VAR:-fallback}` before parse. Bare `$VAR` is not expanded. One committed file can name a per-user dev schema:

```yaml
# sling_build.yml (committed)
dev:
  schema: dev_${USER}
```

```yaml
# ~/.sling/env.yaml (per machine)
env:
  USER: fritz
```

On macOS and Linux the OS already sets `USER` to the login name, so `env.yaml` does not override it. Export a different value in the shell, or use a Sling-specific name such as `${SLING_DEV_USER}`.

An unset variable with no fallback is an error only when the field is in effect:

| Situation                                         | Result                              |
| ------------------------------------------------- | ----------------------------------- |
| `dev.schema: dev_${USER}`, `USER` unset, dev mode | Error                               |
| same file, `--prod`                               | OK — the dev block is not in effect |
| same file, `--schema dev_x`                       | OK — the flag replaces the field    |
| `dev.schema: dev_${USER:-scratch}`, `USER` unset  | OK — schema is `dev_scratch`        |
| `vars.start_date: ${START}`, unset                | Error — `vars` are always in effect |

SQL models keep Jinja `{{ var() }}` and `{{ env_var() }}`. They are not expanded with `${VAR}`.

### Naming in Dev vs Prod

Dev and prod names differ only by schema. The table name is the file name in both modes.

| File Path                        | Model name     | Prod table              | Dev table (`dev_fritz`)   |
| -------------------------------- | -------------- | ----------------------- | ------------------------- |
| `staging/stg_orders.sql`         | stg\_orders    | `staging.stg_orders`    | `dev_fritz.stg_orders`    |
| `marts/core/dim_customers.sql`   | dim\_customers | `marts.dim_customers`   | `dev_fritz.dim_customers` |
| `analytics/plausible/events.sql` | events         | `analytics.events`      | `dev_fritz.events`        |
| `raw.sql`                        | raw            | `public.raw`            | `dev_fritz.raw`           |
| `staging/country_codes.csv`      | country\_codes | `staging.country_codes` | `dev_fritz.country_codes` |

Model names are unique across the project, so one dev schema never has a collision.

### Override Rules

| Condition                  | Effective Mode                                     |
| -------------------------- | -------------------------------------------------- |
| No `dev` block in yml      | Prod                                               |
| `dev` block present in yml | Dev                                                |
| `--prod` flag              | Prod (ignores `dev` block)                         |
| `--schema <name>` flag     | Dev with specified schema (overrides `dev.schema`) |
| `--prod` + `--schema`      | Error — `cannot combine --prod and --schema`       |
| `--target <conn>`          | Overrides the resolved target in either mode       |

### Dev Workflow Example

```yaml
# sling_build.yml
target: MY_POSTGRES

dev:
  schema: dev_${USER}
```

```bash
# 1. Run your model and its dependencies in the dev schema
sling build run -s "+fct_orders"

# 2. Check the compiled SQL
sling build compile -s "fct_orders"

# 3. Query your dev tables directly
sling conns exec MY_POSTGRES -q "SELECT count(*) FROM dev_fritz.fct_orders"

# 4. Deploy to production
sling build run --prod
```

You can also use `--schema` with no `dev` block in the yml:

```bash
sling build run --schema my_scratch -s "stg_orders"
```

## Selectors

Selectors control which models and seeds a build run includes. Use `--select` to include models and `--exclude` to remove them. Both accept comma-separated lists, and the result is the **union** of all patterns.

| Pattern               | Type                | Description                                       | Example                                  |
| --------------------- | ------------------- | ------------------------------------------------- | ---------------------------------------- |
| `dim_customers`       | Name                | Model name (the file name)                        | `sling build run -s dim_customers`       |
| `marts.dim_customers` | Table               | Prod table name                                   | `sling build run -s marts.dim_customers` |
| `stg_*`               | Glob                | Match model names by glob pattern                 | `sling build run -s "stg_*"`             |
| `tag:daily`           | Tag                 | Match models with a specific tag                  | `sling build run -s "tag:daily"`         |
| `+fct_orders`         | Upstream            | Model and all its upstream dependencies           | `sling build run -s "+fct_orders"`       |
| `fct_orders+`         | Downstream          | Model and all its downstream dependents           | `sling build run -s "fct_orders+"`       |
| `+fct_orders+`        | Full graph          | All upstream + model + all downstream             | `sling build run -s "+fct_orders+"`      |
| `2+fct_orders`        | N-degree upstream   | Model and N levels of upstream dependencies       | `sling build run -s "2+fct_orders"`      |
| `fct_orders+1`        | N-degree downstream | Model and N levels of downstream dependents       | `sling build run -s "fct_orders+1"`      |
| `staging/*`           | Path                | Match models by relative file path                | `sling build run -s "staging/*"`         |
| `stg_a-fct_b`         | Slice               | All models between A and B in the DAG (inclusive) | `sling build run -s "stg_a-fct_b"`       |

### Glob Patterns

Glob selectors match against model **names** (the filename without extension).

| Wildcard | Meaning                    | Example               | Matches                          |
| -------- | -------------------------- | --------------------- | -------------------------------- |
| `*`      | Any sequence of characters | `stg_*`               | `stg_orders`, `stg_customers`    |
| `?`      | Any single character       | `stg_?rders`          | `stg_orders`                     |
| `[abc]`  | Character class            | `stg_[oc]*`           | `stg_orders`, `stg_customers`    |
| `{a,b}`  | Alternation                | `{stg,dim}_customers` | `stg_customers`, `dim_customers` |

Wildcards can be anywhere in the pattern:

```bash
sling build run -s "stg_*" # prefix match
sling build run -s "*_orders" # suffix match
sling build run -s "*customer*" # contains
sling build run -s "stg_*_v2" # prefix and suffix
```

### Glob with Graph Operators

Glob patterns work with all graph operators (`+`, `N+`, `+N`). The glob resolves first. Then the graph operator applies to **every** matched model, and Sling combines the results.

```bash
# All staging models + their downstream dependents
sling build run -s "stg_*+"

# All upstream dependencies of models ending in _orders
sling build run -s "+*_orders"

# All fct_ models + their full upstream and downstream graph
sling build run -s "+fct_*+"

# 1 level downstream of all staging models
sling build run -s "stg_*+1"

# 2 levels upstream of all models matching *_revenue
sling build run -s "2+*_revenue"
```

An exact model name that does not exist gives an error. A glob that matches nothing gives an empty result and no error.

### Path / Folder Selectors

Any selector with a `/` matches against the model's **relative file path** from the project root. This is useful to select all models in a folder, which usually maps to a database schema.

```bash
sling build run -s "staging/*" # all models in the staging folder
sling build run -s "marts/core/*" # all models under marts/core
sling build run -s "marts/**/*" # all models anywhere under marts
```

Given this project structure:

```
models/
  staging/
    stg_orders.sql      → staging.stg_orders
    stg_customers.sql   → staging.stg_customers
  marts/
    core/
      dim_customers.sql → marts.dim_customers
      fct_orders.sql    → marts.fct_orders
    finance/
      revenue.sql       → marts.revenue
seeds/
  staging/
    country_codes.csv   → staging.country_codes
```

| Path selector     | Matches                                        |
| ----------------- | ---------------------------------------------- |
| `staging/*`       | `stg_orders`, `stg_customers`, `country_codes` |
| `marts/core/*`    | `dim_customers`, `fct_orders`                  |
| `marts/finance/*` | `revenue`                                      |
| `marts/**/*`      | `dim_customers`, `fct_orders`, `revenue`       |

### Combining and Excluding

```bash
# Select all staging models AND all models tagged "daily"
sling build run -s "stg_*,tag:daily"

# Run all models except debug ones
sling build run --exclude "stg_debug_*"

# Select staging models but skip a specific one
sling build run -s "stg_*" --exclude "stg_legacy"
```

`--exclude` applies after `--select`, and uses the same pattern syntax.

### Default Behavior

* **No `--select`** = all models and seeds are selected
* **`--no-seeds`** = seeds are skipped from execution, but downstream models still run
* Models with `enabled: false` never enter the DAG, so you cannot select them
* Tags match models only. A `tag:` selector never matches a seed

### List Mode

Use `sling build list` to preview which models are selected, with no execution.

```bash
$ sling build list -s "tag:daily"

stg_orders          staging.stg_orders           (full-refresh)
stg_customers       staging.stg_customers        (full-refresh)
core_fct_orders     marts.core_fct_orders        (incremental)
```

This respects `--select` and `--exclude`. Without a target, the table column is omitted and the file path is shown instead.

### Compile Mode

Use `sling build compile` to see the DAG execution order and the compiled SQL for each selected model.

```bash
$ sling build compile -s "+fct_orders"

=== DAG Execution Order ===

Level 0 (seeds):
  - country_codes

Level 1:
  - stg_orders
  - stg_customers

Level 2:
  - fct_orders

=== Compiled SQL ===

-- stg_orders (staging.stg_orders) [full-refresh]
SELECT id, customer_id, amount, created_at
FROM raw_data.orders

-- fct_orders (marts.core_fct_orders) [incremental]
SELECT
    o.id,
    o.customer_id,
    o.amount,
    c.name as customer_name,
    o.created_at
FROM staging.stg_orders o
JOIN staging.stg_customers c ON o.customer_id = c.id
WHERE "created_at" > '2024-03-15 10:00:00'
```

Compile mode checks that all templates compile and all references resolve. It connects to the target only when it must resolve an incremental watermark.

### JSON Output

Add `--json` to `compile` or `list` for machine-readable output:

```bash
sling build compile --json
```

The payload has this shape:

```json
{
  "order": ["stg_orders", "fct_orders"],
  "nodes": [
    {
      "name": "fct_orders",
      "type": "model",
      "table": "marts.core_fct_orders",
      "file": "marts/core/fct_orders.sql",
      "mode": "incremental",
      "dependencies": ["stg_orders"],
      "sql": "SELECT ...",
      "tests": [{"not_null": "id"}]
    }
  ],
  "target": "MY_POSTGRES"
}
```

Sub-projects produce a JSON array of these objects.

## Nested Configs (Multi-Target)

{% hint style="info" %}
Nested-config discovery is **opt-in with `-R` / `--recursive`**. By default, `sling build run <path>` loads only `<path>/sling_build.yml` and ignores any `sling_build.yml` files in subdirectories. Pass `-R` to enable the inheritance and multi-target behavior below.
{% endhint %}

Discovery is one level deep. Sling scans only the immediate subdirectories, and skips directories that start with `.`.

### Inheritance

When a root `sling_build.yml` exists and child directories also have `sling_build.yml` files, `-R` makes each child config **inherit** from the root and override specific settings:

```
my_project/
├── sling_build.yml              # root config (target: MY_POSTGRES)
├── staging/
│   ├── sling_build.yml          # overrides: vars, defaults
│   └── stg_orders.sql
└── marts/
    └── fct_orders.sql           # inherits root config
```

```bash
sling build run my_project -R
```

Merge rules for a child config:

* `vars` — deep-merged, child wins on conflict
* `defaults.tags` — union of parent and child, deduplicated
* `defaults.hooks` — appended, parent hooks first
* All other `defaults` fields — child replaces parent when non-empty
* `target`, `dev`, `dbt_project` — child replaces parent when non-empty

Without `-R`, Sling applies only the root `sling_build.yml` and ignores the child overrides.

### Independent Builds (Multi-Target)

When there is **no root** `sling_build.yml` but child directories each have their own, `-R` treats them as independent build projects that run in parallel:

```
my_project/
├── warehouse_a/
│   ├── sling_build.yml          # target: POSTGRES
│   └── staging/
│       └── stg_orders.sql
└── warehouse_b/
    ├── sling_build.yml          # target: CLICKHOUSE
    └── staging/
        └── stg_events.sql
```

```bash
sling build run my_project -R
```

Each sub-project compiles and executes independently against its own target. Sling limits concurrent sub-projects to the `--threads` value, and collects errors from all of them.

{% hint style="warning" %}
Without `-R`, this layout has no root `sling_build.yml` and no discovered child configs, so `sling build` prints the help menu instead of running. Pass `-R` or add a root config.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.slingdata.io/concepts/build/structure.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
