In ArcGIS Pro 3.4 we have introduced three new templates that allow you to generate attribute rules tailored for specific workflows. We curated a selection of commonly used attribute rules and created a familiar geoprocessing tool experience that guides you through their creation. Attribute rule templates not only speed up the generation of these rules but also makes them more accessible for users who may not have prior knowledge of Arcade scripting.
The Generate ID template attribute rule tool allows us to populate a field with custom-formatted ID values when inserting or updating features. This blog describes a workflow for creating and utilizing a Generate ID template attribute rule to add formatted ID values to fire hydrants in a suburb of Baltimore, Maryland. Imagine placing down some fire hydrant point features on a map and seeing the following behavior:

Uniquely identifying data is a common data management workflow, often accomplished by tagging features with unique ID values. Typically these IDs must follow a standardized format, such as prefix_ID#_Suffix
– such as, Asset_387_west
. Imagine that we work for the City of Baltimore’s GIS department and are tasked with creating a map of fire hydrants in a neighborhood. The city’s fire department requires fire hydrant IDs in the format hyd_#####
where #
represents the ID of the fire hydrant. If the ID value has less than five digits, we will pad the ID with leading zeros. For example, a hydrant with ID = 1
, would format as hyd_00001
.
Manually adding fire hydrant IDs to a map containing hundreds of features is extremely tedious and prone to errors, such as duplicate values and typos. The Generate ID template attribute rule automates incrementing and formatting ID values for us! Once the rule is configured, we simply add new hydrant features to a map and the formatted ID values appear automatically.
Here’s a brief overview video of the Generate ID Attribute Rule tool (note: labeling on the ID values has been enabled so we can see the formatted values on the map):
Utilizing the Generate ID Attribute Rule Tool
Review Generate Symbol Rotation Data
Note: Attribute Rules require an ArcGIS Pro Standard or Advanced license level. To learn more please refer to the ArcGIS Pro license levels help documentation.
- Download the TemplatedARs_GenerateIDs.ppkx project package file and then click on the file to open the TemplatedARs_GenerateID project. This project contains data to step through the workflow in this blog.
- When the project opens the Hydrants Map is active, containing a feature class of fire hydrant points with labeling enabled.

First, right-click on the Fire Hydrants layer in the Contents pane. From the context menu, click on Data Design > Attribute Rules. The Attribute Rules Data Design view will open.
In the ribbon, click on the bottom half of the Calculation Rule button. From the context menu, select Templates > Generate ID.

The Generate ID Template Attribute Rule GP tool dialog opens.
Tool Overview

