The field key and the /0 you never chose — reading feature service attributes and sublayer ids the way the service defines them
A developer points a small map app at a public agency service to draw pins and
show the facility name in a popup. The prototype was written against a layer whose field was
name, and it appends /0
to the service URL. Both of those are values the developer typed. Neither is a value the
developer gets to choose.
That is the whole argument of this post. The attribute key your code indexes and the
sublayer id it appends are both properties of the service. A client that does
not resolve them at run time reads undefined where a value should
be, or, over raw REST, gets back an HTTP 200 whose JSON body carries error code 400. Over raw
REST nothing throws in that second case unless your code goes looking for it. The SDK is
louder about a missing layer: it rejects the load, as the measurements below show.
Everything below was measured on October 5, 2026, anonymously, mainly against
two public services: an ArcGIS Online hosted service in Esri’s Living Atlas organization,
and the USGS The National Map structures MapServer, which runs on
ArcGIS Server 11.3. A second Living Atlas service,
ArcGIS_Tapestry_2025_Packed_Circles, covers the multi-layer cases,
and two smaller checks are reported where they come up: a scan of the first 400 Living Atlas
services and an anonymous probe of a private service. The JavaScript results come from @arcgis/core
5.1.26 under Node and from the js.arcgis.com/5.0/
CDN build (5.0.19) in headless Chromium. Several of the behaviors below are
measured and undocumented; where that is the case the text says so, and it means a later
release could change them.
The job where this bites
Run the prototype against the USGS structures service and here is what happens, request by request.
/0?f=jsondoes not fail. It returns a sublayer called Labels whose type isGroup Layer. The hospitals sublayer used here is 14, one of 70 entries numbered 0 to 69 (it is the copy under the Labels group; we come back to that)./0/querydoes fail, quietly. The response is HTTP 200 with{"error":{"code":400,"message":"Invalid or missing input parameters.","details":[]}}in the body. The SDK refuses layer 0 outright:feature-layer-source:unsupported-type,Source type "Group Layer" is not supported.- With the SDK pointed at layer 14,
attributes["name"]isundefined, because in our test the SDK keyed that fieldNAME. Custom popup code that readsattributes["name"]shows a blank line. - With plain
fetchpointed at the same layer, the opposite happens. The layer’s own metadata declaresNAME, but the keys in the query response are lowercasename, so code that copies the metadata name readsundefined.
The pins in that story are the developer’s scenario, not something we rendered. What we
ran were the requests and the reads. Swap in a Living Atlas hosted service whose only layer is
11, Block_Centroids_2020_Located, and a hard-coded
/0 gets HTTP 200 with
"The requested layer (layerId: 0) was not found." in the body.
Meanwhile, new FeatureLayer({ url: serviceRoot }) with no
layerId loads layer 11 without complaint.
Each service answered the question it was asked. The defect this post is about is in the two values the client assumed.
A field has a name and an alias, and only the name is a key
Every field on a feature layer carries two strings. ArcGIS Online’s own help is
precise about which is which: “Changing the display name for a field does not affect
the field name stored in the attribute table. Because it’s a property of the field and not
a field name, the display name can contain spaces and nonalphanumeric characters.”
The display name is the alias. On the Living Atlas layer used here, the name is
County_Name and the alias is County Name;
the name P0010001 carries the alias Total
Population in 2020.
The JavaScript SDK mirrors this on its Field class, with
name and alias as separate properties,
and it notes that for a service-backed layer “you do not need to add fields to the
constructor of a FeatureLayer loaded from a service since they are already defined by the
service.” That last clause is the point: the field list is the service’s, not
yours.
In every query response we measured, the attribute keys were field names
(on the USGS service in a different case; see below), never aliases. On the Living Atlas
layer, where name and alias differ, a full outFields=* read of
layer 11 returned the keys OBJECTID, GEOID,
County_Name, State_Name,
P0010001, Driving10,
Walking10 and AccessTypes. No alias
appeared as a key. The USGS layer cannot settle the question on its own, because its aliases
equal its names. The popup side follows the same split:
FieldInfo.fieldName is documented as “The field name as
defined by the service”, and its label as “A
string containing the field alias.”
Casing is whatever the source carried
There is a common shorthand that ArcGIS Online field names are uppercase. The documentation
does not say that, and the measurements contradict it. The Living Atlas layer is mixed case
(County_Name, GEOID,
P0010001). The USGS structures layer declares uppercase
(NAME, CITY). In our own build, the
uppercase layers are the ones sourced from USGS The National Map, and even those carry a
lowercase phone field. The ArcGIS Online publishing page says field
names must meet the requirements of the database ArcGIS Online stores the data in, and must be
unique; it does not say how case is derived from a shapefile, file geodatabase or CSV. So the
defensible rule is the plain one: the casing is whatever the source carried, and a client
should not assume any.
The request side is forgiving
Where the case mismatch does not hurt you is in the request. On both services we
measured, field names in outFields and in
where were matched case-insensitively:
HOSTED layer 11: outFields=county_name,geoid -> keys ['County_Name','GEOID'] HOSTED layer 11: outFields=COUNTY_NAME,GEOID -> keys ['County_Name','GEOID'] HOSTED layer 11: where=state_name='Ohio' | STATE_NAME='Ohio' | State_Name='ohio' -> 1 row, State_Name 'Ohio' TNM layer 14: outFields=name,city -> keys ['name','city'] TNM layer 14: outFields=NAME,CITY -> keys ['name','city'] (layer fields[] says NAME, CITY) TNM layer 14: where=state='OH' -> 1 row ; where=STATE='oh' -> 0 rows
Two things deserve a second look. First, the output shows that the hosted layer also matched the
string value case-insensitively ('ohio' found
Ohio), and the USGS service did not
('oh' found nothing, with no error). Value comparison is a property
of the backend you are talking to; do not generalize from either one. Second, a field name that
does not exist in any case fails the whole query, and it fails the same quiet way as the missing
layer: HTTP 200, with a body such as
{"error": {"code": 400, "message": "Cannot perform query. Invalid query parameters.", "details": ["'outFields' parameter is invalid"]}}.
None of the field-name tolerance is documented, so treat it as observed behavior, not a
contract.
The response side is where it breaks
The failure is in client code, and it runs in opposite directions depending on which client you are.
Over raw REST, the response keys are not guaranteed to match the
layer’s fields[].name byte for byte. The hosted layer returned
its canonical casing (County_Name) whatever casing was requested.
The USGS service declared NAME, CITY and
OBJECTID in its layer resource and keyed every query response
name, city and
objectid, whatever case was requested, while its own
displayFieldName still read NAME. That
is one ArcGIS Server 11.3 service, not a rule about ArcGIS Server, and no documentation we read
explains it either way. It is enough to show that looking a key up through the metadata is not
sufficient over raw REST. A raw-REST client has to match keys case-insensitively.
Through the JavaScript SDK, in our test with 5.1.26 under Node and with the
5.0.19 CDN build in Chromium, queryFeatures re-keyed
Graphic.attributes to the layer’s
fields names. On USGS layer 14 the wire said
name and the graphic said NAME, so
attrs["name"] was undefined. There was
one exception: with an explicit outFields: ["NAME", "CITY"], the
object-id key the SDK added on its own stayed lowercase, and the keys came back as
objectid, NAME,
CITY. This re-keying is undocumented, it was measured against one
service, and a minor release could change it. The lookup below is documented. Reading
attrs[field.name] after it still leans on the re-keying, and the
objectid exception shows the re-keying can miss, so a full defense
also falls back to the case-insensitive key match shown in the raw-REST section.
The defense in the SDK: ask the layer for the field
The SDK documents a case-insensitive lookup for exactly this. FieldsIndex
exists, in its own words, to “make case-insensitive lookups for a field by its
name”, and its get() method says “The name is
case-insensitive.” FeatureLayer.getField() is documented
the same way. Resolve the field once, then read the attribute by the
name the layer gives back, falling back to a case-insensitive key
match. This is the lookup half, as we executed it in Node against
@arcgis/core 5.1.26; the fallback is the
getAttr helper further down:
import FeatureLayer from "@arcgis/core/layers/FeatureLayer.js";
async function readField(serviceUrl, wantedField) {
const layer = new FeatureLayer({ url: serviceUrl }); // no layerId: SDK picks the service's first layer
await layer.load();
const field = layer.fieldsIndex.get(wantedField); // documented as case-insensitive
if (!field) throw new Error(`No field "${wantedField}" on layer ${layer.layerId}`);
const { features } = await layer.queryFeatures({
where: "1=1", outFields: [field.name], returnGeometry: false, num: 1,
});
const attrs = features[0].attributes;
return { layerId: layer.layerId, key: field.name, label: field.alias, value: attrs[field.name], naive: attrs[wantedField] };
}
console.log(await readField(
"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/Block_Centroids_2020_Located/FeatureServer",
"county_name"));
console.log(await readField(
"https://carto.nationalmap.gov/arcgis/rest/services/structures/MapServer/14",
"name"));
{ layerId: 11, key: 'County_Name', label: 'County Name', value: 'Autauga County', naive: undefined }
{ layerId: 14, key: 'NAME', label: 'NAME', value: 'Temple University Hospital - Northeast Campus', naive: undefined }
The naive column is the bug. The
value column shows the lookup working on both services in one run,
but that read still relies on the re-keying described above, so for the object id, or if a
later release stops re-keying, fall back to the case-insensitive match below. Note what the
first line also shows: the service URL had no sublayer id at all, and the layer that loaded was
11. That sample only loads and queries; we did not draw either layer. The sample also takes
the first layer on one service and a hard-coded 14 on the other, only to keep the field lesson
separate from the layer-id lesson. Layer 14 is the hospitals copy under the Labels
group; 49 is the copy under Features. In production, pass
readField a URL you have validated against the service’s
layers[] (below). We did not test the attributes on graphics
returned by a click on a drawn layer.
Two related facts from the same measurements. A field that does not exist comes back empty
from getField("no_such_field"), so the guard in the sample is where
a typo surfaces. And the object-id key is not reliably OBJECTID: it
came back objectid in the exception above, so do not write
attrs.OBJECTID.
Arcade is already safe here. The Arcade type guide states “The keys of the
dictionary are case insensitive”, and in our run
$feature.county_name and
$feature["COUNTY_NAME"] both returned “Autauga County”.
A field that does not exist in any case throws (“Key not found”) rather than
returning an empty value. What we did not test is a
PopupTemplate whose {fieldName}
substitution uses the wrong case, because that needs a rendered view. So this post makes no
claim about template strings or the SDK’s own popup, only about code that reads
attributes directly. If your app can use the built-in popup
instead of custom attribute reads, test that path yourself; we did not.
The defense over raw REST: match the key, not the string
Without the SDK there is no re-keying to rely on, and no name in the layer metadata you can trust to equal the response key. Match case-insensitively, with an exact match first. This helper follows the same pattern as our own routing code, and is shown exactly as it ran in our raw-REST test against layer 11:
const getAttr = (attrs, name) => {
if (Object.hasOwn(attrs, name)) return attrs[name];
const k = Object.keys(attrs).find((x) => x.toLowerCase() === name.toLowerCase());
return k === undefined ? undefined : attrs[k];
};
Against layer 11 it produced
a["county_name"] = undefined | getAttr(a, "county_name") = Autauga County.
On the USGS layer, the same helper finds lowercase name when your
code asks for the metadata’s NAME.
Sublayer ids are assigned by whoever published the service
The /0 habit comes from a real default. ArcGIS Pro’s
sharing help: “By default, each layer you share is automatically assigned an ID based
on its drawing order in the Contents pane, beginning with 0 for the first layer.” The
same page lets the author override it, and even suggests doing so in a pattern that guarantees
gaps: “You should be intentional when assigning IDs, like sequentially numbering your
layers in multiples of 10 (10, 20, 30, and so on).” So ids need not start at 0 and
need not be contiguous. The page also warns that, unless they are pinned, “service
sublayer IDs can potentially change when the web layer or service is overwritten, causing
disconnects in web applications that reference the service sublayer.”
Pinning is a publisher action. It is a setting on the Map Properties dialog box in ArcGIS Pro, and Esri’s advice is to “manually assign layer IDs prior to publishing if you intend to overwrite the web layer or service in the future.” That guidance is documented for publishing from Pro; we did not find it stated for uploads made directly in ArcGIS Online. And if you consume a service someone else publishes, such as Living Atlas or USGS, you cannot pin anything. You can only discover and validate.
How often is the id not 0? Rarely, and it is worth being honest about that. In a scan of the
first 400 FeatureServer and MapServer services listed in Esri’s Living Atlas
organization, 3 had no layer 0: Block_Centroids_2020_Located (11),
ArcGIS_Tapestry_2025_Packed_Circles (3 and 4) and
Chennai_Solid_Waste_Management (1). The case for discovery is not
that /0 is usually wrong. It is that the id is the service’s
choice, the occasional exception fails silently over raw REST, and the USGS service shows the other trap: a
layer 0 that exists and is the wrong thing to query.
What a missing sublayer looks like on the wire
Ask the Living Atlas service for a layer it does not have:
GET .../Block_Centroids_2020_Located/FeatureServer/0?f=json
HTTP/1.1 200 OK
{"error":{"code":400,"message":"","details":["The requested layer (layerId: 0) was not found."]}}
GET .../Block_Centroids_2020_Located/FeatureServer/0/query?where=1%3D1&outFields=*&f=json
HTTP 200
{"error":{"code":400,"message":"Invalid URL","details":["Invalid URL"]}}
The status is 200. The 400 lives in the JSON body (we found no
documentation of this; it is measured). Code that checks
response.ok sees success, and the
/query form does not even mention the layer. Every raw-REST helper
should parse the body and throw on error, which is what the sample
below does. One more caution: a private service answered our anonymous requests with that same
Invalid URL body at its root, so an anonymous probe cannot
tell “no layer 0” from “not shared with you”. Do not diagnose a private
service that way.
What the SDK does with no layerId
The SDK reference, as we read it against 5.1.26, disagrees with itself here, and the
measurement settles which entry to trust. The layerId entry says that if no id is given, “the
first layer in the service (layerId = 0) is selected.” The
url entry on the same page says “If no layerId is
given, then the first layer in the service will be loaded.” The second matches what
happened:
A url=service, no layerId: LOADED type=feature layerId=11 B url=service, layerId:0: FAILED name=request:server message="The requested layer (layerId: 0) was not found." C url=service/0: FAILED (same message) E Layer.fromArcGISServerUrl(service): LOADED type=feature layerId=11 F Layer.fromArcGISServerUrl(service/0): FAILED (same message) G portalItem, no layerId: LOADED type=feature layerId=11 H portalItem, layerId:0: FAILED (same message) Tapestry [3, 4], url, no layerId: LOADED layerId=3 Tapestry [3, 4], portalItem, no layerId: LOADED layerId=3
Lines A, B, C, E and F ran in Node on 5.1.26; G, H and the Tapestry portal-item line ran in
Chromium on 5.0.19; the Tapestry url line ran in both. So with
no layerId, the SDK loaded “the first layer in the
service”, and it was not 0: 11 on a one-layer service, 3 on the two-layer Tapestry service.
Whether “first” means first listed or lowest id we could not separate, because on
Tapestry layer 3 is both. So do not treat leaving the id off as a shortcut: which layer the SDK
treats as first is the service’s choice, not yours. Pass a
layerId you have validated. Layer.fromArcGISServerUrl() on a
service root is documented to resolve to a GroupLayer when the
service has multiple layers, and on Tapestry it did, holding both sublayers.
Discover the id from layers[], and do it properly
The discovery call is the service resource itself, <service>?f=json,
which the REST reference describes as providing “basic information about the feature
service, including the feature layers and tables that it contains”. Its
layers[] carries each sublayer’s id
and name.
Taking layers[0] from that list repeats the
/0 assumption by another route: it takes whatever comes first, and
on the USGS service layers[0] is the Labels group layer. Matching on name alone
fails too, because sublayer names repeat. The structures service has two
sublayers called Hospitals/Medical Centers: 14 under Labels and 49 under
Features. And on Tapestry, matching on geometry type alone is ambiguous, because both
layers are polygons. This version skips group layers and matches the full group path. It is the
sample as executed in Node, with plain fetch and no SDK:
// Discover a sublayer id from the service description. Never layers[0].
// Match on the full group path because sublayer names need not be unique.
async function findLayer(serviceUrl, { path, geometryType } = {}) {
const r = await fetch(`${serviceUrl}?f=json`);
const svc = await r.json();
if (svc.error) throw new Error(`HTTP ${r.status}, body error ${svc.error.code}: ${svc.error.message}`);
const byId = new Map((svc.layers ?? []).map((l) => [l.id, l]));
const pathOf = (l) => {
const parts = [l.name];
for (let p = byId.get(l.parentLayerId); p; p = byId.get(p.parentLayerId)) parts.unshift(p.name);
return parts.join(" > ");
};
const matches = (svc.layers ?? []).filter((l) =>
l.type !== "Group Layer" && !(l.subLayerIds?.length) && // skip group layers
(!geometryType || l.geometryType === geometryType) &&
(!path || pathOf(l).toLowerCase() === path.toLowerCase()));
if (matches.length !== 1)
throw new Error(`${matches.length} layers match: ${matches.map((l) => `${l.id} = ${pathOf(l)}`).join(" | ")}`);
return `${serviceUrl}/${matches[0].id}`;
}
const TNM = "https://carto.nationalmap.gov/arcgis/rest/services/structures/MapServer";
const TAP = "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/ArcGIS_Tapestry_2025_Packed_Circles/FeatureServer";
const tryIt = async (url, args) => {
try { console.log(JSON.stringify(args), "->", await findLayer(url, args)); }
catch (e) { console.log(JSON.stringify(args), "-> THROWS", e.message); }
};
await tryIt(TNM, { geometryType: "esriGeometryPoint", path: "Hospitals/Medical Centers" });
await tryIt(TNM, { path: "Features > Medical & Emergency Response > Hospitals/Medical Centers" });
await tryIt(TNM, { path: "Labels > Medical & Emergency Response > Hospitals/Medical Centers" });
await tryIt(TAP, { geometryType: "esriGeometryPolygon" });
await tryIt(TAP, { path: "Tapestry_PackedCircles_LifeModes" });
{"geometryType":"esriGeometryPoint","path":"Hospitals/Medical Centers"} -> THROWS 0 layers match:
{"path":"Features > Medical & Emergency Response > Hospitals/Medical Centers"} -> https://carto.nationalmap.gov/arcgis/rest/services/structures/MapServer/49
{"path":"Labels > Medical & Emergency Response > Hospitals/Medical Centers"} -> https://carto.nationalmap.gov/arcgis/rest/services/structures/MapServer/14
{"geometryType":"esriGeometryPolygon"} -> THROWS 2 layers match: 3 = Tapestry_PackedCircles_Segments | 4 = Tapestry_PackedCircles_LifeModes
{"path":"Tapestry_PackedCircles_LifeModes"} -> https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/ArcGIS_Tapestry_2025_Packed_Circles/FeatureServer/4
The two throws are the design working. A bare leaf name is not a full path, so it matches
nothing; an ambiguous geometry type matches two. In both cases the function refuses to guess,
and it says what it found. Be clear about what that buys you: a group path is chosen by the
same publisher as the id, so it is no more permanent. The value is that the app fails loudly
when the service stops looking the way it expects. Where a publisher has pinned ids, keep the
id in configuration and use a check like this one at load to confirm it still points at the
layer you meant. The first version of this function matched on name alone, and it
threw 2 layers match on the hospitals: that failure is why the path match exists. Discovery
costs one ?f=json request per service, which you can cache; the SDK
already fetches the layer resource when it loads, and
fieldsIndex.get runs on the client.
Field labels: the alias is what a reader sees
The field name is the key; the alias is the label. Where a publisher never set aliases, the
label is the raw name. The USGS layer’s aliases equal its field names
(NAME, SOURCE_DATADESC,
ADDRESSBUILDINGNAME), and the SDK sample above reported
label: 'NAME' for it. A descriptive alias is the more legible label.
WCAG 2.2 Success Criterion 2.4.6 states the principle: it “does not require the use of
labels; however, it does require that if labels are present, they must be accurate and
sufficiently clear or descriptive.” Whether a read-only popup row counts as a label
in that criterion’s sense is an interpretation W3C does not settle, so take it as
guidance, not as a conformance finding.
Who can fix it depends on whose service it is. In ArcGIS Online, setting display names
requires that “You must be the owner of the hosted feature layer or table, an
organization administrator, or a member of a shared update group to which the layer is
shared.” A reader of Living Atlas or USGS data is none of those. The client-side
route is open to anyone, though: set PopupTemplate.fieldInfos[].label,
or set FeatureLayer.fieldConfigurations. Check the
FieldConfiguration reference for which layer types support it first;
our test did not check display on a map-service layer. Since 4.34 the SDK’s
getFieldAlias() is described as “now the recommended
approach for obtaining a field’s alias”, and it resolves the field
configuration alias first, then the field’s own alias. On USGS layer 14, after setting
fieldConfigurations = [{ name: "NAME", alias: "Facility name" }],
getFieldAlias("NAME") returned Facility name while
field.alias still read NAME. That test
checked getFieldAlias() only, on a map-service layer, with the
configuration set after the layer loaded; we did not check that a popup honored it. Neither
route changes the service.
Scale, cost and access, briefly
Scale. Both measured layers report a
maxRecordCount of 2000, and the hosted layer also reports a
standardMaxRecordCount of 16000 and a
tileMaxRecordCount of 8000. In our requests
exceededTransferLimit came back true
whenever the page was smaller than the matching set. On the hosted layer, pagination support is
reported under advancedQueryCapabilities, not at the top level. Two
earlier posts on this blog cover what maxRecordCount does to counts, linked below; nothing in this
post changes it.
Cost. Every request in this post was anonymous: reading a public hosted layer needed no account, no key and no token. That says nothing about credits, and we found no sentence in the credits documentation on what a query costs, so we make no claim either way. What is documented is the publisher’s side: hosted feature storage is billed at “2.4 credits per 10 MB stored per month, calculated hourly”, a charge that excludes hosted feature layer views and feature attachments (attachments are charged as file storage instead). It applies to ArcGIS Online hosted layers only; the USGS service runs on ArcGIS Server and involves no ArcGIS Online credits.
Access. A private hosted layer needs an access token, and how you get one matters more than the field-name question this post is about. Esri’s API key page is direct: “API key authentication is the easiest type of authentication to use, but it is not sufficient to protect confidential information”, and for data services it marks API-key access “Supported, but not recommended due to security risks.” For confidential or sensitive data it points to stronger methods, user authentication and app authentication. Which of those fits a browser-only app is a question to settle against Esri’s authentication documentation before you ship; this post does not answer it, and an API key in client code is not the answer. Referrer URLs are recommended “in order to enhance the security of the token”; they enhance it, they do not make it safe to ship a key that unlocks confidential data in client code. Generating API keys also requires a Creator user type or higher plus the Generate API keys and Assign privileges to OAuth 2.0 applications privileges. It is not available on ArcGIS Online Trial or ArcGIS for Personal Use subscriptions, or to the Hub Community Member user type.
If you are not the one writing the code. The field-key failure looks like success: a blank line in custom popup code raises no error. The layer-id failure looks like success to any code that checks only the HTTP status, because a missing sublayer comes back as HTTP 200. An app can pass every demo against the service it was built on and break the first time it is pointed at a service published by someone else, or at one republished from ArcGIS Pro without pinned ids. The fix is a few lines and one request at load. Without it, the first sign of trouble can be a user noticing that some names are missing, with no error that points at the cause.
What our own build does
The case-insensitive half we already practice. Our routing and Places code reads attributes
through the getAttr helper shown above and a string variant of it,
because the layers it reads mix casings. The layer-id half we do not yet practice. One of our
builds hard-codes a private service’s sublayer id in a registry instead of discovering it,
and several of our builds append /FeatureServer/0 to default URLs.
As far as we know those services do have a layer 0, so those URLs work today. They are
undefended, not broken, and this post is the reason they will change. None of our builds uses
fieldsIndex.get or getField yet,
either.
The short version
- Read attributes through the layer’s field metadata:
layer.fieldsIndex.get(name)?.namein the Maps SDK, handling a miss. The SDK’s re-keying is undocumented, so fall back to a case-insensitive key match rather than trusting it. - Over raw REST, match response keys case-insensitively, exact match first. The metadata name may not equal the wire key.
- Validate every sublayer id against the service’s
layers[]at load, skipping group layers and matching the full group path. Never hard-code an id,/0included, that has not passed that check, and never takelayers[0]. - Parse every REST response body and throw on
error. Over raw REST a missing layer or field is HTTP 200 witherror.code400. - If you are the publisher, set readable aliases, and if you will ever overwrite, pin sublayer ids in Pro before the first publish. If you consume, relabel client-side and validate the ids.
References
- Maps SDK for JavaScript — FieldsIndex (the documented case-insensitive field lookup, and get() taking a case-insensitive name)
- Maps SDK for JavaScript — Field (name and alias as separate properties, and that a service-backed layer takes its fields from the service)
- Maps SDK for JavaScript — FeatureLayer (the layerId and url entries on what loads with no id, getField(), and getFieldAlias() with field configurations (read against 5.1.26 and 5.0.19))
- Maps SDK for JavaScript — Layer (Layer.fromArcGISServerUrl() resolving to a GroupLayer for a multi-layer service)
- Maps SDK for JavaScript — FieldInfo (fieldName as the field name defined by the service, and label as the field alias)
- Arcade — types (that dictionary keys, and so feature attribute keys, are case-insensitive)
- ArcGIS REST API — feature service (the service resource that lists the layers and tables it contains, and maxRecordCount)
- ArcGIS REST API — query (feature service layer) (the query operation, outFields and where, and paging)
- ArcGIS Pro — assign layer IDs (default ids by drawing order from 0, custom ids, ids changing on overwrite, and pinning them before publishing)
- ArcGIS Online — describe attribute fields (display name versus field name, and who may set display names)
- ArcGIS Online — publish hosted feature layers (that field names must meet the backing database requirements and be unique)
- ArcGIS Online — understand credits (the feature storage rate and what it excludes)
- ArcGIS developer documentation — API key authentication (why API keys are not sufficient for confidential data, referrer URLs, and who can generate keys)
- W3C — Understanding SC 2.4.6 Headings and Labels (that labels, where present, must be accurate and sufficiently descriptive)
- ATS — rectangle hit testing in Maps SDK 5.1 (what maxRecordCount does to a count you quote back to a user)
- ATS — three ways to narrow a feature layer, and the count you can quote (the paging and count side of reading a feature layer from code)
Pointing an app at services you do not control?
The field key and the sublayer id are the two values that break first when an app meets someone else’s service, and over raw REST neither one throws when it does. This checklist is what our own builds are changing to, and we will run the same check on yours. Book a free intro call and we will look at what your app assumes about the services it reads.
Book a free intro call