# Tags

An interactive tags input field with autocompletion

****

- Read more: <https://getkirby.com/docs/guide/blueprints/fields>
- Source: <https://github.com/getkirby/kirby/tree/5.5.3/config/fields/tags.php>

****

The tags field is perfect for multiple variable options, which should be stored in a single field. It comes with built-in autocompletion for options and an intuitive interface for adding and removing multiple tags.

<figure class="image"><a data-lightbox href="https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=1416&amp;height=328&amp;enlarge=0"><img alt="" class="rounded" height="214" loading="lazy" sizes="auto" src="https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=924&amp;height=214&amp;enlarge=0" srcset="https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=462&amp;height=107&amp;enlarge=0 462w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=690&amp;height=160&amp;enlarge=0 690w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=924&amp;height=214&amp;enlarge=0 924w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=1380&amp;height=320&amp;enlarge=0 1380w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/b18ed4faf8-1717846665/tags.png?width=1416&amp;height=328&amp;enlarge=0 1848w" width="924"></a></figure>

## Example

```yaml
fields:
  tags:
    label: Tags
    type: tags
```

## Field properties



<div class="table">
	<table class="parameters">
		<thead>
			<tr>
				<th>Name</th>
				<th>Type</th>
				<th>Default</th>

								<th>Description</th>
							</tr>
		</thead>
		<tbody>
						<tr>
				<td>
					accept				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>'all'</code>
									</td>

								<td>If set to <code class="type">all</code>, any type of input is accepted. If set to <code class="type">options</code> only the predefined options are accepted as input.</td>
							</tr>
						<tr>
				<td>
					api				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>API settings for options requests. This will only take affect when <code class="type">options</code> is set to <code class="type">api</code>.</td>
							</tr>
						<tr>
				<td>
					autofocus				</td>
				<td><code class="type type-bool">bool</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Sets the focus on this field when the form loads. Only the first field with this label gets</td>
							</tr>
						<tr>
				<td>
					default				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Default value for the field, which will be used when a page/file/user is created</td>
							</tr>
						<tr>
				<td>
					disabled				</td>
				<td><code class="type type-bool">bool</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>If <code class="type type-bool">true</code>, the field is no longer editable and will not be saved</td>
							</tr>
						<tr>
				<td>
					help				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Optional help text below the field</td>
							</tr>
						<tr>
				<td>
					icon				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>'tag'</code>
									</td>

								<td>Changes the tag icon</td>
							</tr>
						<tr>
				<td>
					label				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>The field label can be set as string or associative array with translations</td>
							</tr>
						<tr>
				<td>
					layout				</td>
				<td><code class="type type-string">string</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Set to <code class="type">list</code> to display each tag with 100% width, otherwise the tags are displayed inline</td>
							</tr>
						<tr>
				<td>
					max				</td>
				<td><code class="type type-int">int</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Maximum number of allowed entries/tags</td>
							</tr>
						<tr>
				<td>
					min				</td>
				<td><code class="type type-int">int</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Minimum number of required entries/tags</td>
							</tr>
						<tr>
				<td>
					options				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>[ ]</code>
									</td>

								<td>An array with options</td>
							</tr>
						<tr>
				<td>
					query				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Query settings for options queries. This will only take affect when <code class="type">options</code> is set to <code class="type">query</code>.</td>
							</tr>
						<tr>
				<td>
					required				</td>
				<td><code class="type type-bool">bool</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>If <code class="type type-bool">true</code>, the field has to be filled in correctly to be saved.</td>
							</tr>
						<tr>
				<td>
					search				</td>
				<td><code class="type type-array">array</code><span class="px-1 color-gray-400" aria-hidden="true">|</span><span class="sr-only">or</span><code class="type type-bool">bool</code></td>

				<td data-label="Default:">
															<code>true</code>
									</td>

								<td>Enable/disable the search in the dropdown Also limit displayed items (display: 20) and set minimum number of characters to search (min: 3)</td>
							</tr>
						<tr>
				<td>
					separator				</td>
				<td><code class="type type-string">string</code></td>

				<td data-label="Default:">
															<code>','</code>
									</td>

								<td>Custom tags separator, which will be used to store tags in the content file</td>
							</tr>
						<tr>
				<td>
					sort				</td>
				<td><code class="type type-bool">bool</code></td>

				<td data-label="Default:">
															<code>false</code>
									</td>

								<td>If <code class="type type-bool">true</code>, selected entries will be sorted according to their position in the dropdown</td>
							</tr>
						<tr>
				<td>
					translate				</td>
				<td><code class="type type-bool">bool</code></td>

				<td data-label="Default:">
															<code>true</code>
									</td>

								<td>If <code class="type type-bool">false</code>, the field will be disabled in non-default languages and cannot be translated. This is only relevant in multi-language setups.</td>
							</tr>
						<tr>
				<td>
					when				</td>
				<td><code class="type type-mixed">mixed</code></td>

				<td data-label="Default:">
															<code>null</code>
									</td>

								<td>Conditions when the field will be shown (since 3.1.0)</td>
							</tr>
						<tr>
				<td>
					width				</td>
				<td><code class="type type-string">string</code></td>

				<td data-label="Default:">
															<code>'1/1'</code>
									</td>

								<td>The width of the field in the field grid, e.g. <code class="type">1/1</code>, <code class="type">1/2</code>, <code class="type">1/3</code>, <code class="type">1/4</code>, <code class="type">2/3</code>, <code class="type">3/4</code></td>
							</tr>
					</tbody>
	</table>
