Skip to content
Draft
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
10 changes: 10 additions & 0 deletions content/collections/pages/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,16 @@ A structured collection will **not** have a maximum depth unless you set one, al
<figcaption>These reorderable entries have a max depth of 1.</figcaption>
</figure>

### Turning ordering off

:::warning
Switching **Orderable** back off **deletes the collection's tree immediately**, without a confirmation, and there's no undoing it.

Your entries are untouched, but they stop being nested — so every nested entry's URL moves. An entry that lived at `/about/staff` falls back to whatever its collection's route produces without a `parent_uri`, and the old URL returns a 404. Set up [redirects](/routing#redirects) before you flip the switch on a live site.

[Structured taxonomies](/taxonomies#turning-it-off) behave the same way.
:::

### Default sort order in listings

For non-structured collections, you can choose which field and direction to sort the list of entries in the Control Panel by setting the `sort_by` and `sort_dir` variables in your collection.yaml. By default, the Title field will be used.
Expand Down
1 change: 1 addition & 0 deletions content/collections/pages/graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Returns a [paginated](#pagination) list of [EntryInterface](#entry-interface) ty
| `page` | `Int` | The paginated page to be shown. Defaults to `1`.
| `filter` | `JsonArgument` | Narrows down the results based on [filters](#filtering).
| `sort` | `[String]` | [Sorts](#sorting) the results based on one or more fields and directions.
| `with_descendants` | `Boolean` | Defaults to `true`. When filtering by a term on a [nestable taxonomy](/taxonomies#ordering-and-nesting), entries tagged with its descendant terms are included. Pass `false` to match only the exact term.

Example query and response:

Expand Down
75 changes: 74 additions & 1 deletion content/collections/pages/rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ You may send requests to the following endpoints:
- [Sites](#sites)
- [Collections](#collections) / [Collection](#collection)
- [Entries](#entries) / [Entry](#entry)
- [Collection Tree](#collection-tree) / [Navigation Tree](#navigation-tree)
- [Collection Tree](#collection-tree) / [Navigation Tree](#navigation-tree) / [Taxonomy Tree](#taxonomy-tree)
- [Navs](#navs) / [Nav](#nav)
- [Taxonomies](#taxonomies) / [Taxonomy](#taxonomy)
- [Taxonomy Terms](#taxonomy-terms) / [Taxonomy Term](#taxonomy-term)
- [Taxonomy Term Entries](#taxonomy-term-entries)
- [Asset Containers](#asset-containers) / [Asset Container](#asset-container)
- [Assets](#assets) / [Asset](#asset)
- [Globals](#globals) / [Global](#global)
Expand Down Expand Up @@ -367,6 +368,12 @@ Gets entries within a collection.
If you are using [Multi-Site](/multi-site), the entries endpoint will serve from all sites at once. If needed, you can limit the fetched data to a specific site with the `site` query parameter (ie. `?site=fr`), or a `site` [filter](#filtering) (ie. `&filter[site]=fr`).
:::

When you [filter](#filtering) by a term on a [nestable taxonomy](/taxonomies#ordering-and-nesting), entries tagged with that term's descendants are included. Add `?with_descendants=false` to match only the exact term.

```url
/api/collections/products/entries?filter[taxonomy:product_categories]=clothing&with_descendants=false
```


## Entry

Expand Down Expand Up @@ -569,6 +576,72 @@ Gets a single taxonomy term.
}
```

## Taxonomy Term Entries

`GET` `/api/taxonomies/{taxonomy}/terms/{slug}/entries`

Gets the entries tagged with a taxonomy term.

``` json
{
"data": [
{
"title": "My First Day"
}
],
"links": {...},
"meta": {...}
}
```

On a [nestable taxonomy](/taxonomies#ordering-and-nesting), the entries tagged with the term's descendants are included. Add `?with_descendants=false` to get only the entries tagged with this exact term.

```url
/api/taxonomies/product_categories/terms/clothing/entries?with_descendants=false
```

## Taxonomy Tree

`GET` `/api/taxonomies/{taxonomy}/tree`

Gets the term tree for a [structured taxonomy](/taxonomies#ordering-and-nesting). Returns a 404 if the taxonomy isn't structured.

``` json
{
"data": [
{
"term": {
"title": "Clothing",
"url": "/product-categories/clothing"
},
"depth": 1,
"children": [
{
"term": {
"title": "Shirts",
"url": "/product-categories/clothing/shirts"
},
"depth": 2,
"children": []
}
]
}
]
}
```

### Params

On this endpoint, the [fields](#selecting-fields) param will allow you to select fields within each `term` object. You may also set a `max_depth` to limit nesting depth, or `site` to choose the site.

```url
/api/taxonomies/{taxonomy}/tree?fields=title,url&max_depth=2&site=fr
```

:::warning
Taxonomy trees are **not per-site**. The `site` param localizes each `term` payload — its title, slug, and URL — but the tree's shape and order are the same for every site. Requesting a site the taxonomy isn't available in returns a 404.
:::

## Globals

`GET` `/api/globals`
Expand Down
18 changes: 11 additions & 7 deletions content/collections/pages/structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Every structure is a hierarchy of branches. What differs is *what the hierarchy

1. **Structured collections** — the tree *is* the content hierarchy. Nesting and order drive URLs (and sibling order). Your sitemap lives on the collection.
2. **Navigations** — the tree is a menu. Mix entry references, hard URLs, and text nodes. Position in the tree does **not** rewrite entry URLs.
3. **Structured taxonomies** — the tree nests terms inside each other. Nesting and order drive term URLs, and entry queries for a term include its whole subtree. Covered in [Taxonomies](/taxonomies#ordering-and-nesting).

Same drag-and-drop UI. Same YAML tree shape. Different jobs.

Expand Down Expand Up @@ -44,13 +45,14 @@ Same drag-and-drop UI. Same YAML tree shape. Different jobs.

**Use both when** a pages collection owns the URLs and one or more navs compose what appears in chrome. That's normal — not overkill.

| | Structured collection | Navigation |
| --- | --- | --- |
| Owns URLs | Yes — position drives routes | No — entries keep their collection URLs |
| Entry once | Yes | No — can repeat |
| Freeform links / text | Entry-link redirects (via collection settings) | Yes — URLs and text nodes |
| Config lives in | The collection itself | `content/navigation` |
| Tree lives in | `content/trees/collections` | `content/trees/navigation` |
| | Structured collection | Navigation | Structured taxonomy |
| --- | --- | --- | --- |
| Owns URLs | Yes — position drives routes | No — entries keep their collection URLs | Yes — position drives term routes |
| Item once | Yes | No — can repeat | Yes |
| Freeform links / text | Entry-link redirects (via collection settings) | Yes — URLs and text nodes | No |
| Config lives in | The collection itself | `content/navigation` | The taxonomy itself |
| Tree lives in | `content/trees/collections` | `content/trees/navigation` | `content/trees/taxonomies` |
| Tree per site | Yes | Yes | No — one tree, shared |

## Structured collections

Expand Down Expand Up @@ -109,6 +111,8 @@ Each page may have an optional `children` array which is itself another tree. Ne

_\* Text and link branches are only available in navs._

In a taxonomy's tree, branches reference a term by slug with a `term` key instead — see [Taxonomies](/taxonomies#the-tree).

## Templating

Loop either kind of structure with the [nav tag](/tags/nav).
Expand Down
177 changes: 177 additions & 0 deletions content/collections/pages/taxonomies.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ For each taxonomy [assigned to a collection](#collections) you will also get the
- Accessible at `/{collection-url}/{taxonomy-slug}/{term-slug}` (eg. `/products/tags/t-shirts`)
- The `{collection_handle}/{taxonomy_handle}/show` view will be used. (eg. `products/tags/show.antlers.html`)

If the taxonomy is [nestable](#nested-term-urls), term URLs include the slugs of the term's ancestors.

## Term values and slugs

A term **value** is how you might identify a term in your content. For example, “Star Wars”.
Expand All @@ -88,6 +90,179 @@ Titles are saved on a first-come, first-serve basis, which means consistency is

To further clarify, `Star wars`, `star wars`, `StAr WaRS`, and `star-wars` are all treated as the same term. If case-sensitivity is important, you can add a `title` field to the taxonomy blueprint.

## Ordering and nesting

Flick on the **Orderable** switch in the "Ordering & Nesting" area of a taxonomy's settings and you'll have a drag and drop UI in the control panel to order and nest the terms. The taxonomy is now "structured". Learn more about [structures](/structures).

Existing terms are added to the tree in their current sort order, so turning the switch on doesn't rearrange anything on its own.

### Constraining depth

A structured taxonomy will **not** have a maximum depth unless you set one, allowing you to nest terms as deep as you like. Set the **Max Depth** option to limit this behavior. Setting it to `1` gives you a flat, reorderable list — order without nesting, and term URLs stay flat.

``` yaml
# content/taxonomies/product_categories.yaml
title: 'Product Categories'
structure:
max_depth: 3
```

:::tip
Max depth is enforced on the server, not just in the tree UI. Nesting a term too deep — by dragging it, or by choosing a parent when creating one — will be rejected.
:::

### Nested term URLs

Once a taxonomy is nestable (structured with a max depth other than `1`), the default term route gains a `{parent_uri}` segment:

```url
/{taxonomy-slug}/{parent_uri}/{term-slug}
```

So a `shirts` term nested under `clothing` lives at `/product-categories/clothing/shirts`. Root terms have an empty `parent_uri` and keep their existing URL.

### The tree

A taxonomy's tree is stored in a single file at `content/trees/taxonomies/{taxonomy_handle}.yaml`, and each branch references a term by slug.

``` yaml
tree:
-
term: clothing
children:
-
term: shirts
-
term: footwear
```

:::tip
You *can* edit the tree in the file. You *shouldn't*, unless you enjoy YAML indentation as a hobby. The Control Panel's drag-and-drop UI is the move.
:::

### Multi-site

**Taxonomy trees are not per-site.** There is one tree per taxonomy, shared by every site — which is why there's only one tree file, with no site directory. Shape and order are global. Titles, slugs, and therefore parent URIs *are* localized, exactly as they already were for terms.

This is a **deliberate divergence from [collection structures](/collections#ordering)**, which do have a tree per site. A long-standing complaint about collection trees is that people want to arrange things once and have it apply everywhere, which is closer to how terms already work.

Two consequences are worth stating outright, because both read the other way round at first glance:

- **The site selector on a taxonomy switches which site the tree is _rendered_ in, not which tree you are editing.** It swaps the titles, slugs, and URLs shown on each branch. Dragging a term to a new position applies that move to every site. The same control on a collection means something different.
- **The `site` parameter on the [tree endpoint](/rest-api#taxonomy-tree) localizes the term payloads, not the tree's shape or order.** The parameter reads as though it selects a tree; it doesn't.

Branches reference a term by its slug in the default site, so renaming a slug in a secondary site never moves a term in the tree.

### Nesting variables

On a structured taxonomy, terms get these variables in addition to the usual ones.

| Variable | Description |
|----------|-------------|
| `parent` | The term one level up, or `null` for a root term. |
| `children` | The terms directly beneath this one. |
| `ancestors` | Every term above this one, root first. |
| `depth` | How deep the term sits in the tree. Root terms are `1`. |

::tabs

::tab antlers
```antlers
{{ ancestors }}
<a href="{{ url }}">{{ title }}</a> /
{{ /ancestors }}

<h1>{{ title }}</h1>

<ul>
{{ children }}
<li><a href="{{ url }}">{{ title }}</a></li>
{{ /children }}
</ul>
```
::tab blade
```blade
@foreach ($ancestors as $ancestor)
<a href="{{ $ancestor->url }}">{{ $ancestor->title }}</a> /
@endforeach

<h1>{{ $title }}</h1>

<ul>
@foreach ($children as $child)
<li><a href="{{ $child->url }}">{{ $child->title }}</a></li>
@endforeach
</ul>
```
::

:::tip
Terms don't have an `is_root` variable. To check whether you're on a top-level term, compare the depth — `{{ if depth == 1 }}` in Antlers, or `@if ($depth == 1)` in Blade. That's the same check Statamic uses internally.

If you're writing PHP, don't reach for `LocalizedTerm::isRoot()` for this. It's unrelated, and answers a different question: whether the term is in the default site.
:::

### Descendant entries

Filtering entries by a term on a nestable taxonomy **includes the entries of that term's whole subtree by default**. Asking for entries in `clothing` gets you everything tagged `shirts` and `shoes` too, which is almost always what you want from a category page.

When it isn't, pass `with_descendants="false"` to limit the results to entries tagged with that exact term.

::tabs

::tab antlers
```antlers
{{ collection:products taxonomy:product_categories="clothing" with_descendants="false" }}
{{ title }}
{{ /collection:products }}
```
::tab blade
```blade
<statamic:collection:products
taxonomy:product_categories="clothing"
with_descendants="false"
>
{{ $title }}
</statamic:collection:products>
```
::

The opt-out is available wherever you can filter entries by a term:

| Where | How |
|-------|-----|
| [Collection tag](/tags/collection) | `with_descendants="false"` |
| `{{ entries }}` on a [term route](#routing) | `with_descendants="false"` |
| The `{{ query }}` tag, and any other tag pair that loops over a query builder | `with_descendants="false"` |
| [REST API](/rest-api#entries) collection entries | `?with_descendants=false` |
| [REST API](/rest-api#taxonomy-term-entries) term entries | `?with_descendants=false` |
| [GraphQL](/graphql#entries-query) `entries` query | `with_descendants: false` |

On a flat taxonomy — or one with a max depth of `1` — there are no descendants, so the parameter does nothing.

:::tip
In PHP, call `withTaxonomyDescendants(false)` on the [query builder](/content-queries).

```php
Entry::query()
->whereTaxonomy('product_categories::clothing')
->withTaxonomyDescendants(false)
->get();
```
:::

The `entries_count` variable counts descendants too, so it agrees with what `{{ entries }}` gives you. It has no opt-out — query the entries yourself if you need a count of only the directly tagged ones.

### Turning it off

:::warning
Switching **Orderable** back off **deletes the taxonomy's tree file immediately**, without a confirmation, and there's no undoing it.

Your terms are untouched, but they stop being nested — so every nested term's URL moves. A term that lived at `/product-categories/clothing/shirts` is now at `/product-categories/shirts`, and the old URL returns a 404. Set up [redirects](/routing#redirects) before you flip the switch on a live site.

Structured collections behave the same way when you turn **Orderable** off.
:::

## Templating

### Views
Expand Down Expand Up @@ -214,6 +389,8 @@ When on a [term route](#routing), you can list the entries by using an `entries`
```
::

On a nestable taxonomy this includes the entries of the term's [descendants](#descendant-entries). Add `with_descendants="false"` to get only the entries tagged with this exact term.

## Search indexes

You can configure search indexes for your collections to improve the efficiency and relevancy of your users searches. Learn [how to connect indexes](/search#connecting-indexes).
Loading