Skip to content

Learn how to use style a feature layer.

Style a feature layer using API key authentication

A feature layer is a dataset in a feature service hosted in an ArcGIS portal. Each feature layer contains features with a single geometry type (point, line, or polygon), and a set of attributes. A feature layer can be styled in MapLibre GL JS with a layer connected to a GeoJSON source.

In this tutorial, you use symbol, line, and fill layers to style three hosted feature layers using the MapLibre ArcGIS plugin.

Prerequisites

You need an ArcGIS Location Platform or ArcGIS Online account.

Steps

Get the starter app

Select a type of authentication and follow the steps to create a new app.

You can choose one of the following to create a new CodePen:

  • Option 1: Complete the Display a map tutorial; or,
  • Option 2: Start from the Display a map tutorial .

Set up authentication

Create a new API key credential with the correct privileges to get an access token.

  1. Go to the Create an API key tutorial to follow the steps to get an access token with these privilege(s):
    • Privileges
      • Location services > Basemaps
    • Item access
      • Note: If you are using your own custom data layer for this tutorial, you need to grant the API key credentials access to the layer item. Learn more in Item access privileges.

Set developer credentials

Use the API key or OAuth developer credentials so your application can access ArcGIS services.

  1. Update the accessToken variable to use your API key.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71  /* Use for API key authentication */  const accessToken = "YOUR_ACCESS_TOKEN"; 
    Expand

Load a hiker icon

To use an image as an icon, you must first load the image into the map style using map.loadImage and map.addImage.

  1. Call map.loadImage with 'https://static.arcgis.com/images/Symbols/NPS/npsPictograph_0231b.png' as the first parameter. The second parameter is a callback taking an error and the image.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  const map = new maplibregl.Map({  container: "map", // the id of the div element  zoom: 12, // starting zoom  center: [-118.805, 34.027] // starting location [longitude, latitude]  });  const basemapStyle = maplibreArcGIS.BasemapStyle.applyStyle(map, {  style: "arcgis/outdoor",  token: accessToken  });   map.loadImage("https://static.arcgis.com/images/Symbols/NPS/npsPictograph_0231b.png").then((resp) => {   }); 
    Expand
  2. Call map.addImage to define hiker-icon as the provided image.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.loadImage("https://static.arcgis.com/images/Symbols/NPS/npsPictograph_0231b.png").then((resp) => {   map.addImage("hiker-icon", resp.data);   }); 
    Expand

Style and display trailheads with a hiker icon and labels

To display an icon of a hiker for each trailhead, along with a label, you can use a single symbol layer. Properties for the label start with text- and properties for the icon start with icon-.

  1. Create an asynchronous function called showTrailheads. Inside, use the plugin to create a new FeatureLayer object that references the Trailheads feature service, then add the sources to the map.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.loadImage("https://static.arcgis.com/images/Symbols/NPS/npsPictograph_0231b.png").then((resp) => {   map.addImage("hiker-icon", resp.data);   });   async function showTrailheads() {   const trailheads = await maplibreArcGIS.FeatureLayer.fromUrl("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trailheads/FeatureServer/0");  trailheads.addSourcesTo(map);   });  } 
    Expand
  2. Add a symbol layer for the hiker icons. Use icon-image to reference the icon you loaded.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  async function showTrailheads() {   const trailheads = await maplibreArcGIS.FeatureLayer.fromUrl("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trailheads/FeatureServer/0");  trailheads.addSourcesTo(map);   map.addLayer({  ...trailheads.layer,  type: "symbol",  layout: {  "icon-image": "hiker-icon",  "icon-size": 0.3,  "icon-allow-overlap": true,   }   });  } 
    Expand
  3. Add additional label properties. Set a text-anchor of bottom, with a text-offset of [0, -2] to position the label above the icon. Use the TRL_NAME attribute as the text field.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.addLayer({  ...trailheads.layer,  type: "symbol",  layout: {  "icon-image": "hiker-icon",  "icon-size": 0.3,  "icon-allow-overlap": true,   "text-font": ["Arial Italic"],  "text-field": ["get", "TRL_NAME"],  "text-size": 12,  "text-anchor": "bottom",  "text-offset": [0, -2]   },  paint: {  "text-color": "white",  "text-halo-color": "seagreen",  "text-halo-width": 2   } 
    Expand

Add a load event handler

It is important to wait for the MapLibre GL JS map to finish loading before adding any layers.

  1. Add an event handler to the map load event. Inside, call the showTrailheads function.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  },  paint: {  "text-color": "white",  "text-halo-color": "seagreen",  "text-halo-width": 2   }   });  }   map.on("load", async () => {   await showTrailheads();   }); 
    Expand