</div>


## Autocomplete

For autocompletion to work, you need to set the `options` attribute.  Options can be a fixed list (see below) or [dynamic options](#dynamic-options) using the query option.

```yaml
fields:
  tags:
    label: Tags
    type: tags
    options:
      - Design
      - Architecture
      - Photography
      - 3D
      - Web
```

You can also define different `value` and `text` for options:

```yaml
fields:
  tags:
    label: Tags
    type: tags
    options:
      - value: study/design
        text: Design
      - value: study/architecture
        text: Architecture
```

<h2 id="dynamic-options"><a href="#dynamic-options" tabindex="-1">Dynamic options</a></h2>
<p>Our options <a href="https://getkirby.com/docs/guide/blueprints/query-language">query syntax</a> offers a very powerful way of converting pages, files, users, page values and even items in structure fields into automatically generated option lists.</p>
<h3 id="dynamic-options__option-queries"><a href="#dynamic-options__option-queries" tabindex="-1">Option queries</a></h3>
<figure class="code">
<pre><code class="language-yaml">fields:
  category:
    label: Category
    type: tags
    options:
      type: query
      query: site.children.published</code></pre>
</figure>
<p>The example above will turn all published main pages of the site into options. The title of each page will be used as the text of the option and the page id will be used as the stored value.</p>
<h3 id="dynamic-options__a-few-more-examples"><a href="#dynamic-options__a-few-more-examples" tabindex="-1">A few more examples</a></h3>
<figure class="code">
<pre><code class="language-yaml">query: site.children.template("project").limit(10)
query: page.images.offset(2)
query: users.filterBy("role", "admin").sortBy("name", "desc")
query: page.links.toStructure</code></pre>
</figure>
<p>You can start at the <code class="type">site</code>, current <code class="type">page</code>, <code class="type">users</code> collection or the <code class="type">kirby</code> instance to run your query. The result must be a collection of <code class="type">pages</code>, <code class="type">files</code>, <code class="type">users</code> or a structure object</p>
<p>You can use array syntax and nested queries in Kirby's query syntax.</p>
<figure class="code">
<pre><code class="language-yaml">query: site.index.filterBy("template", "in", ["note", "album"])
query: kirby.collection("some-collection").not(kirby.collection("excluded-collection"))</code></pre>
</figure>
<h3 id="dynamic-options__getting-options-from-all-siblings-or-the-index"><a href="#dynamic-options__getting-options-from-all-siblings-or-the-index" tabindex="-1">Getting options from all siblings or the index</a></h3>
<figure class="code">
<pre><code class="language-yaml">query: page.siblings.pluck("tags", ",", true)
query: page.index.pluck("tags", ",", true)
query: site.index.pluck("tags", ",", true)</code></pre>
</figure>
<h3 id="dynamic-options__custom-text-and-value"><a href="#dynamic-options__custom-text-and-value" tabindex="-1">Custom text and value</a></h3>
<p>To customize the displayed text or the stored value, you can be more specific when defining the query: <code class="type">text</code> and <code class="type">value</code> can be defined with the help of our string template language to get exactly what you want as the result.</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: query
    query: site.children.published
    text: "{{ page.year }}"
    value: "{{ page.slug }}"</code></pre>
</figure>
<p>As in the example above, all custom fields of a page can be accessed. You can even combine fields and use <a href="https://getkirby.com/docs/reference/templates/field-methods">field methods</a>:</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: query
    query: site.children.published
    text: "{{ page.year }} - {{ page.title.upper }}"
    value: "{{ page.slug }}"</code></pre>
</figure>
<h3 id="dynamic-options__numeric-keys"><a href="#dynamic-options__numeric-keys" tabindex="-1">Numeric keys</a></h3>
<p>If you want to store numeric keys as values, you have to use the long notation with <code class="type">value</code> and <code class="type">text</code>:</p>
<figure class="code">
<pre><code class="language-yaml">fields:
  category:
    label: Category
    type: tags
    options:
      - value: '100'
        text: Design
      - value: '200'
        text: Architecture</code></pre>
</figure>
<h2 id="options-from-other-fields"><a href="#options-from-other-fields" tabindex="-1">Options from other fields</a></h2>
<p>With a query it is not only possible to fetch options from pages, users, files or structure fields. You can also split comma-separated values of fields such as tags or checkboxes in order to create options from the result array.</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: query
    query: site.taxonomy.split</code></pre>
</figure>
<p>Of course you get the same flexibility with those array values, to modify the result text and stored value. Each item in the array will automatically be converted into an object with a <code class="type">key</code> and <code class="type">value</code> property. Those properties are regular Kirby content fields and you can use all <a href="https://getkirby.com/docs/reference/templates/field-methods">field methods</a> to work with them further. Items in the array need to be referenced as <code class="type">item</code></p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: query
    query: site.taxonomy.split
    text: "{{ item.value.upper }}"
    value: "{{ item.value.slug }}"</code></pre>
</figure>
<h3 id="options-from-other-fields__a-custom-separator"><a href="#options-from-other-fields__a-custom-separator" tabindex="-1">A custom separator</a></h3>
<p>If the values in a field are separated by something other than a comma, you can of course specify this as well in the query.</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: query
    query: page.categories.split(";")</code></pre>
</figure>
<h3 id="options-from-other-fields__options-from-structure-field"><a href="#options-from-other-fields__options-from-structure-field" tabindex="-1">Options from structure field</a></h3>
<p>To fetch options from a structure field, you can use the <code class="type">toStructure</code> method and then fetch the text and value from the fields of the structure items:</p>
<p>Assuming we have a structure field like this:</p>
<figure class="code">
<pre><code class="language-yaml">fediverse:
  label: Follow the Kirby Team in the fediverse …
  type: structure
  fields:
    name:
      label: Team Member
      type: text
    fediverse:
      label: Fediverse URL
      type: url</code></pre>
</figure>
<p>We can fetch the fields by using the keyword <code class="type">item</code>:</p>
<figure class="code">
<pre><code class="language-yaml">contact:
  label: Contact person
  type: tags
  options:
    type: query
    query: site.contactoptions.toStructure
    text: "{{ item.name }}"
    value: "{{ item.fediverse }}"</code></pre>
</figure>
<h2 id="options-via-api"><a href="#options-via-api" tabindex="-1">Options via API</a></h2>
<p>If the option queries are not enough or you need to pluck an external source for option data, you can use the API type.</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: api
    url: https://your-options-api.com/options.json</code></pre>
</figure>
<p>By default, the API type expects that the JSON endpoint returns an option array like this:</p>
<figure class="code">
<pre><code class="language-js">{
  "design": "Design",
  "architecture": "Architecture",
  "photography": "Photography",
  "3d": "3D",
  "web": "Web"
}</code></pre>
</figure>
<p>The following structure is also supported:</p>
<figure class="code">
<pre><code class="language-js">[
  {
    "key": "design",
    "value": "Design"
  },
  {
    "key": "architecture",
    "value": "Architecture"
  }
]</code></pre>
</figure>
<p>You can be much more specific with the endpoint though and describe which kind of data to fetch and what to convert to text and value - pretty much as with the option queries.</p>
<h3 id="options-via-api__a-simple-company-list-example"><a href="#options-via-api__a-simple-company-list-example" tabindex="-1">A simple company list example</a></h3>
<p>Let's assume that our JSON endpoint returns the following JSON:</p>
<figure class="code">
<pre><code class="language-js">{
  "Companies": [
    {"name": "Apple"},
    {"name": "Intel"},
    {"name": "Microsoft"}
  ]
}</code></pre>
</figure>
<p>As you can see, the format doesn't follow our expected option format at all. We first need to go down to the companies property and then somehow convert each company object into text and value for the options.</p>
<p>This can be done with our template language:</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: api
    url: https://example.com/companies.json
    query: Companies
    text: "{{ item.name }}"
    value: "{{ item.name.slug }}"</code></pre>
</figure>
<p>With the <code class="type">query</code> attribute we can define where to start in the JSON document. This can even go down nested structures or sort entries:</p>
<figure class="code">
<pre><code class="language-yaml">query: Companies.sortBy("name", "desc")</code></pre>
</figure>
<p>The JSON document is turned into a Kirby structure and thus can be queried and manipulated just like any other data within Kirby.</p>
<p>Afterwards the text and value setting can be modified by defining the template for each item.</p>
<p>Again, each item is being converted to a Kirby object and every property of the object is a typical Kirby field with all the available field methods. We can go pretty wild with this, if we want. Let's just assume we have a little bit more data for each company …</p>
<figure class="code">
<pre><code class="language-js">{
  "Companies": [
    {
      "name": "Apple",
      "products": [
        {"name": "MacBook"},
        {"name": "iPhone"},
        {"name": "iPad"}
      ]
    },
    {
      "name": "Intel",
      "products": [
        {"name": "Intel Core something"}
      ]
    },
    {
      "name": "Microsoft",
      "products": [
        {"name": "Windows"},
        {"name": "Hololens"},
        {"name": "Xbox"}
      ]
    }
  ]
}</code></pre>
</figure>
<figure class="code">
<pre><code class="language-yaml">value: "{{ item.name.slug.upper }}"
text: "{{ item.name }} - Products: {{ item.products.count }}"</code></pre>
</figure>
<p>This would produce the following PHP array of options:</p>
<figure class="code">
<pre><code class="language-php">[
  value: 'APPLE',
  text: 'Apple - Products: 3'
],
[
  value: 'INTEL',
  text: 'Intel - Products: 1'
],
[
  value: 'MICROSOFT',
  text: 'Microsoft - Products: 3'
]</code></pre>
</figure>
<h3 id="options-via-api__dynamic-api-urls"><a href="#options-via-api__dynamic-api-urls" tabindex="-1">Dynamic API URLs</a></h3>
<p>Instead of hard-coding an absolute URL into your blueprint, it's often better to have more control over the API URL. Especially when you are working with different environments (ie. local, staging, production)</p>
<p>The URL option of the API type can also be modified by using the string template language:</p>
<figure class="code">
<pre><code class="language-yaml">category:
  label: Category
  type: tags
  options:
    type: api
    url: "{{ site.url }}/my-api/companies.json"
    query: Companies
    text: "{{ item.name }}"
    value: "{{ item.name.slug }}"</code></pre>
</figure>
<p>With this simple addition, the API URL will always refer to the main URL of the site. You can also access the configuration instead to get even more flexibility:</p>
<figure class="code">
<pre><code class="language-yaml">url: "{{ kirby.option('optionApiUrl') }}/companies.json"</code></pre>
</figure>
<div class="box box--alert">
<div class="box-icon iconbox bg-black color-white"><svg xmlns="http://www.w3.org/2000/svg" data-type="alert" viewBox="0 0 24 24" aria-hidden="true"><path d="M12.865 3.00017L22.3912 19.5002C22.6674 19.9785 22.5035 20.5901 22.0252 20.8662C21.8732 20.954 21.7008 21.0002 21.5252 21.0002H2.47266C1.92037 21.0002 1.47266 20.5525 1.47266 20.0002C1.47266 19.8246 1.51886 19.6522 1.60663 19.5002L11.1329 3.00017C11.4091 2.52187 12.0206 2.358 12.4989 2.63414C12.651 2.72191 12.7772 2.84815 12.865 3.00017ZM4.20471 19.0002H19.7932L11.9989 5.50017L4.20471 19.0002ZM10.9989 16.0002H12.9989V18.0002H10.9989V16.0002ZM10.9989 9.00017H12.9989V14.0002H10.9989V9.00017Z"/></svg></div>
<div class="box-text">
	<p>Make sure to only use values inside the string template for the URL that can be fully trusted. If attackers with malicious intent have control over the data source for the URL, they can perform a server-side request forgery (SSRF) attack that could leak internal data from files on the server or from other servers in the same network that should normally be protected by a firewall.</p></div>
</div>
<h2 id="translating-options"><a href="#translating-options" tabindex="-1">Translating options</a></h2>
<p>You can provide different translations of your options' text that are shown depending on the user's Panel language:</p>
<figure class="code">
<pre><code class="language-yaml">options:
  summer:
    en: Summer
    de: Sommer
  autumn:
    en: Fall
    de: Herbst
  winter:
    en: Winter
    de: Winter
  spring:
    en: spring
    de: Frühling</code></pre>
</figure>
<p>If you prefer to use <a href="https://getkirby.com/docs/guide/languages/custom-language-variables">custom language variables</a>, you can do that via the <code class="type">*</code> key:</p>
<figure class="code">
<pre><code class="language-yaml">options:
  summer:
    *: season.summer
  autumn:
    *: season.autumn
  winter:
    *: season.winter
  spring:
    *: season.spring</code></pre>
</figure>

## Limit input to options

Using the `accept` property, you can limit tag input to the query options:

```yaml
tags:
    label: Tags
    type: tags
    accept: options
    options: query
    query:
       fetch: site.tags.split
```

## Tags field layout

Tags can also be shown as a list using the `layout` property:

```yaml
fields:
  tags:
    label: Tags
    type: tags
    layout: list
```

<figure class="image"><a data-lightbox href="https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=598&amp;height=231&amp;enlarge=0"><img alt="" class="rounded" height="231" loading="lazy" sizes="auto" src="https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=598&amp;height=231&amp;enlarge=0" srcset="https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=462&amp;height=178&amp;enlarge=0 462w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=598&amp;height=231&amp;enlarge=0 690w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=598&amp;height=231&amp;enlarge=0 924w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=598&amp;height=231&amp;enlarge=0 1380w, https://assets.getkirby.com/media/pages/docs/reference/panel/fields/tags/698260e42c-1717846665/tags-list-layout.png?width=598&amp;height=231&amp;enlarge=0 1848w" width="598"></a></figure>

## How to use in templates/snippets

A tags field stores all selected values in a comma separated list (`value1, value2, value3`) You can split this list with the [`split()`](/docs/reference/templates/field-methods/split) method in your templates and then work with the result:

```php
<ul>
  <?php foreach ($page->tags()->split() as $category): ?>
  <li><?= $category ?></li>
  <?php endforeach ?>
</ul>
```

If you use another separator than the default comma, you have to pass this separator to the `split()` method, for example when using `|` as separator:

```php
<ul>
  <?php foreach ($page->tags()->split('|') as $category): ?>
  <li><?= $category ?></li>
  <?php endforeach ?>
</ul>
```

See [`$field->split()`](/docs/reference/templates/field-methods/split) for more information.