> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/mapbox-tiling-service/llms.txt)

# Vector Recipe

**Current version:** `1`

A **vector recipe** is a JSON document containing configuration options that tell [Mapbox Tiling Service](https://docs.mapbox.com/api/maps/mapbox-tiling-service/) (MTS) how to turn tileset source data into vector tiles.

Recipes must be constructed according to the rules in this recipe reference.

## Recipe top-level fields

A recipe is a JSON object that must contain the following top-level fields:

| Required fields | Description | Data type |
| --- | --- | --- |
| [**version**](#recipe-version) | The version of the Mapbox Tiling Service recipe reference that the recipe uses. | `Integer` |
| [**layers**](#layers) | The names of the layers and their configuration options. | `Object<String, LayerObject>` |

The recipe can contain the following top-level optional fields:

| Optional fields | Description | Data type |
| --- | --- | --- |
| [**fillzoom**](#fillzoom) | The zoom level from which to generate [overzoomed](https://docs.mapbox.com/help/glossary/overzoom/) tiles, if there are no tiles at the maximum zoom level. | `Integer` |
| [**incremental**](#incremental) | Whether the tileset supports incremental updates. | `Boolean` |

```json
{
  "version": 1,
  "fillzoom": 7,
  "layers": { layername: LayerObject, ... },
  "incremental" : true
}
```

## Recipe version

The recipe `version` indicates the version of the Mapbox Tiling Service recipe reference that the recipe uses. The current version is `1`. Using any `version` other than the current version or a previous version will result in an error.

## Fillzoom

Fillzoom defines a level from which to generate [overzoomed](https://docs.mapbox.com/help/glossary/overzoom/) tiles. Normally, MTS generates overzoomed tiles from tiles at the maximum [zoom level](#zoom-levels). If you specify `fillzoom` and MTS cannot find tiles at the maximum zoom level, it generates overzoomed tiles from the `fillzoom` level.

For example, in a recipe with a maximum zoom level `10` and fillzoom level `7` specified, a request for a `z11` tile will use `z10` parent tiles to generate the new, overzoomed `z11` tile. If there are no tiles at `z10`, a request for a `z11` tile will use the `z7` tiles instead.

> **Note (warning): Note**
> 
> If a feature is subject to a zoom filter in the recipe and has a `maxzoom` attribute in the tileset source determining the highest zoom level at which the feature can appear, it will not appear at zoom levels higher than the `maxzoom`. But, if `fillzoom` is specified in the recipe, the feature may still appear at higher zoom levels if the tile it belongs to contains no other features. This is a rare but expected case. For an example of using a maximum zoom filter, see the [Feature filters section](#feature-filters).

## Incremental

The optional `incremental` boolean property defines if the tileset supports incremental updates. The default value is `false`. Incremental updates enable updating and re-processing a specific part of an existing tileset instead of re-processing the entire tileset. Incremental updates are particularly beneficial for large datasets where only small portions change. For example, if your tileset source contains 1,000 points and only 1 point changed recently, you can use incremental updates to update and re-process only that 1 updated point. Without incremental updates, MTS will re-process and re-tile all 1,000 points in the tileset, even the points that did not change.

## Layers

The recipe `layers` object contains key-value pairs associating the names of tile layers with their configuration. For each key-value pair, the key is the name of the layer, and the value is a [`LayerObject`](#layer-configuration) containing the configuration and options for the layer.

Each layer must have a name (for example "trees", "parks", and "paths" in the example below). This is the unique identifier for the layer of data in your final tileset. In any tileset, each layer name must be unique. The layer name must be a string with only underscores (`_`) and alphanumeric characters.

The corresponding `LayerObject` for each layer name describes how that vector tile layer should be created. It specifies where to retrieve source data, the precision of the data through zoom levels, and how to transform features and render them into tiles.

### Layer example

The simplest example [tileset recipe](https://docs.mapbox.com/help/glossary/tileset-recipe/) includes a layer name, a tileset source ID, a minimum zoom value, and a maximum zoom.

```json
{
  "version": 1,
  "layers": {
    "trees": {
      "source": "mapbox://tileset-source/{username}/trees-data",
      "minzoom": 4,
      "maxzoom": 8
    }
  }
}
```

### Multilayer recipes

Recipes can also contain definitions for multiple layers. This format can be used to create [multilayer tilesets](https://docs.mapbox.com/help/troubleshooting/multilayer-tilesets/). Recipes can have a maximum of 20 layers defined.

```json
{
  "version": 1,
  "layers": {
    "trees": { ... },
    "parks": { ... },
    "paths": { ... }
  }
}
```

Although you can refer to multiple tilesets from the same style, a single multilayer tileset can contain more layers and can be accessed more quickly over the network than a composite of multiple single-layer tilesets.

## Layer configuration

These are the required top-level fields for each layer:

| Required fields | Description | Data type |
| --- | --- | --- |
| [**source**](#tileset-source) | The source data to use for this layer. For more information about this field, see the [Tileset source section](#tileset-source). | `String` |
| [**minzoom**](#zoom-levels) | The lowest zoom level for the tileset. For more information about this field, see the [Zoom levels section](#zoom-levels). | `Integer` |
| [**maxzoom**](#zoom-levels) | The highest zoom level for the tileset. For more information about this field, see the [Zoom levels section](#zoom-levels). | `Integer` |

### Tileset source

The features that make up each layer come from a [tileset source](https://docs.mapbox.com/help/glossary/tileset-source/), which is a collection of geographic data stored as [line-delimited GeoJSON](https://docs.mapbox.com/mapbox-tiling-service/vector/supported-file-formats#line-delimited-geojson) on Mapbox.com. Tileset sources can be created using MTS's [Create a tileset source endpoint](https://docs.mapbox.com/api/maps/mapbox-tiling-service/#create-a-tileset-source).

### Zoom levels

Each tileset layer contains tiles at one or more zoom levels that will be visible at different times as the user zooms in and out of the map.

The `minzoom` and `maxzoom` configurations control the zoom levels at which your data will be tiled. They are required for a recipe. These values must be integers. The `minzoom` must be less than or equal to `maxzoom`, and both must be between the values of `0` and `16`.

> **Note (warning): Note**
> 
> `maxzoom` does not limit the zoom level your maps can be viewed at, as long as your layers all have the same `maxzoom`. Your map will still zoom past the max zoom in your recipe because of [overzooming](https://docs.mapbox.com/help/glossary/overzoom/). If you do have a different `maxzoom` in different layers, only the layers with the highest `maxzoom` will be overzoomed.

The minimum and maximum zoom levels you choose directly impact the time it takes MTS to finish processing your data. Each additional zoom level increases the amount of data in the resulting tileset exponentially. So it's important to first consider the use cases your map is trying to solve for when choosing your `minzoom` and `maxzoom` zoom levels so that you don't incur any performance costs unnecessarily.

The following table will help you decide the appropriate `minzoom` and `maxzoom` based on your data precision:

| Zoom level | Precision in feet | Precision in meters | Use Case | Example |
| --- | --- | --- | --- | --- |
| 0 | 32000 ft | 10000 m |  | 
![zoom level 0](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z0.3bdfbc2.480.png)

 |
| 1 | 16000 ft | 5000 m |  | 

![zoom level 1](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z1.9f5cbe0.480.png)

 |
| 2 | 8000 ft | 2500 m |  | 

![zoom level 2](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z2.346f2cc.480.png)

 |
| 3 | 4000 ft | 1250 m |  | 

![zoom level 3](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z3.324385a.480.png)

 |
| 4 | 2000 ft | 600 m |  | 

![zoom level 4](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z4.e4375a6.480.png)

 |
| 5 | 1000 ft | 300 m |  | 

![zoom level 5](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z5.93eb9c6.480.png)

 |
| 6 | 500 ft | 150 m |  | 

![zoom level 6](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z6.acd0695.480.png)

 |
| 7 | 250 ft | 80 m |  | 

![zoom level 7](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z7.c8ea814.480.png)

 |
| 8 | 125 ft | 40 m |  | 

![zoom level 8](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z8.31d4893.480.png)

 |
| 9 | 64 ft | 20 m |  | 

![zoom level 9](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z9.189ac28.480.png)

 |
| 10 | 32 ft | 10 m | low-resolution administrative boundaries (for example, countries and state boundaries) | 

![zoom level 10](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z10.e38f8f2.480.png)

 |
| 11 | 16 ft | 5 m |  | 

![zoom level 11](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z11.4af0ff3.480.png)

 |
| 12 | 8 ft | 2 m |  | 

![zoom level 12](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z12.307f5ba.480.png)

 |
| 13 | 4 ft | 1 m |  | 

![zoom level 13](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z13.7fd3e56.480.png)

 |
| 14 | 2 ft | 0.5 m | high-resolution administrative boundaries (such as neighborhoods, census tracts, and towns), national park polygon data, national land use, and land cover polygon data | 

![zoom level 14](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z14.72c175b.480.png)

 |
| 15 | 1 ft | 0.25 m |  | 

![zoom level 15](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z15.4d4d4b7.480.png)

 |
| 16 | 0.5 ft | 0.125 m | building/parcel polygon data, road polygon or line data, points of interest (such as hotels, restaurants, and venues) | 

![zoom level 16](https://docs.mapbox.com/mapbox-tiling-service/assets/ideal-img/z16.5be597d.480.png)

 |

Zoom levels greater than `16`, which are applicable for use-cases like lane-level navigation maps or high-definition indoor maps, are not possible by default. If you have a use case that requires a zoom level greater than `16`, [reach out to Mapbox Sales](https://www.mapbox.com/contact/sales?page=/contact) and let us know.

### Additional layer options

You can further refine the resulting tileset with the following optional fields:

| Optional fields | Description | Data type |
| --- | --- | --- |
| [**features**](#feature-configuration) | Specifies the output on a *per feature* basis. For more information about this field, see the [Feature configuration section](#feature-configuration). | `Object` |
| [**tiles**](#tile-configuration) | Specifies the output on a *per tile* basis. For more information about this field, see the [Tile configuration section](#tile-configuration). | `Object` |

### Layer configuration summary

The following describes every available field for a `LayerObject` and the JSON type for that field.

```json
{
  "source": String,
  "minzoom": Integer,
  "maxzoom": Integer,
  "features": {
    "id": Expression,
    "bbox": [ Number, Number, Number, Number ],
    "attributes": {
      "zoom_element": Array<String>,
      "set": Object<String, Expression>,
      "allowed_output": Array<String>
    },
    "filter": Expression<Boolean>,
    "simplification": Expression<Number>,
    "simplification": {
      "distance": Expression<Number>,
      "outward_only": Expression<Boolean>
    }
  },
  "tiles": {
    "bbox": [ Number, Number, Number, Number ],
    "extent": Expression<Number>,
    "buffer_size": Expression<Number>,
    "limit": [
      [ String, Expression<Boolean>, Integer, String ], ...
    ],
    "union": [
      {
        "where": Expression<Boolean>,
        "group_by": Array<String>,
        "aggregate": Object<String, String>,
        "maintain_direction": Boolean,
        "simplification": Expression<Number>,
        "simplification": {
          "distance": Expression<Number>,
          "outward_only": Expression<Boolean>
        }
      }, ...
    ],
    "filter": Expression<Boolean>,
    "attributes": {
      "set": Object<String, Expression>
    },
    "order": String,
    "remove_filled": Expression<Boolean>,
    "id": Expression,
    "layer_size": Integer
  }
}
```

## Expressions

When writing rules in a recipe document, you can use "expressions" to base them on the properties in your data. Example rules that you might specify in a recipe document could include:

-   Tile uploaded tornado data from zooms 5 through 16.
-   Only tile the most populated cities within a dataset at zooms less than 7, then from zooms 8 through 16, only tile the top 20 most populated cities or towns per tile.
-   Only tile the top 5 flood-affected regions by area from the uploaded dataset until zoom 10, at zoom levels greater than 10, only show flooded regions with an area greater than 8 pixels.

The expressions used to control recipe features are from the expression language described in the [Expressions reference](#expressions-reference) section, and are closely related to the expressions used in [Mapbox GL styles](https://docs.mapbox.com/style-spec/reference/expressions/#expression-reference).

Plain numbers, strings, `true`, `false`, and `null` are all valid expressions, so you don't need to learn all the details of the expression language to start using MTS.

## Feature configuration

The `features` configuration object is used to describe how features are individually processed into vector tiles. This field contains the following elements, which are applied in the order indicated in the list below:

| Optional fields | Description | Data type |
| --- | --- | --- |
| [**id**](#id-expression) | Sets the ID of each feature. For more information about this field, see the [ID expression section](#id-expression). | `Expression` |
| [**bbox**](#bounding-box) | Clips each feature to the specified [bounding box](https://docs.mapbox.com/help/glossary/bounding-box/). For more information about this field, see the [Bounding box section](#bounding-box). | `Array<Number>` |
| [**attributes**](#feature-attributes) | Adds, removes, and modifies feature attributes. For more information about this field, see the [Feature attributes section](#feature-attributes). | `FeaturesAttributesObject` |
| [**filter**](#feature-filters) | Removes features that do not match a pattern. For more information about this field, see the [Feature filters section](#feature-filters). | `Expression<Boolean>` |
| [**simplification**](#feature-simplification) | Controls simplification of the feature geometry. For more information about this field, see the [Feature simplification section](#feature-simplification). | `Expression<Number>` or `SimplificationObject` |

### ID expression

Each feature that is tiled by MTS must have a numeric or string ID that uniquely identifies it, which by default comes from the `id` field of the GeoJSON Feature object in the source. If no ID is present for a feature in the GeoJSON source, or if the provided ID is not a string or number, an ID will be randomly assigned by MTS.

If you want to use a value from a feature attribute (or some other calculation) for your feature's ID, you can use an expression to retrieve that attribute. For example, if you have an attribute called `source_identifier`, you can use it for the feature ID by specifying:

```json
{
  "features": {
    "id": [ "get", "source_identifier" ]
  }
}
```

If you do not want your features to have an ID in the output tiles, or if you want to make additional changes to the ID, you can do this later in the recipe, in the Tiles section.

Read more about feature IDs and their custom operators in the ["Identification" section](#identification).

### Bounding box

If you want to clip your features to a bounding box instead of creating tiles for the full geographic extent of your source data, you can specify the bounding box with `bbox`. It must contain an array of four numbers in this order: the minimum longitude, minimum latitude, maximum longitude, and maximum latitude. For example, if you wanted to clip all your features to the bounds of the state of Wyoming, you could specify:

```json
{
  "features": {
    "bbox": [ -111, 41, -104, 45 ]
  }
}
```

> **Related content (playground): [Location Helper](https://labs.mapbox.com/location-helper)**
> 
> To experiment with bounding box values for your code, try our *Location Helper*.

### Feature attributes

The `attributes` configuration allows for manipulation of attribute data, generation of new attributes, and removal of attributes. The available options in the `FeaturesAttributesObject` are described below, and performed in the order they are listed:

| Optional field | Description | Data type |
| --- | --- | --- |
| [**zoom_element**](#zoom-element) | A list of attributes whose values vary by zoom level. For more information about this option, see the [Zoom element section](#zoom-element). | `Array<String>` |
| [**set**](#set-attributes) | Adds feature attributes or changes the value of attributes. For more information about this option, see the [Set attributes section](#set-attributes). | `Object<String, Expression>` |
| [**allowed_output**](#allowed-output) | A list of attributes that will appear in the final tileset. For more information about this option, see the [Allowed output section](#allowed-output). | `Array<String>` |

#### Zoom element

You may want certain attributes to vary by zoom level. For example, you might want to provide abbreviated street names at low zoom levels and the full names at high zoom levels. You can specify which attributes are provided per zoom level in the source data by providing a `zoom_element` array in the `attributes` object.

For each attribute listed in the `zoom_element`, the final output attribute at zoom level `N` will be the `Nth` element in the array in the source data. If no `zoom_element` is defined, no attributes are altered. If the zoom level is greater than or equal to the number of elements in the array, the last element is used.

For example, the following recipe would specify that `name` should be treated as a `zoom_element` attribute:

```json
{
  "features": {
    "attributes": {
      "zoom_element": [ "name" ]
    }
  }
}
```

The `name` attribute in individual GeoJSON features would then be expected to contain an array of possible `name` values by zoom level, like this:

```json
{
  "type": "Feature",
  "geometry": { ... },
  "properties": { "name": [ null, null, "Main", "Main St.", "Main Street" ] }
}
```

Using the attributes from the GeoJSON above, this feature would have no `name` attribute at zoom levels `0` and `1`, and would have "Main" at zoom level `2`, "Main St." at zoom level `3`, and "Main Street" at zoom levels `4` and above.

#### Set attributes

You can add attributes or change the values of attributes by evaluating expressions in a `set` specification.

The `set` object contains key-value pairs where each key is the name of an attribute and each value is an expression that should be evaluated to provide the new value for that attribute.

Consider the following attributes for a single GeoJSON feature:

```json
{
  "type": "Feature",
  "geometry": { ... },
  "properties": { "area": 12345, "population": 123, "name": "Smallville" }
}
```

If you wanted to add a new attribute, `density`, that is calculated from the `population` per `area`, and also to change the value of the `name` attribute to convert it to all capitals, you could use this recipe:

```json
{
  "features": {
    "attributes": {
      "set": {
        "density": [ "/", [ "get", "population" ], [ "get", "area" ] ],
        "name": [ "upcase", [ "get", "name" ] ]
      }
    }
  }
}
```

If you specify multiple `set` operations, the order in which they will happen is not defined.

#### Allowed output

If an `allowed_output` array is provided, only the attributes specified in the array will be saved to the tileset. This does not prevent the attributes that are not listed in the `allowed_output` from being used in filter expressions and other steps throughout the publish job; the other attributes are removed in the final step of processing.

In this example, features in the resulting tileset will only have two attributes, `name_en` and `name_es`:

```json
{
  "features": {
    "attributes": {
      "allowed_output": [ "name_en", "name_es" ]
    }
  }
}
```

### Feature filters

The filter configuration is a single expression that results in a `true` or `false` evaluation. It is common to determine this result by considering the zoom level being created and an attribute of the feature. For example, if a feature has `minzoom` and `maxzoom` attributes to determine the zoom levels at which the feature appears, you can compare these attributes to the zoom level being created by specifying:

```json
{
  "features": {
    "filter": [ "all",
      [ ">=", [ "zoom" ], [ "get", "minzoom" ] ],
      [ "<=", [ "zoom" ], [ "get", "maxzoom" ] ]
    ]
  }
}
```

Or you could exclude residential roads from zoom levels below 12 by specifying:

```json
{
  "features": {
    "filter": [ "any",
      [ "!=", [ "get", "road" ], "residential" ],
      [ ">=", [ "zoom" ], 12 ]
    ]
  }
}
```

The `filter` specification is the primary way to control which features are allowed into the final tileset on a per-feature basis. If no filter is provided, by default a value of `true` is used for all features.

### Feature simplification

MTS normally simplifies the geometry of features so that at each zoom level they correspond to the resolution of the display screen. If you want to preserve additional detail or if you want to reduce tile size by further simplifying the geometry, you can specify a `simplification` to do that.

The `simplification` expression or value describes the maximum distance that a point can be from the straight line that connects its two neighbors, and still be considered to be effectively on the line and thus safe to remove. Any point with a distance that is larger than the `simplification` value is considered to be away from the line, and must be preserved.

As you increase the `simplification` value, the number of vertices in each feature decreases. The [MTS recipe validator](https://docs.mapbox.com/api/maps/#validate-a-recipe) will reject any `simplification` value that is more than `4096` or less than `0`. If you do not specify a value for `simplification`, MTS will use `4`, the default value.

The resulting shape depends on the original shape of your feature. For instance, if your original feature is circular and you increase `simplification`, the result will look more like a polygon. If your original feature is a curved line and you increase `simplification`, the result will look more like a straight line.

The `simplification` may be a constant number, or it may be an expression that evaluates to a number. A typical use for an [expression](#expressions-reference) for the `simplification` would be to use a minimal simplification at the layer's maximum zoom level, so it can be overzoomed with high precision, but to use a larger simplification at lower zoom levels that will never be magnified far, as in the example below.

Note: Even if the `simplification` is specified as `0`, complex features will receive additional simplification so that they can be rendered correctly by [Mapbox GL](https://docs.mapbox.com/help/glossary/mapbox-gl/). Polygons or MultiPolygons with more than 65535 vertices and LineStrings and polygon rings with more than 6553 points will be simplified to reduce their complexity to these limits.

MTS uses the [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) to simplify features.

This sample recipe uses the normal simplification of `4` at most zoom levels, but reduces the simplification to `1` at the highest zoom level so that overzoomed tiles will look better.

```json
{
  "minzoom": 0,
  "maxzoom": 10,
  "features": {
    "simplification": [ "case",
      [ "==", [ "zoom" ], 10 ], 1,
      4
    ]
  }
}
```

#### Simplification objects

If the `simplification` is a `SimplificationObject`, it can contain the following fields:

| Optional field | Description | Data type |
| --- | --- | --- |
| [**distance**](#simplification-distance) | Controls simplification of the feature geometry. For more information about this field, see the [Simplification distance section](#simplification-distance). | `Expression<Number>` |
| [**outward_only**](#outward-only-simplification) | Places limits on polygon simplification to improve `union` quality. For more information about this field, see the [Outward-only simplification section](#outward-only-simplification). | `Expression<Boolean>` |

This sample recipe simplifies features where `type` is `rowhouse` outward, so that MTS can union each block of row houses into a single block feature instead of leaving them as separate features. MTS then performs normal simplification on the row house blocks after it unions them to further reduce the tile size. MTS simplifies other building types normally and does not union them together.

```json
{
  "features": {
    "simplification": {
      "outward_only": [ "==", [ "get", "type" ], "rowhouse" ]
    }
  },
  "tiles": {
    "union": [
      {
        "where": [ "==", [ "get", "type" ], "rowhouse" ],
        "simplification": {
          "outward_only": false
        }
      }
    ]
  }
}
```

#### Simplification distance

The simplification `distance` is a numeric expression, as described in the [Feature simplification](#feature-simplification) section. It describes the maximum distance that a point can be from the straight line that connects its two neighbors, and still be considered to be effectively on the line and thus safe to remove. Any point with a distance that is larger than the `simplification` value is considered to be away from the line, and must be preserved. Larger values cause the feature geometry to become increasingly coarse.

#### Outward-only simplification

If the `outward_only` expression evaluates to `true`, then `Polygon` and `MultiPolygon` features will be simplified only in ways that slightly increase their area, never in ways that slightly decrease their area, so the borders of adjacent polygons will often overlap slightly instead of sometimes being separated by small gaps. This will increase the quality of polygon unioning if the recipe contains a `union` rule, at the cost of increased tile size because of reduced opportunity for simplification. (See the ["Feature union" section](#feature-union).)

## Tile configuration

The `tiles` object contains the following elements, which are applied in this order as each tile is being assembled from its component features:

| Optional field | Description | Data type |
| --- | --- | --- |
| [**bbox**](#tile-bounding-box) | Clips each feature to the specified [bounding box](https://docs.mapbox.com/help/glossary/bounding-box/). For more information about this field, see the [Tile bounding box section](#tile-bounding-box). | `Array<Number>` |
| [**extent**](#tile-extent) | Sets the precision of a vector tile. For more information about this field, see the [Tile extent section](#tile-extent). | `Expression<Number>` |
| [**buffer_size**](#buffer-size) | Keeps extra data from beyond the edge of each vector tile. For more information about this field, see the [Buffer size section](#buffer-size). | `Expression<Number>` |
| [**limit**](#limit) | Limits the number of features of a specified type that can appear in the tile. For more information about this field, see the [Limit section](#limit). | `Array<LimitArray>` |
| [**union**](#feature-union) | Joins features based on whether a defined specified attribute matches. For more information about this field, see the [Feature union section](#feature-union). | `Array<UnionObject>` |
| [**filter**](#post-union-filter) | Removes features that do not match a specified pattern. For more information about this field, see the [Post-union filter section](#post-union-filter). | `Expression<Boolean>` |
| [**attributes**](#post-union-attributes) | Adds attributes or changes the values of attributes. For more information about this field, see the [Post-union attributes section](#post-union-attributes). | `TilesAttributesObject` |
| [**order**](#order) | Specifies the order of a sequence of features in the final output tile. For more information about this field, see the [Order section](#order). | `String` |
| [**remove_filled**](#remove-filled) | Removes features that cover the entire tile. For more information about this field, see the [Remove filled section](#remove-filled). | `Expression<Boolean>` |
| [**id**](#tiles-id) | Controls the ID of features in the final output tileset. For more information about this field, see the [Tiles ID section](#tiles-id). | `Expression` |
| [**layer_size**](#layer-size) | Sets the maximum allowed size (in KiB) of the tile layer. For more information about this field, see the [Layer size section](#layer-size). | `Integer` |

### Tile bounding box

If you want to generate tiles for only a geographic subset of your data, so you can test a recipe without the time and expense of generating all the tiles, you can specify a bounding box with `bbox`. It must contain an array of four numbers in this order: the minimum longitude, minimum latitude, maximum longitude, and maximum latitude. Only the tiles that intersect this bounding box will be generated.

For example, if you wanted to generate only those tiles that include part of the state of Wyoming, you could specify:

```json
{
  "tiles": {
    "bbox": [ -111, 41, -104, 45 ]
  }
}
```

Note that the `tiles` bounding box is only useful for testing, not for final tilesets. Because the sizes of tiles are different in each zoom level, different areas will come into view as you zoom in and out. To clip features consistently in every zoom level, use the [`features` bounding box](#bounding-box).

The reason to use the `tiles` bounding box instead of the `features` bounding box for testing is that with the `tiles` bounding box, exactly the same set of features appear in each tile as in the full tileset, so you your `union` and `limit` rules will have the same set of features to work with as in the final tiles. With the `features` bounding box, some of the features will already have been removed from consideration before the `union` and `limit` happen, so you may see different results from these than with the full tileset.

### Tile extent

You can change the geometric precision within each vector tile by setting the `extent`.

The `extent` is a precision control for vector tiles, and by default is `4096`. In most situations, changing this value is not recommended. If you set this value, it must be a power of `2` between `256` and `8192`. You may also set this number using an expression.

### Buffer size

Sets the size of the buffer that will be created around the edges of your vector tiles. `buffer_size` is a percentage of the size of a tile. The default buffer size is `0.5`. Note that the value of this attribute cannot be larger than `100`. An expression can also be used in `buffer_size`, but must still return a number between 0 and 100. Buffers are particularly helpful for label point layers to avoid cutting labels off at tile boundaries.

### Limit

A limitation rule that reduces the number of features for the specified type to a specific number, choosing the lowest or highest-numbered features according to some attribute.

The `limit` field must be an array of limitation rules, each of which is evaluated in sequence to potentially limit the total number of features to be included in the final tile. There are two ways in which data is limited using the `limit` field, `where` and `where_in_distance`.

The `where` style limits the total number of features in the tile and its buffer. Only the first `number` of features that match a `filter-expression` are kept and the rest are dropped from the tile. The sorting of these is based on `attribute` which is the key to a feature's attribute and either sorted `lowest` to highest or `highest` to lowest.

The format for a `where` style `limit` are the following syntaxes:

-   `[ "lowest_where", filter-expression, number, attribute ]`
-   `[ "highest_where", filter-expression, number, attribute ]`

The `where_in_distance` is the second style of `limit` and subdivides a tile into a grid of regions. Within each region only one feature is kept from the features selected by the `filter-expression` for the `limit`. The number of regions contained within a single tile is controlled by `region_count`. This should always be a value equal to 4 to a power in size (in other words, 1, 4, 16, 64, 256, 1024, 4096, or 16384). If the `region_count` is not equal to one of these numbers it will be rounded to the nearest one of these numbers in processing. The feature that is selected in each region is the one that has the highest or lowest value for the attribute named in the `attribute` field.

The format for a `where_in_distance` style `limit` are the following syntaxes:

-   `[ "lowest_where_in_distance", filter-expression, region_count , attribute ]`
-   `[ "highest_where_in_distance", filter-expression, region_count , attribute ]`

The only valid values for `region_count` are `1`, `4`, `16`, `64`, `256`, `1024`, `4096`, or `16384`.

These `where_in_distance` `limit` forms also cause the tile buffer for the limited features to be rounded down to a multiple of the buffer size corresponding to the `region_count`:

| region_count | associated buffer size |
| --- | --- |
| 1 | 100 |
| 4 | 50 |
| 16 | 25 |
| 64 | 12.5 |
| 256 | 6.25 |
| 1024 | 3.125 |
| 4096 | 1.5625 |
| 16384 | 0.78125 |

### Feature union

You can union features together if a specified set of their attributes match. The simplest case unions all features that have exactly the same attributes:

```json
"tiles": { "union": [ { } ] }
```

The `union` specification object `UnionObject` can contain an expression to union only features that match the specified expression as well as having matching attributes:

| Optional field | Description | Data type |
| --- | --- | --- |
| [**where**](#union-where) | Selects which features are considered for this `union`. | `Expression<Boolean>` |
| [**group_by**](#union-group_by) | Unions features where all the specified attributes are the same in each feature. | `Array<String>` |
| [**cluster**](#union-cluster) | Union features by grouping nearby features into representative points. **Only works for Point features** | `Boolean` |
| [**region_count**](#union-region_count) | The number of evenly-spaced regions contained within a single tile to group features into. Must be a power of 4 between 1 and 16384 (one of 1, 4, 16, 64, 256, 1024, 4096, or 16384). **Requires `cluster` value to be `true`** | `Number` |
| [**aggregate**](#union-aggregate) | Specifies how to handle other attributes that are not specified in `group_by` when features are unioned. | `Object<String, String>` |
| [**maintain_direction**](#union-maintain_direction) | Permits LineStrings to be reversed if it helps to connect them. | `Boolean` |
| [**simplification**](#union-simplification) | Specifies how features will be resimplified after unioning, as described in the [Feature simplification section](#feature-simplification). | `Number` or `SimplificationObject` |

#### Union `where`

You can specify which features a `union` rule applies to by providing a `where` expression that selects a subset of features. For example, if your layer contains some building footprints that you want to union if they have the same `height`, and some parks that you want to union if they have the same `maintainer`, you could do this:

```json
"tiles": {
  "union": [
    {
      "where": [ "==", [ "get", "type" ], "building" ],
      "group_by": [ "height" ]
    },
    {
      "where": [ "==", [ "get", "type" ], "park" ],
      "group_by": [ "maintainer" ]
    }
  ]
}
```

#### Union `group_by`

Within a set of features that are union candidates, you can specify which of them should become part of the same union by listing the attributes that must be the same in all features of the union. For example, if you want to union all buildings that have the same `height` and `construction_type` together, you could specify:

```json
"tiles": {
  "union": [
    {
      "group_by": [ "height", "construction_type" ]
    }
  ]
}
```

All attributes that are not part of the `group_by` and that do not have an `aggregate` rule specified will be removed at the end of unioning.

If you do not specify a `group_by`, then all feature attributes must be the same for features to be unioned together.

If you specify an empty `group_by` list, then all features will be unioned together regardless of their attributes.

#### Union `cluster`

When all the features are points, you can specify whether to cluster the data. Clustering creates a representative point out of the features in the same tile that are to be grouped. The coordinate of the representative point is the middle point between the features in a cluster.

Defaults to: `cluster: false`

#### Union `region_count`

When using clustered point featured with `cluster: true`, you can adjust the number of clusters created by specifying the number of evenly-spaced regions to divide a single tile into when clustering point features. Each region will result in a representative point feature, whose coordinate is influenced by only the features inside that region. The number of regions must be a value equal to a power of 4 between 1 and 16384 (in other words: 1, 4, 16, 64, 256, 1024, 4096, or 16384).

Defaults to `region_count: 1`

#### Union `aggregate`

You can specify which attributes MTS should keep that are not part of a union's `group_by` and how it should handle them by specifying `aggregate` rules for them.

Each key of the object is the name of an attribute. Each value is a string listed below that indicates how the named attribute should be aggregated from the source features. Attributes that are not specified in either `group_by` or `aggregate` will be removed.

These strings are the available aggregation methods:

-   `sum`: add numbers
-   `product`: multiply numbers
-   `min`: choose the lowest number
-   `max`: choose the highest number
-   `mean`: take the average of numbers
-   `comma`: concatenate with a comma
-   `concat`: concatenate the attribute data without a delimiter
-   `arbitrary`: take the attribute from one of the source features
-   `arbitrary-non-null`: take the attribute from one of the source features where the attribute is not `null`

#### Union maintain_direction

Use `maintain_direction: false` to make more compact unions of `LineString`s for which directionality doesn't matter. This reverses some of the `LineString`s if that helps to connect them.

Defaults to `maintain_direction: true`

#### Union simplification

For polygon unions, it is often useful to specify `outward_only: true` in the main `simplification` section, so the source features will slightly overlap and be merged into a single geometry. Then you can specify `outward_only: false` in the `union`'s post-unioning `simplification` section, so additional simplification will be applied to the polygon after unioning has already taken their overlap into account.

#### Union example

For example, you could specify the following to union only features where `highway=motorway`, and keep the average of their `speed_limit` attributes:

```json
"tiles": {
  "union": [
    {
      "where": [ "==", [ "get", "highway" ], "motorway" ],
      "maintain_direction": true,
      "aggregate": { "speed_limit": "mean" }
    }
  ]
}
```

This example uses `maintain_direction: true` (the default) because `motorway` roads are generally mapped as pairs of roadways whose direction indicates their one-way direction.

For a more elaborate `union` example, see the [Census Blocks recipe example](https://docs.mapbox.com/mapbox-tiling-service/examples/census-blocks/).

> **Note: Using union with zoom based properties**
> 
> You can use union on `zoom-element` properties. If you are using union with a `zoom-element` property, the union will occur after the feature has been assigned the value based on the zoom level.

### Post-union filter

You can further limit the set of features in a tile after unioning by specifying a `filter` rule.

For instance, if your `union` rule was unioning street segments together, summing their `length` attribute, you could use `filter` here to remove the unioned streets whose combined `length` still did not reach a minimum visible threshold, as in this example:

```json
"tiles": {
  "union": [
    {
      "aggregate": { "length": "sum" }
    }
  ],
  "filter": [ ">=", [ "get", "length" ], 200 ]
}
```

### Post-union attributes

You can add or change feature attributes after unioning by specifying an `attributes.set` rule.

For instance, if your `union` rule was unioning census blocks together, summing their `population` and `area` attributes, you could calculate the `density` of each unioned feature afterward, as in this example:

```json
"tiles": {
  "union": [
    {
      "group_by": [ "tract" ]
      "aggregate": { "population": "sum", "area": sum }
    }
  ],
  "attributes": {
    "set": {
      "density": [ "/", [ "get", "population" ], [ "get", area ] ]
    }
  }
}
```

### Order

You can order the sequence of features in the final output tile by a specified attribute. The attribute values must be comparable (all strings or all numbers).

This example orders the features by the `sequence` attribute:

```json
"tiles": { "order": "sequence" }
```

Renderers, including Mapbox GL, draw or place features in the order they appear in the tile. `LineString` and `Polygon` features that are drawn early in the sequence may be covered by overlapping features drawn later in the sequence. Conversely, labels that are placed early in the sequence will prevent labels of features later in the sequence from being placed nearby.

You can follow these general guidelines:

-   The most important **LineStrings** and **Polygons** should have the *highest* values for their `order` attribute, so they are not covered by overlapping features.
-   The most important **labels** should have the *lowest* values for their `order` attribute, so they are placed first.

Read more about how label visibility is affected by the order of features in the [Optimize map label placement](https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#order-of-features-inside-layers) guide.

If no `order` attribute is specified, the features will appear in an unpredictable order.

### Remove filled

During tile creation it is possible for polygons to completely cover a tile and its surrounding buffer area. These are called "filled features". If all features within a tile are filled features, it may be useful in some tilesets not to create these tiles at all and to expect that clients will look to lower zoom levels instead.

The common situation that this applies to are large areas of water, where if the water polygon covers the entire tile, it is more efficient to overzoom the water from a lower zoom level rather than creating duplicate high-zoom tiles for an entire ocean.

To do this, you can use the `remove_filled` recipe option. The syntax utilizes a `filter-expression` to allow control over what features exactly should be removed.

```json
"tiles": {
  "remove_filled": expression
}
```

No tile will be created if:

-   All features within the tile are considered filled features
-   All features are matched by the filter-expression provided in `remove_filled`

The following example removes all tiles that contain only filled features:

```json
"tiles": {
  "remove_filled": true
}
```

The following example removes all tiles that contain only filled features after zoom level 5:

```json
"tiles": {
  "remove_filled": [">", [ "zoom" ], 5]
}
```

### Tiles ID

You can change the final output ID of features by specifying an `id` expression. For instance, you might want all the features that have the same road name to have the same ID, so that if one of them is selected, they are all selected, even though the source features had to have different IDs because in earlier stages of processing, IDs are required to be distinct. So you could set a new ID at the end of processing, like this:

```json
"tiles": {
  "id": [ "hash", [ "get", "road-name" ] ]
}
```

Within the `id` expression you can refer to the ID assigned earlier with `[ "id" ]`, so you can make conditional expressions that leave the ID unchanged in some circumstances or use it in calculations, like this example, which replaces the ID if there is a `special_id` attribute, or otherwise multiplies the existing ID by 2:

```json
"tiles": {
  "id": [ "case",
    [ "has", "special_id" ], [ "get", "special_id" ],
    [ "*", [ "id" ], 2 ]
  ]
}
```

If you do not want your features to have an ID in the final output tiles, set the ID to `null`:

```json
"tiles": {
  "id": null
}
```

### Allowed output

In the `features.attributes` section of the recipe, you can specify a list of attributes in `allowed_output` that will appear in the final tile. The removal of other attributes happens at this point, at the end of `tiles` processing. As a result, in `limit`, `union`, or `id` processing you can continue to refer to attributes that will not appear in the final output features because they do not appear in `allowed_output`.

### Layer size

MTS limits the size (in bytes) of tile layers for the sake of network speed and rendering time. When the size of a layer's data in a single tile exceeds the `layer_size`, the features that came last in the specified `order` will be dropped from that tile to stay within the `layer_size` limit.

The normal limit is `1250`, for 1250 kilobytes. You can further limit the layer size by specifying a lower `layer_size` number, or raise the limit to as high as `2500` by specifying a higher `layer_size` number.

If you are receiving warnings about dropped features due to exceeding `layer_size`, you may want to consider a few options:

-   Reduce the number of total attributes per feature. If you don't plan on using all your properties for styling or in your map, you can drop specific features using the [attributes](#feature-attributes) option.
-   Reduce the size of attributes in your features. Some property types, especially strings, can take up a lot of unused space in a tile. Consider dropping long form text from your data.
-   Filter your features at low zooms (z0 - z6) based on specific attributes. You can use the [feature filter](#feature-filters) or the [tile limit](#limit) options to remove features at specific zoom levels, but keep the most important based on specific attributes.
-   Lastly, you can consider spreading your data across different layers or tilesets to stay within the layer size limit. Be aware this can have cost implications by creating more than one layer or tileset.

If you want to exclude specific features, it is generally better to use a `limit` specification instead. Setting `layer_size` is more useful for holding yourself to a performance budget than as a general feature limiting mechanism.

## Identification

You can configure the identification of features in both "Features" and "Tiles". Both follow the same patterns and have the same options. In general, `features.id` is used for defining or generating *input* IDs, while `tiles.id` is used for defining or generating *output* IDs in the final vector tiles.

```json
{
  "features": {
    "id": [ <Expression> ]
  },
  "tiles": {
    "id": [ <Expression> ]
  }
}
```

All IDs encoded in the final vector tiles will be integers, as defined by the vector tile specification. If you provide a string value to `tiles.id` the value will be automatically hashed to an integer between `0` and `2^53 - 1`.

### Order of operations

The following describes the order of operations and type conversions applied when using the `features.id` and `tiles.id` options. Remember the [Mapbox Vector Tile specification](https://docs.mapbox.com/vector-tiles/specification/) requires all IDs to be encoded as positive integers between `0` and `2^53 - 1`.

-   In general ID value in either `features.id` or `tiles.id` will be converted to an Integer or Float if possible and will otherwise return a string or Null value.
-   If `features.id` **is not defined**, the top-level `id` of the GeoJSON feature will be used.
    -   If there is no top-level `id` from the GeoJSON feature, a `random` integer will be generated.
    -   If the value is set to `null`, no feature ID is set.
-   If `features.id` **is defined**, the result of the expression will be converted to an Integer or Float if possible and will otherwise return a string.
    -   If the expression returns a null value, a random integer will be generated. If you are trying to prevent feature IDs in your final tile, set `tiles.id` to null.
-   If `tiles.id` **is not defined**, the resulting value from `features.id` will be used.
-   If `tiles.id` **is defined**, the value of the resulting expression replaces whatever value was defined in `features.id`.
-   If the result of the `tiles.id` expression is a...
    -   **string**, it is hashed to an integer.
    -   **string representation of an integer**, it will be converted to the absolute value of that number and fit within `0` to `2^53 - 1`.
    -   **empty string**, it will be treated as null and not encoded.
    -   **negative number**, the absolute value will be used.
    -   **floating point number**, it will be rounded down and fit within `0` to `2^53 - 1`.
    -   **boolean**, it will be treated as null and not encoded.

If you don't want to encode any IDs in your final vector tile you can pass `null` to `tiles.id` as your value instead of an expression array.

### Duplicate IDs

While ID collision for unidentified features is unlikely, unique IDs aren't guaranteed since feature parsing occurs in a distributed environment. To guarantee unique identifiers, bring your own IDs. Duplicate feature IDs can cause unexpected behavior in tilesets and should be avoided.

## Expressions reference

Instead of applying a static value in a recipe operation, some recipe operations allow you to set values dynamically on a *per feature* or *per zoom level* basis. This dynamic value setting is possible with **Mapbox expressions**.

An expression defines a formula for computing the value of a property using the *operators* described below. The set of expression operators provided by MTS includes:

-   **Mathematical operators** for performing arithmetic and other operations on numeric values
-   **Logical operators** for manipulating boolean values and making conditional decisions
-   **String operators** for manipulating strings
-   **Data operators** that provide access to the properties of features in your tileset source

Expressions are represented as JSON arrays. The first element of an expression array is a string naming the expression operator, for example [`"*"`](#*) or [`"case"`](#case). Elements that follow (if any) are the *arguments* to the expression. Each argument is either a literal value (a string, number, boolean, or `null`), or another expression array.

```json
[expression_name, argument_0, argument_1, ...]
```

Expressions allow you to dynamically set values in the recipe and optimize your output tileset. The [LayerObject](#layer-configuration) houses all the available recipe options and denotes which fields accept expressions by listing a `Expression` value.

### Evaluating expressions

A *data expression* is any expression that accesses data in your tileset source — that is, any expression that uses one of the data operators: [`get`](#get), [`has`](#has), [`id`](#id), [`geometry-type`](#geometry-type), or [`properties`](#properties). The syntax of the expression and its arguments are checked when a recipe is validated, while the evaluation of the expression checked when a tileset is being processed. All expressions will be evaluated against a specific feature or zoom level, depending on logic of the recipe configurations.

### Types

Mapbox expressions run on a series of primitive types.

-   **null**: A null value.
-   **number**: An integer or floating point number.
-   **string**: Any series of UTF-8 characters.
-   **boolean**: True or False.
-   **array**: An array of values, optionally may specify the type of values in the array, for example, `array<boolean>`.
-   **object**: A map of key and value pairs.

It is also possible to see a **value** type in the documentation and error responses. This is a variable type that will not be a known primitive type until the expression is evaluated.

### Errors

Errors in your expression can occur in two different ways, during recipe validation or during the evaluation of that recipe within MTS. If an expression in a recipe has a syntax error, such as an invalid input type, the recipe will fail validation and will not be allowed within MTS. If an expression experiences an error during the evaluation of an expression, the recipe operation determines the proper error handling and the processing a job or feature may continue. For example, if a `filter` recipe operation experiences an evaluation error, the recipe expression will error and `filter` will consider the feature to be `false`. The default behavior of an expression evaluation error varies on the specific recipe operation.

### Type expressions

The expressions in this section are for testing for and converting between different data types like strings, numbers, and boolean values.

Often, such tests and conversions are unnecessary, but they are helpful if a feature's data type is ambiguous or inconsistent. For example, you could use `to-number` to make sure that values like `"1.5"` (instead of `1.5`) are treated as numeric values. Another helpful use case is to convert values to their commonly represented types like using `to-boolean` to convert a `1` to `true`.

#### [array](#types-array)

```text
["array", value]: array
```

```text
["array", type: "string" | "number" | "boolean", value]: array<type>
```

```text
["array",
    type: "string" | "number" | "boolean",
    N: number (literal),
    value
]: array<type, N>
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "countries": {
      "source": "mapbox://tileset-source/{username}/{id}",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "filter": [
          "all",
          [">=", ["length", ["array", ["get", "LANGUAGES"]]], 1]
        ],
        "attributes": {
          "set": {"num_official_languages": ["length", ["get", "LANGUAGES"]]}
        }
      }
    }
  }
}
```

#### [boolean](#types-boolean)

```text
["boolean", value]: boolean
```

```text
["boolean", value, fallback: value, fallback: value, ...]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 12,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          ["==", ["boolean", ["get", "commercial"], false], true]
        ]
      }
    }
  }
}
```

#### [literal](#types-literal)

```text
["literal", [...] (JSON array literal)]: array<T, N>
```

```text
["literal", {...} (JSON object literal)]: object
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/roads",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "road_type": [
              "get",
              ["get", "MTFCC"],
              [
                "literal",
                {
                  "S1100": "Primary Road",
                  "S1200": "Secondary Road",
                  "S1400": "Local Street",
                  "S1720": "Stairway",
                  "S1730": "Alley",
                  "S1829": "Bike Path or Trail"
                }
              ]
            ]
          }
        }
      }
    }
  }
}
```

#### [number](#types-number)

```text
["number", value]: number
```

```text
["number", value, fallback: value, fallback: value, ...]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 12,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          ["<", ["number", ["get", "construction-date"], 2020], 1900]
        ]
      }
    }
  }
}
```

#### [object](#types-object)

```text
["object", value]: object
```

```text
["object", value, fallback: value, fallback: value, ...]: object
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "set": {
          "attributes": {
            "project-lead": [
              "get",
              "project-lead",
              ["object", ["get", "employees"], {}]
            ]
          }
        }
      }
    }
  }
}
```

#### [string](#types-string)

```text
["string", value]: string
```

```text
["string", value, fallback: value, fallback: value, ...]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 12,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/{id}",
      "features": {
        "filter": ["any", ["==", ["string", ["get", "state"]], "New York"]]
      }
    }
  }
}
```

#### [to-boolean](#types-to-boolean)

```text
["to-boolean", value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/natural-earth-roads",
      "features": {
        "attributes": {"set": {"tollRequired": ["to-boolean", ["get", "toll"]]}}
      }
    }
  }
}
```

#### [to-number](#types-to-number)

```text
["to-number", value, fallback: value, fallback: value, ...]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "buildings": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_age": [
              "-",
              2021,
              ["to-number", ["get", "construction-date"], 2021]
            ]
          }
        }
      }
    }
  }
}
```

#### [to-string](#types-to-string)

```text
["to-string", value]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "buildings": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/populated-places",
      "features": {
        "attributes": {
          "set": {
            "elevationDigitID": [
              "concat",
              ["length", ["to-string", ["get", "ELEVATION"]]],
              ["get", "NAME"]
            ]
          }
        }
      }
    }
  }
}
```

#### [typeof](#types-typeof)

```text
["typeof", value]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "placeName": {
      "source": "mapbox://tileset-source/{username}/populated-places",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "attributes": {
          "set": {
            "placeName": [
              "case",
              ["==", ["typeof", ["get", "NAME"]], "number"],
              "INVALIDNUM",
              ["==", ["typeof", ["get", "NAME"]], "boolean"],
              "INVALIDBOOL",
              ["get", "NAME"]
            ]
          }
        }
      }
    }
  }
}
```

### Feature data

The `feature`, [`geometry-type`](#geometry-type), [`properties`](#properties), and [`id`](#id) operators are used to retrieve information about the properties contained in the [GeoJSON](https://docs.mapbox.com/help/glossary/geojson/) specification. Feature expressions are often used as inputs to other expressions, enabling you to build chains of logic around your data. Common use cases for feature expressions include:

-   Applying transformations to specific geometry types.
-   Filtering out features that have (or lack) specific properties.

#### [geometry-type](#geometry-type)

```text
["geometry-type"]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "simplification": [
          "case",
          ["==", ["geometry-type"], "LineString"],
          0,
          10
        ]
      }
    }
  }
}
```

#### [id](#id)

```text
["id"]: value
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "poi": {
      "source": "mapbox://{username}/places-of-interest",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "filter": [
          "case",
          ["<=", ["zoom"], 4],
          ["==", ["%", ["id"], 2], 0],
          true
        ]
      }
    }
  }
}
```

#### [properties](#properties)

```text
["properties"]: object
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://{username}/roads",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "attributes": {"set": {"old_attributes": ["to-string", ["properties"]]}}
      },
      "tiles": {
        "attributes": {
          "set": {"streetname": ["concat", ["get", "streetname"], " street"]}
        }
      }
    }
  }
}
```

### Lookup

The [`at`](#at), [`get`](#get), [`has`](#has), [`in`](#in), [`index-of`](#index-of), [`length`](#length), and [`slice`](#slice) operators are for retrieving information about feature properties contained in the GeoJSON specification. They access or test for properties, arrays, or strings, and the results can be used as inputs for other expressions. For example, the [Census blocks - unioning census geographies](https://docs.mapbox.com/mapbox-tiling-service/examples/census-blocks/) example uses `get` to retrieve existing properties and concatenates them into a single attribute based on zoom level. Another example is the [Roads - unioning features based on shared attributes](https://docs.mapbox.com/mapbox-tiling-service/examples/union-road-features-based-on-common-attributes/) example, which uses `get` to retrieve properties and filter them by comparing against other values.

#### [at](#at)

```text
["at", number, array]: ItemType
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "bakeries": {
      "source": "mapbox://tileset-source/{username}/dc-bakeries",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "cheapest-menu-item": ["at", 0, ["get", "sorted-item-prices"]]
          }
        }
      }
    }
  }
}
```

#### [get](#get)

```text
["get", string]: value
```

```text
["get", string, object]: value
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/roads",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "road_type": [
              "get",
              ["get", "MTFCC"],
              [
                "literal",
                {
                  "S1100": "Primary Road",
                  "S1200": "Secondary Road",
                  "S1400": "Local Street",
                  "S1720": "Stairway",
                  "S1730": "Alley",
                  "S1829": "Bike Path or Trail"
                }
              ]
            ]
          }
        }
      }
    }
  }
}
```

#### [has](#has)

```text
["has", string]: boolean
```

```text
["has", string, object]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/road-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": ["has", "quality"]}
    }
  }
}
```

#### [in](#in)

```text
["in", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "countries": {
      "source": "mapbox://tileset-source/{username}/{id}",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "attributes": {
          "set": {"spanish_speaking": ["in", "Spanish", ["get", "LANGUAGES"]]}
        }
      }
    }
  }
}
```

#### [index-of](#index-of)

```text
["index-of", value, value]: number
```

```text
["index-of", value, value, number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "streets": {
      "minzoom": 0,
      "maxzoom": 13,
      "source": "mapbox://sample-source/tiger-roads",
      "features": {
        "attributes": {
          "set": {
            "FULLNAME": [
              "let",
              "name",
              ["coalesce", ["get", "FULLNAME"], ""],
              [
                "let",
                "index_of_st",
                ["index-of", " St", ["var", "name"]],
                "length_minus_3",
                ["-", ["length", ["var", "name"]], 3],
                [
                  "case",
                  [
                    "all",
                    [">=", ["var", "index_of_st"], 0],
                    ["==", ["var", "index_of_st"], ["var", "length_minus_3"]]
                  ],
                  [
                    "concat",
                    ["slice", ["var", "name"], 0, ["var", "index_of_st"]],
                    " Street"
                  ],
                  ["get", "FULLNAME"]
                ]
              ]
            ]
          }
        }
      }
    }
  }
}
```

#### [length](#length)

```text
["length", string | array | value]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "countries": {
      "source": "mapbox://tileset-source/{username}/{id}",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "attributes": {
          "set": {"num_official_languages": ["length", ["get", "LANGUAGES"]]}
        }
      }
    }
  }
}
```

#### [slice](#slice)

```text
["slice",
    input: InputType (array or string),
    startIndex: number
]: OutputType (ItemType or string)
```

```text
["slice",
    input: InputType (array or string),
    startIndex: number,
    endIndex: number
]: OutputType (ItemType or string)
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "bakeries": {
      "source": "mapbox://tileset-source/{username}/dc-bakeries",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {"abbreviated-title": ["slice", ["get", "title"], 0, 3]}
        }
      }
    }
  }
}
```

### Decision

The expressions in this section can be used to add conditional logic to your recipe. For example, the [`'case'`](#case) operator provides if/then/else logic, and the [`'match'`](#match) operator allows you to map specific values of an input expression to different output expressions.

Decision logic could be used to filter a feature based on its property value compared against the current zoom. Decision logic could also be used to simplify a feature's geometry conditionally, based on a feature property value.

The [Roads - ranking and simplifying linestrings](https://docs.mapbox.com/mapbox-tiling-service/examples/natural-earth-data-roads/) example includes a few examples of decision logic, including the `<=` and `match` operators.

#### [!](#!)

```text
["!", boolean]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "airport-buildings": {
      "source": "mapbox://tileset-source/{username}/airport-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "filter": [
          "!",
          [
            "any",
            ["==", ["get", "type"], "runway"],
            ["==", ["get", "type"], "taxiway"],
            ["==", ["get", "type"], "apron"]
          ]
        ]
      }
    }
  }
}
```

#### [!=](#!=)

```text
["!=", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "trails": {
      "source": "mapbox://tileset-source/{username}/trails-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": ["!=", ["get", "right_of_way"], "private"]}
    }
  }
}
```

#### [<](#<)

```text
["<", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "airport": {
      "source": "mapbox://tileset-source/{username}/airport-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": ["<", ["get", "runways"], 3]}
    }
  }
}
```

#### [<=](#<=)

```text
["<=", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/road-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": ["<=", ["get", "quality"], 10]}
    }
  }
}
```

#### [==](#==)

```text
["==", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "trails": {
      "source": "mapbox://tileset-source/{username}/trails-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": ["==", ["get", "right_of_way"], "public"]}
    }
  }
}
```

#### [>](#>)

```text
[">", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "trip": {
      "source": "mapbox://tileset-source/{username}/trip-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": [">", ["get", "length_m"], 999]}
    }
  }
}
```

#### [>=](#>=)

```text
[">=", value, value]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "trip": {
      "source": "mapbox://tileset-source/{username}/trip-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {"filter": [">=", ["get", "length_m"], 1000]}
    }
  }
}
```

#### [all](#all)

```text
["all", boolean, boolean]: boolean
```

```text
["all", boolean, boolean, ...]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/road-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "filter": [
          "all",
          [">=", ["get", "length_km"], 100],
          ["==", ["get", "type"], "Primary Highway"]
        ]
      }
    }
  }
}
```

#### [any](#any)

```text
["any", boolean, boolean]: boolean
```

```text
["any", boolean, boolean, ...]: boolean
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "surfaces": {
      "source": "mapbox://tileset-source/{username}/airport-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "filter": [
          "any",
          ["==", ["get", "type"], "runway"],
          ["==", ["get", "type"], "taxiway"],
          ["==", ["get", "type"], "apron"]
        ]
      }
    }
  }
}
```

#### [case](#case)

```text
["case",
    condition: boolean, output: OutputType,
    condition: boolean, output: OutputType,
    ...,
    fallback: OutputType
]: OutputType
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "countries": {
      "source": "mapbox://tileset-source/{username}/{id}",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {
        "simplification": 10,
        "attributes": {
          "set": {
            "num_official_languages": [
              "case",
              ["==", ["get", "LANGUAGES"], null],
              0,
              ["==", ["typeof", ["get", "LANGUAGES"]], "string"],
              1,
              ["length", ["get", "LANGUAGES"]]
            ]
          }
        }
      }
    }
  }
}
```

#### [coalesce](#coalesce)

```text
["coalesce", OutputType, OutputType, ...]: OutputType
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "streets": {
      "minzoom": 0,
      "maxzoom": 13,
      "source": "mapbox://sample-source/tiger-roads",
      "features": {
        "attributes": {
          "allowed_output": ["name"],
          "set": {
            "name": [
              "coalesce",
              ["get", "FULLNAME"],
              [
                "concat",
                "ZIP code ",
                ["coalesce", ["get", "ZIPL"], ["get", "ZIPR"], "unknown"]
              ]
            ]
          }
        }
      }
    }
  }
}
```

#### [match](#match)

```text
["match",
    input: InputType (number or string),
    label: InputType | [InputType, InputType, ...], output: OutputType,
    label: InputType | [InputType, InputType, ...], output: OutputType,
    ...,
    fallback: OutputType
]: OutputType
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/road-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "quality": [
              "match",
              ["get", "type"],
              "Ferry Route",
              0,
              ["Major Highway", "Primary Highway"],
              1,
              "Secondary Highway",
              2,
              3
            ]
          }
        }
      }
    }
  }
}
```

### Scales

The expressions in this section provide transformation operations between a numerical input and an output. Scale expressions are most commonly used to generate varying `"simplification"` values by `"zoom"` level, but they can also be used to generate new attributes based on existing properties of the source data.

#### [interpolate](#interpolate)

```text
["interpolate",
    interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2],
    input: number,
    stop_input_1: number, stop_output_1: OutputType,
    ...,
    stop_input_n: number, stop_output_n: OutputType
]: OutputType (number, array<number>)
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 12,
      "features": {
        "simplification": ["interpolate", ["linear"], ["zoom"], 0, 10, 10, 0]
      }
    }
  }
}
```

#### [step](#step)

```text
["step",
    input: number,
    stop_output_0: OutputType,
    stop_input_1: number, stop_output_1: OutputType,
    stop_input_n: number, stop_output_n: OutputType, ...
]: OutputType
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 12,
      "features": {"simplification": ["step", ["zoom"], 10, 3, 2, 7, 1, 10, 0]}
    }
  }
}
```

### Variable binding

The `let` and `var` expressions allow you to calculate and give a name to one or more values that can then be used repeatedly within a single expression.

In most cases, recipes will instead use the `set` recipe field to create a new feature attribute, but because the order of `set` operations is undefined, a `set` expression cannot refer to the output of a different `set` expression. So if an expression needs to refer repeatedly to a previously-calculated value, it must instead use `let` to calculate that value and `var` to refer to the result of the calculation.

See [`let`](#let) below for a more detailed example of how `let` and `var` are used together.

#### [let](#let)

```text
["let",
    string (alphanumeric literal), any, string (alphanumeric literal), any, ...,
    OutputType
]: OutputType
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "cities": {
      "minzoom": 0,
      "maxzoom": 7,
      "source": "mapbox://tileset-source/{username}/populated-places",
      "features": {
        "attributes": {
          "allowed_output": ["NAME", "POP_OTHER"],
          "set": {
            "POP_OTHER": [
              "let",
              "divisor",
              [
                "case",
                ["<", ["zoom"], 1],
                100000,
                ["<", ["zoom"], 2],
                50000,
                ["<", ["zoom"], 3],
                25000,
                ["<", ["zoom"], 4],
                10000,
                ["<", ["zoom"], 5],
                5000,
                ["<", ["zoom"], 6],
                2000,
                1000
              ],
              [
                "case",
                ["has", "POP_OTHER"],
                [
                  "*",
                  ["var", "divisor"],
                  ["round", ["/", ["get", "POP_OTHER"], ["var", "divisor"]]]
                ],
                ["get", "POP_OTHER"]
              ]
            ]
          }
        }
      }
    }
  }
}
```

#### [var](#var)

```text
["var", previously bound variable name]: the type of the bound expression
```

### String

The expressions in this section are for manipulating string values. Supported expressions are [`concat`](#concat), [`downcase`](#downcase), and [`upcase`](#upcase). The [Census blocks - unioning census geographies](https://docs.mapbox.com/mapbox-tiling-service/examples/census-blocks/) example demonstrates the `concat` string expression used in combination with the `case` expression to conditionally concatenate a string to an attribute.

The [`slice`](#slice) expression (in the [Lookup](#lookup) section) can also be used to manipulate strings, although it can also be used for array slicing.

#### [concat](#concat)

```text
["concat", value, value, ...]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "buildings": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_description": [
              "concat",
              ["get", "construction-date"],
              ":",
              [
                "get",
                ["get", "building_group"],
                [
                  "literal",
                  {"R": "residential property", "C": "commercial property"}
                ]
              ]
            ]
          }
        }
      }
    }
  }
}
```

#### [downcase](#downcase)

```text
["downcase", string]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/roads",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {"set": {"lowercase-name": ["downcase", ["get", "name"]]}}
      }
    }
  }
}
```

#### [upcase](#upcase)

```text
["upcase", string]: string
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "streets": {
      "minzoom": 0,
      "maxzoom": 12,
      "source": "mapbox://sample-source/tiger-roads",
      "features": {
        "attributes": {
          "set": {
            "FULLNAME": [
              "case",
              ["==", ["typeof", ["get", "FULLNAME"]], "string"],
              ["upcase", ["get", "FULLNAME"]],
              ["get", "FULLNAME"]
            ]
          }
        }
      }
    }
  }
}
```

### Math

The [`-`](#-), [`*`](#*), [`/`](#/), [`%`](#%25), [`^`](#%5E), [`+`](#+), [`abs`](#abs), [`acos`](#acos), [`asin`](#asin), [`atan`](#atan), [`ceil`](#ceil), [`cos`](#cos), [`e`](#e), [`floor`](#floor), [`hash`](#hash), [`ln`](#ln), [`ln2`](#ln2), [`log10`](#log10), [`log2`](#log2), [`max`](#max), [`min`](#min), [`pi`](#pi), [`random`](#random), [`round`](#round), [`sin`](#sin), [`sqrt`](#sqrt), and [`tan`](#tan) operators are for applying mathematical operations.

#### [-](#-)

```text
["-", number, number]: number
```

```text
["-", number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          [
            ">",
            ["-", ["get", "current-value"], ["get", "value-last-year"]],
            10000
          ]
        ]
      }
    }
  }
}
```

#### [*](#*)

```text
["*", number, number, ...]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          [">", ["*", ["get", "sqft"], ["get", "cost-per-sqft"]], 100000]
        ]
      }
    }
  }
}
```

#### [/](#/)

```text
["/", number, number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {"filter": ["any", [">", ["/", ["get", "capacity"], 12], 10]]}
    }
  }
}
```

#### [%](#%)

```text
["%", number, number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "cities": {
      "minzoom": 0,
      "maxzoom": 5,
      "source": "mapbox://sample-source/populated-places",
      "features": {
        "filter": ["==", 0, ["%", ["random"], ["^", 2, ["-", 5, ["zoom"]]]]]
      }
    }
  }
}
```

#### [^](#^)

```text
["^", number, number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "cities": {
      "minzoom": 0,
      "maxzoom": 5,
      "source": "mapbox://sample-source/populated-places",
      "features": {
        "filter": ["==", 0, ["%", ["random"], ["^", 2, ["-", 5, ["zoom"]]]]]
      }
    }
  }
}
```

#### [+](#+)

```text
["+", number, number, ...]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "buildings": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          [">", ["+", ["get", "altitude"], ["get", "building-height"]], 10000]
        ]
      }
    }
  }
}
```

#### [abs](#abs)

```text
["abs", number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": ["any", ["<", ["abs", ["get", "latitude"]], 23.43652]]
      }
    }
  }
}
```

#### [acos](#acos)

```text
["acos", number]: number
```

#### [asin](#asin)

```text
["asin", number]: number
```

#### [atan](#atan)

```text
["atan", number]: number
```

#### [ceil](#ceil)

```text
["ceil", number]: number
```

#### [cos](#cos)

```text
["cos", number]: number
```

#### [e](#e)

```text
["e"]: number
```

#### [floor](#floor)

```text
["floor", number]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "source": "mapbox://tileset-source/{username}/populated-places",
      "minzoom": 0,
      "maxzoom": 10,
      "features": {"filter": ["all", [">=", ["floor", ["get", "min_zoom"]], 7]]}
    }
  }
}
```

#### [hash](#hash)

```text
["hash", string]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "traffic": {
      "source": "mapbox://{username}/traffic",
      "minzoom": 0,
      "maxzoom": 10,
      "tiles": {
        "id": ["hash", ["concat", ["get", "roadname"], ["get", "hour"]]]
      }
    }
  }
}
```

#### [ln](#ln)

```text
["ln", number]: number
```

#### [ln2](#ln2)

```text
["ln2"]: number
```

#### [log10](#log10)

```text
["log10", number]: number
```

#### [log2](#log2)

```text
["log2", number]: number
```

#### [max](#max)

```text
["max", number, number, ...]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          ["<", ["max", ["get", "year-build"], ["get", "year-sold"]], 2010]
        ]
      }
    }
  }
}
```

#### [min](#min)

```text
["min", number, number, ...]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/counties",
      "features": {
        "filter": [
          "any",
          [
            "<",
            [
              "min",
              ["at", 0, ["get", "population-history"]],
              ["at", 1, ["get", "population-history"]],
              ["at", 2, ["get", "population-history"]],
              ["at", 3, ["get", "population-history"]],
              ["at", 4, ["get", "population-history"]]
            ],
            1000
          ]
        ]
      }
    }
  }
}
```

#### [pi](#pi)

```text
["pi"]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 15,
      "source": "mapbox://tileset-source/{username}/building-data",
      "features": {
        "filter": [
          "any",
          [">", ["*", ["pi"], ["^", ["get", "signal-range"], 2]], 500]
        ]
      }
    }
  }
}
```

#### [random](#random)

```text
["random"]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "cities": {
      "minzoom": 0,
      "maxzoom": 5,
      "source": "mapbox://sample-source/populated-places",
      "features": {
        "filter": ["==", 0, ["%", ["random"], ["^", 2, ["-", 5, ["zoom"]]]]]
      }
    }
  }
}
```

#### [round](#round)

```text
["round", number]: number
```

##### Example

```json
 {
  "version": 1,
  "dessert": {
    "shops": {
      "source": "mapbox://tileset-source/{username}/dessert-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "icecream_pints_per_capita": [
              "round",
              ["/", ["get", "icecream_pints"], ["get", "population"]]
            ]
          }
        }
      }
    }
  }
}
```

#### [sin](#sin)

```text
["sin", number]: number
```

#### [sqrt](#sqrt)

```text
["sqrt", number]: number
```

#### [tan](#tan)

```text
["tan", number]: number
```

### Zoom

The expression in this section is for performing zoom-specific operations with your data. This is often used to simplify or filter data based on the tile's zoom level. For example, you can write an expression include less detailed information at lower zooms and include full detail at higher zooms. This provides a faster and more usable map. The [Roads - ranking and simplifying linestrings](https://docs.mapbox.com/mapbox-tiling-service/examples/natural-earth-data-roads/) example demonstrates this.

#### [zoom](#zoom)

```text
["zoom"]: number
```

##### Example

```json
 {
  "version": 1,
  "layers": {
    "places": {
      "minzoom": 0,
      "maxzoom": 14,
      "source": "mapbox://tileset-source/{username}/{id}",
      "features": {
        "filter": [
          "any",
          [">=", ["zoom"], 12],
          ["!=", ["get", "type"], "residential"]
        ]
      }
    }
  }
}
```

## More resources

-   To see the recipe for an existing tileset created with MTS, go to its [Tileset explorer](https://console.mapbox.com/studio/tilesets/), click on **Job history**, then click on **View recipe**.
-   For a list of example recipes that use some of the expression operators described above, see the [Tileset recipe examples](https://docs.mapbox.com/mapbox-tiling-service/examples/) page.
-   The [Get started using Mapbox Tiling Service and the Tilesets CLI tutorial](https://docs.mapbox.com/help/tutorials/get-started-mts-and-tilesets-cli/) walks you through the process of creating a new tileset, including creating and using a tileset recipe, with the [Tilesets CLI](https://github.com/mapbox/tilesets-cli/).
-   Mapbox Tiling Service has several endpoints that can be used to validate, update, and read a tileset's recipe. Learn more in the [Mapbox Tiling Service](https://docs.mapbox.com/api/maps/mapbox-tiling-service/) documentation.