- The Expression parameter allows us to specify a subset of the features we’d like to apply the Generate ID rule to using a SQL expression. Since we want all of the fire hydrant features to have formatted ID values we’ll leave this parameter as is (this is default).
- The Field parameter allows us to specify the field that we’d like ID values to be written to. Let’s choose HydrantID.
- The Create Sequences parameter allows us to choose if we’d like the Generate ID tool to create database sequences for the IDs we specify. Numeric values associated with IDs are stored in database sequences. The rule increments the sequence every time it is triggered. You might choose to disable this option if you already have existing database sequences you’d like to use.
- The Definition Type parameter allows us to define the format of the ID. There are three options:
-
- Builder – The ID format will be constructed using the ID Builder. This is the option we will use (choose it from the dropdown).
- Table – The ID format will be defined from a table using the ID Definition Table parameter.
- If you run the Generate ID Template Attribute Rule GP tool from the Geoprocessing Pane rather than from the Attribute Rules Data Design View, the output folder includes an ID Definition Table .csv file.
- The ID Definition Table is a table of the sequence definition configurations. You can edit and reuse the IDs you configured when you select this parameter. Simply select the .csv file from your file system!
- Coded Values – An ID configuration entry and sequence will be generated for each combination of coded values or subtypes from selected fields.
- Pro Tip: To configure the formatting of ID values generated using this definition I recommend using the ID Definition Table generated when running the tool via the Geoprocessing Pane. Run the tool with the Create Sequences option disabled. Edit the formatting of the IDs in the ID Definition Table using a text editor of your choice. Afterwards, rerun the tool with the Table Definition Type.
- The ID Builder parameter allows us to configure IDs we’d like the template attribute rule to generate. Let’s format our fire hydrant IDs to match the specifications provided by the city (
hyd_#####
):
-
-
- Filter SQL allows us to specify an optional SQL expression to select which features we’d like to apply the ID to based on attribute values. In our example, we want our hydrant ID value to be added to all fire hydrants so we’ll leave the parameter as is.
- Description is an optional description for the ID value that will be added to the rule settings for the attribute rule. Let’s give our ID the following description: “IDs for fire hydrants.”
- Sequence Name is the name of the database sequence to be created. Let’s call our database sequence “HydrantIDs”
- Starting Value is the starting number of the sequence. We already have manually added ID values for the first three hydrants, so let’s start our sequence at “4” (the default value is 1).
- Increment Value describes how the sequence value will be incremented. Let’s set our increment value to 1 (this is default).
- Prefix is an optional string or value that will be placed before the sequence value. Since we want our hydrant IDs in the format
hyd_#####
we should set our prefix to “hyd_”. - Suffix is an optional string or value that will be placed after the sequence value.
- Padding is an optional positive number that represents the number of digits, where zeros will be used to fill the unused digits. To create hydrant IDs in the format
hyd_#####
where unused digits (the #s) are zeros, let’s set the padding to 5. - Separator is an optional string value used to join the prefix, sequence, and suffix. We don’t need one to meet our formatting requirements for hydrant IDs.
- The Add Another button at the bottom of the ID builder parameter, allows us to add multiple formatted IDs to our fire hydrant class. We might choose to do this, for example, to distinguish between various fire hydrant types (e.g.,
hydTypeA_#####
,hydType_#####
, etc.). For our example we’ll stick with just one ID. - Each sequence can be previewed to help us validate the formatting we configured.
-
Review and Modify the Generated Rule
Click the OK button at the bottom of the tool to generate the template attribute rule. In the Attribute Rules Data Design View a new calculation rule appears called “GenerateID”. Rename the rule to “Set Hydrant IDs”.

If you scroll down to the script expression in the Details Pane, you can see the Arcade script that was generated. I’ve included the script in the collapsible below (note: it’s quite long!)
Click here to view Generate ID Attribute Rule Arcade script expression from ArcGIS Pro 3.4
/*
where_clause (Text): A SQL Expression used to determine if the rule should continue(matches query), or return early
seq_infos (Array): Array of sequence dictionaries
where_clause (Text): A SQL expression applied to $feature to identify the sequence to use
description (Text): A description of the sequence, this is for informational purposes and does not affect the execution
prefix (Text): A string place before the sequence value, can be an empty string
suffix (Text): A string placed after the sequence value, can be an empty string
padding (Text): Pads the sequence number to a given length, can be an empty string. The format must such as '0000' for a 4 digit number such as 0005
sequence_key (Text): The key used in the get_sequence_value function to identify the sequence
separator (Text): A string used to join the prefix, sequence and suffice, can be an empty string
intersect_values (Dictionary): The parameters used to look up a value in an intersecting layer to get an ID to determine a unique sequence for rows
target_name (Text): The unqualified target class name
where_clause (Text): An optional sql expression to limit the results from the target class
spatial_operator (Text): The optional string for spatial filtering. Intersects/Within.
search_distance (Number): The optional buffer distance to apply to spatial_operator.
search_units (Text): The optional units to apply to search_distance.
row_id_field (Text): The field in the intersected class used to determine the sequence used
id_values (Dictionary): A Dictionary where the keys are the values(as text) from the field in row_id_field in the intersected layers and the value is the key of the sequence to use
*/
var rule_settings = {
'where_clause': null,
'seq_infos': [
{
'where_clause': '',
'description': 'IDs for fire hydrants',
'prefix': 'hyd_',
'suffix': null,
'padding': '00000',
'sequence_key': 'HydrantIDs',
'separator': null,
'intersect_values': {
'target_name': null,
'where_clause': null,
'spatial_operator': 'Intersects',
'search_distance': null,
'search_units': null,
'order_by_clause': null,
'row_id_field': null,
'id_values': {
},
},
},
],
};
function get_sequence_value(sequence_key) {
return Decode(
sequence_key,
'HydrantIDs', NextSequenceValue('HydrantIDs'),
null
);
}
function get_feature_set(key) {
return Decode(
key,
null
);
}
function IsEmpty2(value) {
var type = TypeOf(value);
if (type == '') {
return true; // null
} else if (type == 'Boolean') {
return !value;
} else if (type == 'String') {
return IsEmpty(value);
} else if (
(type == 'Array') ||
(type == 'Dictionary') ||
(type == 'FeatureSet')
) {
for (var x in value) {
return false;
}
return true;
} else if (type == 'Number') {
return IsNan(value);
} else if (type == 'Point') {
return IsNan(value.x);
} else if (type == 'Multipoint') {
return Count(value.points) == 0;
} else if (type == 'Polyline') {
return Count(value.paths) == 0;
} else if (type == 'Polygon') {
return Count(value.rings) == 0;
} else if (type == 'Extent') {
return IsNan(value.xmin);
} else if (
(type == 'Feature') ||
(type == 'DateOnly') ||
(type == 'Time') ||
(type == 'Date') ||
(type == 'FeatureSetCollection') ||
(type == 'Portal') ||
(type == 'Function')
) {
return false
}
return null;
}
function features_to_featureset(features) {
// Converts features array to feature set.
if (TypeOf(features) == 'FeatureSet') {
return features;
}
var rows = [];
var feat, feat_dict;
for (var i in features) {
feat = features[i];
feat_dict = {
'__oid__': i + 1, // Incrementing OID field.
};
for (var j in feat) {
feat_dict[j] = feat[j];
}
Push(rows, {
'attributes': feat_dict,
});
}
if (IsEmpty(feat)) {
return;
}
// Add OID field to schema.
var feat_schema = Array(Schema(feat).fields);
Push(feat_schema, {
'name': '__oid__',
'type': 'esriFieldTypeInteger',
});
return FeatureSet({
'fields': feat_schema,
'features': rows,
});
}
function count_features(features, where_clause) {
// count features that match where_clause
if (IsEmpty2(features)) {
return 0;
}
if (IsEmpty(where_clause)) {
return Count(features);
}
var fs = features_to_featureset(features);
if (IsEmpty(fs)) {
return 0;
}
return Count(Filter(fs, where_clause));
}
function not_empty(x) {
return !IsEmpty(x);
}
function get_unit_code(unit) {
// Converts unit string to unit code
if (IsEmpty(unit)) {
return
}
var u = Lower(Replace(Replace(Replace(unit, ' ', ''), '-', ''), '_', ''));
// US / INT suffix differentiates the unit code. If no suffix, then it defaults to US Survey.
var international = false;
if (Right(u, 2) == 'us') {
u = Left(u, Count(u) - 2);
} else if (Right(u, 3) == 'int' && u != 'point') {
u = Left(u, Count(u) - 3);
international = true;
}
if (Right(u, 1) == 's' && u != 'inches') { // plural
u = Left(u, Count(u) - 1);
}
return When(
// Metric
u == 'km' || u == 'kilometer', 9036,
u == 'm' || u == 'meter', 9001,
u == 'dm' || u == 'decimeter', 109005,
u == 'cm' || u == 'centimeter', 1033,
u == 'mm' || u == 'millimeter', 1025,
// US Survey / International
u == 'nmi' || u == 'nauticalmile', IIf(international, 9030, 109012),
u == 'mi' || u == 'mile', IIf(international, 9093, 9035),
u == 'yd' || u == 'yard', IIf(international, 9096, 109002),
u == 'ft' || u == 'foot' || u == 'feet', IIf(international, 9002, 9003),
u == 'in' || u == 'inch' || u == 'inches', IIf(international, 109008, 109009),
// Misc
u == 'dd' || u == 'deg' || u == 'degree' || u == 'decimaldegree', 9102,
u == 'pt' || u == 'point', 109016,
// Default
null,
)
}
function pad_string(val, pad) {
if (IsEmpty(pad) || IsEmpty(val)) {
return Text(val);
}
return Right(Text(pad) + Text(val), Max([Count(Text(pad)), Count(Text(val))]));
}
function apply_sql_spatial_filter(feature_set, options) {
// Applies optional spatial/attribute filters and OrderBy clause to feature_set.
var spatialFilter = Decode(
Lower(DefaultValue(options, 'spatial_operator', '')),
'intersects', Intersects,
'contains', Contains,
'crosses', Crosses,
'envelopeintersects', EnvelopeIntersects,
'intersects', Intersects,
'overlaps', Overlaps,
'touches', Touches,
'within', Within,
null
);
var geo = DefaultValue(options, 'input_geometry', null);
if (!IsEmpty(spatialFilter) && !IsEmpty2(geo)) {
if (!IsEmpty(DefaultValue(options, 'search_distance', null))) {
geo = Buffer(geo, options.search_distance, get_unit_code(options.search_units));
}
feature_set = spatialFilter(feature_set, geo);
}
if (!IsEmpty(DefaultValue(options, 'where_clause', null))) {
feature_set = Filter(feature_set, options.where_clause);
}
if (!IsEmpty(DefaultValue(options, 'order_by_clause', null))) {
feature_set = OrderBy(feature_set, options.order_by_clause);
}
return feature_set;
}
if (count_features([$feature], DefaultValue(rule_settings, 'where_clause', null)) != 1) {
return;
}
Expects($feature, 'HydrantID')
if (!IsEmpty($feature.HydrantID)) {
return;
}
var fs = features_to_featureset([$feature]);
var seq_val = null;
var input_geometry = Geometry($feature);
for (var i in rule_settings.seq_infos) {
var seq = rule_settings.seq_infos[i];
if (count_features(fs, seq.where_clause) == 1) {
var seq_val = null;
// If there are intersect lookup details, get the sequence key by intersecting the layer, if not, use
if (!IsEmpty2(DefaultValue(seq, ['intersect_values', 'id_values'], {}))) {
// Convert the key to the featureset
seq.intersect_values.input_geometry = input_geometry;
var intersect_fs = apply_sql_spatial_filter(get_feature_set(seq.intersect_values.target_name), seq.intersect_values);
for (var feat in intersect_fs) {
var row_id = Text(feat[seq.intersect_values.row_id_field]);
if (HasKey(seq.intersect_values.id_values, row_id)) {
seq_val = get_sequence_value(seq.intersect_values.id_values[row_id]);
if (!IsEmpty(seq_val)) {
break;
}
}
}
}
if (IsEmpty(seq_val) && !IsEmpty2(seq.sequence_key)) {
seq_val = get_sequence_value(seq.sequence_key);
}
// Check if seq was found
if (IsEmpty(seq_val)) {
continue;
}
return Concatenate(Filter([seq.prefix, pad_string(seq_val, seq.padding), seq.suffix], not_empty), seq.separator);
}
}
return;
Once the attribute rule is saved, click the map and on the edit tab click the Create button. Add a few fire hydrant points.
In the Contents pane, right click on the Fire Hydrants layer and open the Attributes table. You’ll see that the HydrantID values for the features we added follow the hyd_#####
format exactly as we configured in the tool!

Conclusion
The Generate ID template rule brings one of the most used attribute rules into an easy to use, wizard-like experience. With just a few clicks you can add custom-formatted IDs to your features. And all with no prior knowledge of Arcade needed to use it.
We’d love to hear your feedback regarding the Generate Symbol Rotation template attribute rule! We encourage you to share your thoughts about it or any other attribute rule and data management topics that come to mind on the ArcGIS Ideas page!
FAQs
Do I need a specific ArcGIS Pro license level to generate template attribute rules?
Yes, you will need either an ArcGIS Pro Standard or Advanced license level to utilize template attribute rules.
What are the differences between generating a template attribute rule from the Attribute Rules Data Design view versus from the Geoprocessing pane?
If you generate a template attribute rule from the Attribute Rule Data Design view for a particular feature class, the rule is added directly to the grid in the design view. Prior to saving the rule, you may make edits to the rule via the Details pane.
On the other hand, if you use the template attribute rule geoprocessing tool, you must import the .csv file of the attribute rule generated by the tool to the class you specified in the Input Table parameter. The .csv file containing the attribute rule is stored in the path specified in the Output Folder parameter.
What are the differences between Attribute Rule Templates and Ready to Use Rules?
Template attribute rules can be used to generate Calculation rules using geoprocessing tools. After generating a template attribute rule a user can modify the Arcade script associated with it and any other attribute rule properties via the Details pane.
Ready to Use Rules require a Data Reviewer license. They can be used to generate Constraint and Validation reviewer rules from directly within the Attribute Rules Data Design view. Reviewer rules are used to detect features that do not comply with established data quality requirements defined by your organization. To learn more about reviewer rules please visit Manage reviewer rules in a geodatabase.
Where can I learn more about Generate ID template attribute rules?
The following online help documents are useful in more about Generate ID template attribute rules:
Commenting is not enabled for this article.