| name | arcgis-tables-forms |
| description | Configure FeatureTable widget and FormTemplate with input elements. Use for displaying attribute data in tables, customizing edit forms, and configuring field inputs. |
ArcGIS Tables & Forms
Use this skill for configuring FeatureTable widgets and FormTemplate with various input elements.
FeatureTable
Display feature attributes in an interactive table.
FeatureTable Component
<arcgis-map item-id="YOUR_WEBMAP_ID">
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-map>
<arcgis-feature-table reference-element="arcgis-map"></arcgis-feature-table>
<script type="module">
const map = document.querySelector("arcgis-map");
const table = document.querySelector("arcgis-feature-table");
await map.viewOnReady();
// Set the layer for the table
const layer = map.view.map.layers.find(l => l.type === "feature");
table.layer = layer;
</script>
FeatureTable Widget (Core API)
import FeatureTable from "@arcgis/core/widgets/FeatureTable.js";
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
container: "tableDiv"
});
With Field Configuration
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
container: "tableDiv",
fieldConfigs: [
{
name: "name",
label: "Name",
direction: "asc" // Initial sort
},
{
name: "category",
label: "Category"
},
{
name: "value",
label: "Value",
format: {
digitSeparator: true,
places: 2
}
},
{
name: "date_created",
label: "Created",
format: {
dateFormat: "short-date"
}
}
]
});
FeatureTable Configuration
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
container: "tableDiv",
// Display options
visibleElements: {
header: true,
menu: true,
menuItems: {
clearSelection: true,
refreshData: true,
toggleColumns: true,
selectedRecordsShowAllToggle: true,
selectedRecordsShowSelectedToggle: true,
zoomToSelection: true
},
selectionColumn: true,
columnMenus: true
},
// Table behavior
multiSortEnabled: true,
editingEnabled: true,
highlightEnabled: true,
attachmentsEnabled: true,
relatedRecordsEnabled: true,
// Pagination
pageSize: 50,
// Initial state
filterGeometry: view.extent, // Only show features in view
highlightOnRowSelectEnabled: true
});
Column Templates
import FieldColumnTemplate from "@arcgis/core/widgets/FeatureTable/support/FieldColumnTemplate.js";
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
tableTemplate: {
columnTemplates: [
new FieldColumnTemplate({
fieldName: "name",
label: "Name",
sortable: true,
initialSortPriority: 0,
direction: "asc"
}),
new FieldColumnTemplate({
fieldName: "status",
label: "Status",
menuConfig: {
items: [{
label: "Custom Action",
iconClass: "esri-icon-settings",
clickFunction: (event) => {
console.log("Custom action on:", event.feature);
}
}]
}
}),
new FieldColumnTemplate({
fieldName: "value",
label: "Value ($)",
textAlign: "right",
formatFunction: (info) => {
return `$${info.value.toLocaleString()}`;
}
})
]
}
});
Group Column Template
import GroupColumnTemplate from "@arcgis/core/widgets/FeatureTable/support/GroupColumnTemplate.js";
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
tableTemplate: {
columnTemplates: [
new GroupColumnTemplate({
label: "Location",
columnTemplates: [
new FieldColumnTemplate({ fieldName: "city", label: "City" }),
new FieldColumnTemplate({ fieldName: "state", label: "State" }),
new FieldColumnTemplate({ fieldName: "country", label: "Country" })
]
}),
new GroupColumnTemplate({
label: "Details",
columnTemplates: [
new FieldColumnTemplate({ fieldName: "name", label: "Name" }),
new FieldColumnTemplate({ fieldName: "type", label: "Type" })
]
})
]
}
});
FeatureTable Events
// Row selection
featureTable.on("selection-change", (event) => {
console.log("Added:", event.added);
console.log("Removed:", event.removed);
// Get all selected features
const selectedFeatures = featureTable.highlightIds.toArray();
});
// Row click (double-click to zoom)
featureTable.viewModel.on("row-highlight-change", (event) => {
if (event.feature) {
view.goTo(event.feature.geometry);
}
});
// Editing complete
featureTable.on("edit-complete", (event) => {
console.log("Edited feature:", event.feature);
console.log("Updated attributes:", event.attributes);
});
Programmatic Selection
// Select by ObjectIDs
featureTable.highlightIds.add(123);
featureTable.highlightIds.addMany([124, 125, 126]);
// Clear selection
featureTable.highlightIds.removeAll();
// Select from query
const results = await featureLayer.queryObjectIds({
where: "status = 'active'"
});
featureTable.highlightIds.addMany(results);
Filter and Refresh
// Filter by geometry
featureTable.filterGeometry = view.extent;
// Filter by expression
featureTable.layer.definitionExpression = "category = 'A'";
// Refresh data
featureTable.refresh();
// Clear filters
featureTable.filterGeometry = null;
featureTable.layer.definitionExpression = null;
Sync with Map Selection
// Map click selects in table
view.on("click", async (event) => {
const response = await view.hitTest(event);
const feature = response.results.find(r => r.layer === featureLayer);
if (feature) {
featureTable.highlightIds.removeAll();
featureTable.highlightIds.add(feature.graphic.attributes.OBJECTID);
}
});
// Table selection highlights on map
featureTable.on("selection-change", async (event) => {
const layerView = await view.whenLayerView(featureLayer);
if (highlightHandle) {
highlightHandle.remove();
}
const objectIds = featureTable.highlightIds.toArray();
if (objectIds.length > 0) {
const query = featureLayer.createQuery();
query.objectIds = objectIds;
const results = await featureLayer.queryFeatures(query);
highlightHandle = layerView.highlight(results.features);
}
});
FormTemplate
Configure edit forms for features.
Basic FormTemplate
import FormTemplate from "@arcgis/core/form/FormTemplate.js";
const formTemplate = new FormTemplate({
title: "Edit Feature",
description: "Update the feature attributes",
elements: [
{
type: "field",
fieldName: "name",
label: "Name"
},
{
type: "field",
fieldName: "category",
label: "Category"
},
{
type: "field",
fieldName: "description",
label: "Description"
}
]
});
featureLayer.formTemplate = formTemplate;
Field Elements
import FieldElement from "@arcgis/core/form/elements/FieldElement.js";
const fieldElement = new FieldElement({
fieldName: "name",
label: "Name",
description: "Enter the feature name",
hint: "Required field",
requiredExpression: "true",
editableExpression: "$feature.status != 'locked'",
visibilityExpression: "$feature.type != 'hidden'"
});
Group Elements
import GroupElement from "@arcgis/core/form/elements/GroupElement.js";
const formTemplate = new FormTemplate({
elements: [
new GroupElement({
label: "Basic Information",
description: "Enter basic details",
elements: [
{ type: "field", fieldName: "name", label: "Name" },
{ type: "field", fieldName: "type", label: "Type" }
]
}),
new GroupElement({
label: "Location",
initialState: "collapsed", // expanded, collapsed
elements: [
{ type: "field", fieldName: "address", label: "Address" },
{ type: "field", fieldName: "city", label: "City" },
{ type: "field", fieldName: "state", label: "State" }
]
})
]
});
Text Elements
import TextElement from "@arcgis/core/form/elements/TextElement.js";
const formTemplate = new FormTemplate({
elements: [
new TextElement({
type: "text",
text: "<h3>Important Instructions</h3><p>Please fill out all required fields.</p>"
}),
{ type: "field", fieldName: "name", label: "Name" },
new TextElement({
text: "<hr><small>Fields below are optional</small>"
}),
{ type: "field", fieldName: "notes", label: "Notes" }
]
});
Relationship Elements
import RelationshipElement from "@arcgis/core/form/elements/RelationshipElement.js";
const formTemplate = new FormTemplate({
elements: [
{ type: "field", fieldName: "name", label: "Name" },
new RelationshipElement({
relationshipId: 0,
label: "Related Inspections",
description: "View and manage related inspection records",
displayCount: 5,
orderByFields: [{
field: "inspection_date",
order: "desc"
}],
editableExpression: "true"
})
]
});
Input Types
TextBox Input
{
type: "field",
fieldName: "name",
label: "Name",
input: {
type: "text-box",
maxLength: 100,
minLength: 1
}
}
TextArea Input
{
type: "field",
fieldName: "description",
label: "Description",
input: {
type: "text-area",
maxLength: 1000,
minLength: 0
}
}
ComboBox Input
{
type: "field",
fieldName: "category",
label: "Category",
input: {
type: "combo-box",
showNoValueOption: true,
noValueOptionLabel: "Select a category..."
}
}
// Works with coded value domains
// Domain values automatically populate the combo box
Radio Buttons Input
{
type: "field",
fieldName: "priority",
label: "Priority",
input: {
type: "radio-buttons",
showNoValueOption: false
}
}
Switch Input
{
type: "field",
fieldName: "is_active",
label: "Active",
input: {
type: "switch",
offValue: 0,
onValue: 1
}
}
DatePicker Input
{
type: "field",
fieldName: "start_date",
label: "Start Date",
input: {
type: "date-picker",
min: new Date("2020-01-01"),
max: new Date("2030-12-31"),
includeTime: false
}
}
DateTimePicker Input
{
type: "field",
fieldName: "event_datetime",
label: "Event Date/Time",
input: {
type: "datetime-picker",
min: new Date("2020-01-01T00:00:00"),
max: new Date("2030-12-31T23:59:59"),
includeTime: true
}
}
TimePicker Input
{
type: "field",
fieldName: "event_time",
label: "Event Time",
input: {
type: "time-picker"
}
}
Barcode Scanner Input
{
type: "field",
fieldName: "barcode",
label: "Barcode",
input: {
type: "barcode-scanner"
}
}
Expression-Based Configuration
Visibility Expressions
const formTemplate = new FormTemplate({
expressionInfos: [
{
name: "show-commercial-fields",
expression: "$feature.property_type == 'commercial'"
},
{
name: "show-residential-fields",
expression: "$feature.property_type == 'residential'"
}
],
elements: [
{ type: "field", fieldName: "property_type", label: "Property Type" },
{
type: "group",
label: "Commercial Details",
visibilityExpression: "show-commercial-fields",
elements: [
{ type: "field", fieldName: "business_name", label: "Business Name" },
{ type: "field", fieldName: "num_employees", label: "Employees" }
]
},
{
type: "group",
label: "Residential Details",
visibilityExpression: "show-residential-fields",
elements: [
{ type: "field", fieldName: "num_bedrooms", label: "Bedrooms" },
{ type: "field", fieldName: "num_bathrooms", label: "Bathrooms" }
]
}
]
});
Required Expressions
{
type: "field",
fieldName: "inspection_notes",
label: "Inspection Notes",
requiredExpression: "$feature.inspection_result == 'failed'"
}
Editable Expressions
{
type: "field",
fieldName: "approved_by",
label: "Approved By",
editableExpression: "$feature.status == 'pending'"
}
FeatureForm Widget
Widget for editing feature attributes.
import FeatureForm from "@arcgis/core/widgets/FeatureForm.js";
const featureForm = new FeatureForm({
container: "formDiv",
layer: featureLayer,
formTemplate: formTemplate
});
// Set feature to edit
featureForm.feature = selectedGraphic;
// Listen for submit
featureForm.on("submit", () => {
if (featureForm.feature) {
const updated = featureForm.getValues();
featureLayer.applyEdits({
updateFeatures: [{
attributes: {
...featureForm.feature.attributes,
...updated
},
geometry: featureForm.feature.geometry
}]
});
}
});
// Handle value changes
featureForm.on("value-change", (event) => {
console.log(`${event.fieldName} changed to ${event.value}`);
});
AttributeTableTemplate
Configure attribute table in Editor widget.
import AttributeTableTemplate from "@arcgis/core/widgets/Editor/support/AttributeTableTemplate.js";
import AttributeTableFieldElement from "@arcgis/core/widgets/Editor/support/AttributeTableFieldElement.js";
const tableTemplate = new AttributeTableTemplate({
elements: [
new AttributeTableFieldElement({
fieldName: "name",
label: "Name",
editable: true
}),
new AttributeTableFieldElement({
fieldName: "status",
label: "Status",
editable: true
})
]
});
Editor Widget Integration
import Editor from "@arcgis/core/widgets/Editor.js";
const editor = new Editor({
view: view,
layerInfos: [{
layer: featureLayer,
formTemplate: formTemplate,
enabled: true,
addEnabled: true,
updateEnabled: true,
deleteEnabled: true
}]
});
view.ui.add(editor, "top-right");
Common Patterns
Complete Form Setup
const formTemplate = new FormTemplate({
title: "Property Information",
description: "Enter property details",
preserveFieldValuesWhenHidden: true,
expressionInfos: [
{
name: "is-commercial",
expression: "$feature.type == 'commercial'"
}
],
elements: [
// Header text
{
type: "text",
text: "<b>Basic Information</b>"
},
// Required field
{
type: "field",
fieldName: "name",
label: "Property Name",
requiredExpression: "true",
input: { type: "text-box", maxLength: 100 }
},
// Dropdown
{
type: "field",
fieldName: "type",
label: "Property Type",
input: { type: "combo-box" }
},
// Conditional group
{
type: "group",
label: "Commercial Details",
visibilityExpression: "is-commercial",
elements: [
{ type: "field", fieldName: "business_type", label: "Business Type" },
{ type: "field", fieldName: "sqft", label: "Square Footage" }
]
},
// Date field
{
type: "field",
fieldName: "inspection_date",
label: "Last Inspection",
input: { type: "date-picker" }
},
// Long text
{
type: "field",
fieldName: "notes",
label: "Notes",
input: { type: "text-area", maxLength: 500 }
}
]
});
FeatureTable with Editing
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
container: "tableDiv",
editingEnabled: true,
fieldConfigs: [
{ name: "name", label: "Name", editable: true },
{ name: "status", label: "Status", editable: true },
{ name: "created_date", label: "Created", editable: false }
]
});
featureTable.on("edit-complete", async (event) => {
console.log("Edit saved:", event.feature.attributes);
// Refresh related data
await featureLayer.refresh();
});
Responsive Table Layout
const featureTable = new FeatureTable({
view: view,
layer: featureLayer,
container: "tableDiv",
autoRefreshEnabled: true,
pageSize: 25
});
// Resize handling
window.addEventListener("resize", () => {
featureTable.refresh();
});
// Toggle visibility
function toggleTable(visible) {
document.getElementById("tableDiv").style.display = visible ? "block" : "none";
if (visible) {
featureTable.refresh();
}
}
TypeScript Usage
Form elements use autocasting with type properties. For TypeScript safety, use as const:
// Use 'as const' for type safety in form templates
const formTemplate = {
title: "Edit Feature",
elements: [
{
type: "field",
fieldName: "name",
label: "Name"
},
{
type: "group",
label: "Address",
elements: [
{ type: "field", fieldName: "street" },
{ type: "field", fieldName: "city" }
]
}
]
} as const;
// For input types
const formElement = {
type: "field",
fieldName: "status",
input: { type: "combo-box" }
} as const;
Tip: See arcgis-core-maps skill for detailed guidance on autocasting vs explicit classes.
Common Pitfalls
Field Names Must Match: fieldName must exactly match layer field
// Layer has field "PropertyName" { fieldName: "PropertyName" } // Correct { fieldName: "propertyname" } // Wrong - case sensitiveCoded Value Domains: ComboBox auto-populates from domain
// If field has coded value domain, values come from domain { type: "field", fieldName: "status", input: { type: "combo-box" } } // Dropdown shows domain values automaticallyExpression Names: Reference expressions by name string
expressionInfos: [{ name: "my-expr", expression: "..." }], elements: [{ visibilityExpression: "my-expr" // String reference }]Layer Must Be Editable: For edit features to work
// Layer capabilities must include editing if (layer.capabilities.editing.supportsUpdateByOthers) { featureTable.editingEnabled = true; }Container Size: Table needs explicit height
#tableDiv { height: 400px; /* Required */ width: 100%; }