Style and display all trails

  1. Create an asynchronous function called showTrails. Inside, use the plugin to create a new FeatureLayer object that references Trails feature service, then add the sources to the map.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  },  paint: {  "text-color": "white",  "text-halo-color": "seagreen",  "text-halo-width": 2   }   });  }   async function showTrails() {   const trails = await maplibreArcGIS.FeatureLayer.fromUrl("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trails/FeatureServer/0");  trails.addSourcesTo(map);  
    Expand
  2. Add a line layer with id trails-line to display the trails source.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  async function showTrails() {   const trails = await maplibreArcGIS.FeatureLayer.fromUrl("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trails/FeatureServer/0");  trails.addSourcesTo(map);   map.addLayer({  ...trails.layer,  paint: {  "line-color": "hsl(291, 44%, 60%)",   "line-opacity": 0.75  }  });  
    Expand
  3. To show trails that allow bikes with a dashed line, create a new layer that sits above the other layer. You can reuse the existing source with a different id of biketrails-line. Use a filter to only show trails where USE_BIKE has a value of YES. Set line-dasharray to [1, 2] to create short dashes.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.addLayer({  ...trails.layer,  paint: {  "line-color": "hsl(291, 44%, 60%)",   "line-width": ["interpolate", ["linear"], ["get", "ELEV_GAIN"], 0, 3, 2300, 7],   "line-opacity": 0.75  }  });   map.addLayer({  ...trails.layer,  id: "bike-trails",  filter: ["==", ["get", "USE_BIKE"], "Yes"],  paint: {  "line-dasharray": [1, 2],  "line-width": 2,  "line-color": "hsl(300, 100%, 78.4%)"  }  });   } 
    Expand

Style trail width by elevation gain

To make the width of the trail line reflect the elevation gain, you can use a MapLibre GL expression as the value of the line-width. You use the ['interpolate'] and ['get'] expressions to achieve this.

  1. Update the trails-line layer definition that calculates line-width from the ELEV_GAIN attribute.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.addLayer({  ...trails.layer,  paint: {  "line-color": "hsl(291, 44%, 60%)",   "line-width": ["interpolate", ["linear"], ["get", "ELEV_GAIN"], 0, 3, 2300, 7],   "line-opacity": 0.75  }  }); 
    Expand

Display park areas with different colors

You can use a different style for each unique attribute value in a feature layer. Use a ['match', ...] expression to display park polygons with different colors depending on the TYPE field.

  1. Create an asynchronous function called showParks. Inside, use the plugin to create a new FeatureLayer object that references the Parks feature service, then add the sources to the map.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.addLayer({  ...trails.layer,  id: "bike-trails",  filter: ["==", ["get", "USE_BIKE"], "Yes"],  paint: {  "line-dasharray": [1, 2],  "line-width": 2,  "line-color": "hsl(300, 100%, 78.4%)"  }  });   }   async function showParks() {   const parks = await maplibreArcGIS.FeatureLayer.fromUrl("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Parks_and_Open_Space/FeatureServer/0");  parks.addSourcesTo(map);   } 
    Expand
  2. Add a fill layer with id parks-fill and source parks. For the fill color, use a match expression for different TYPE values.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  async function showParks() {   const parks = await maplibreArcGIS.FeatureLayer.fromUrl("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Parks_and_Open_Space/FeatureServer/0");  parks.addSourcesTo(map);   map.addLayer({  ...parks.layer, // The layer property contains a preformatted maplibre layer, including a layer ID and source.  paint: { // Override default paint properties provided by parks.layer  "fill-color": [  "match", ["get", "TYPE"],  "Natural Areas", "#9E559C",  "Regional Open Space", "#A7C636",  "Local Park", "#149ECE",  "Regional Recreation Park", "#ED5151",  "black"  ],  "fill-opacity": 0.2  }  });   } 
    Expand
  3. Back inside the load event handler, call the showParks functions. Place it before the other functions so that the parks layer is placed under the trails and trailheads.

    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165  map.on("load", async () => {   await showParks();   await showTrails();   await showTrailheads();   }); 
    Expand

Run the app

Run the app.

You should now see trailheads with icons and labels, trails whose width varies by elevation, dashed lines for trails that allow bikes, and parks of different colors.

What's next?

Learn how to use additional location services in these tutorials:

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.