---
title: "Two ways to store a job-site address, benchmarked instead of argued about"
description: "Salesforce Location has a VisitorAddressId lookup to a real Address object. It looked like the right answer. Three DMLs, two records, and a circular delete later, plain fields won."
source: "https://noconaconsulting.com/blog/job-site-address-circular-delete/"
---

# Two ways to store a job-site address, benchmarked instead of argued about

_Architecture · Doorvana · Garage door manufacturing & service_

Salesforce Location has a VisitorAddressId lookup to a real Address object. It looked like the right answer. Three DMLs, two records, and a circular delete later, plain fields won.

Nocona Consulting · September 25, 2026 · 5 min read

At [Doorvana](https://noconaconsulting.com/work/) the property is a standard `Location` record — the garage door opening belongs to a house, and that house outlives whoever owns it. Which raises a dull-sounding question with a surprisingly sharp answer: where does the address go?

Salesforce offers two paths, and one of them looks obviously better on paper.

## The path that looks right

`Location` ships with a `VisitorAddressId` field. It is not an address — it is a **lookup to a record of the standard `Address` object**, which is fully writable and carries Street as a textarea, City, `StateCode` and `CountryCode` as real picklists, PostalCode, and its own latitude and longitude.

Compare that to hand-rolling `Site_Street__c`, `Site_City__c`, `Site_State__c`, `Site_Postal_Code__c` and you appear to be reinventing something Salesforce already built, including the state picklist.

So I built both and measured, rather than reasoning about it in a doc.

## What the sandbox said

_The Address path needs the Location to exist before the Address can be created, because Address.ParentId is required and points at Location — then the Location has to be updated to point back. Two of its three DMLs exist only to wire the pair together._

Three DML operations against one. But the row count is not what decided it.

## The sharp edge: a circular delete

`Location.VisitorAddressId` points at the Address. `Address.ParentId` points back at the Location. **Neither record can be deleted while the other points at it**, and both deletes fail with the same unhelpful message:

```plaintext
could not be completed because it is associated with the following locations
```

Cleanup is a three-step dance in a fixed order:

1.  null out `Location.VisitorAddressId`
2.  delete the `Address`
3.  delete the `Location`

For most objects that would be an annoyance. For a property base it is a real problem. Properties get merged, de-duplicated, corrected and retired over years — a wrong address typed once in 2027 is still there in 2032 unless someone can remove it. Making deletion a documented procedure rather than a delete button means, in practice, that nobody cleans up.

Two smaller findings in the same test, both disqualifying on their own:

-   `Address.LocationType` is required and its only available value is **Virtual**. Every job-site address would be stamped “Virtual”, forever.
-   `Address.AddressType` offers Mailing, Shipping, Billing and Home. There is no job-site value.

The standard object is not built for this. It is built for corporate addresses.

## Correcting my own note while I was at it

I had written in the ERD that empty Lat/Long on Location would break Field Service dispatch. That is wrong, and testing it is what showed me.

Work Order and Service Appointment carry their own standard compound address fields, and routing uses those. A Location with no coordinates does not break scheduling. What it costs is **geospatial reporting over the property base** — mapping the sites served, analysing coverage by area — which is a real cost but a different one, and worth being accurate about when a client asks whether it matters.

Rule of thumb

Benchmark the thing. “Three DML versus one” and “the delete is circular” are facts you can put in front of a client. “It felt cleaner” is not.

## The state field had to be rebuilt anyway

Choosing custom fields means giving up the `StateCode` picklist the Address object hands you, and that mattered: with free text, “TX”, “Texas” and “tx” are three different states and every report is quietly wrong.

So `Site_State__c` became a restricted picklist on a `US_State` global value set — 51 values, Texas default. Which surfaced its own Salesforce limitation.

A text field cannot be repointed at a global value set

Salesforce rejects the change in place: _“Cannot change which global value set this picklist uses.”_ The field has to be deleted and recreated. That means pulling its field-level security out of the permission set first, running a validated destructive deploy, recreating the field, and restoring the FLS in sorted position.

manifest/destructiveChangesPre.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>Location.Site_State__c</members>
        <name>CustomField</name>
    </types>
</Package>
```

Worth knowing: the API name was reusable immediately, with no purge from Deleted Fields required. Verified afterwards by describe — picklist, restricted, 51 values, TX default.

One more thing fell out of the same pass. `Site_City__c` was 80 characters and `Site_State__c` was shorter, which is inverted relative to the standard compound address where City is 40. Nobody would notice until a migration _out_ of these fields into standard address fields truncated somebody’s city. Narrowed to 40 while it was free to do so.

## Where this leaves the model

Four plain fields on Location, one of them a restricted picklist on a shared value set. One DML to create a property. A delete button that works.

The Address object is the more sophisticated answer, and for a company managing its own corporate sites it is probably the right one. For a property base that will accumulate tens of thousands of job sites and needs to stay clean, the boring version wins — and it won on numbers I could show the client rather than on taste.

-   Location
-   Field Service
-   Data model
-   Address
-   Global value sets

Disclosure: Doorvana's owner is also a co-owner of Nocona Consulting. This is a real engagement between two separate companies, but not an arm's-length one — and not an independent reference. [More on the work page](https://noconaconsulting.com/work/).

## FAQ

### Should a Salesforce Location use VisitorAddressId or custom address fields?

For job-site addresses, custom fields on the Location are usually the better answer. VisitorAddressId points at a record of the standard Address object, which requires creating two records and three DML operations to wire together because Address.ParentId is required and points back at the Location. The pair also forms a circular reference that blocks deletion of either record. Custom fields are one DML on one record, at the cost of rebuilding the state picklist yourself.

### Why can a Salesforce Location or Address record not be deleted?

When Location.VisitorAddressId points at an Address and that Address's ParentId points back at the Location, the two records reference each other and neither can be deleted while the other exists. Both deletes fail with an error about the record being associated with the following locations. The cleanup order is to null out Location.VisitorAddressId first, then delete the Address, then delete the Location.

### Does an empty latitude and longitude on a Location break Field Service scheduling?

No. Work Order and Service Appointment carry their own standard compound address fields and Field Service routing uses those, so a Location with empty Lat/Long does not break dispatch. What it does cost is geospatial reporting across the property base — mapping every site served, or analysing drive time by area.

### Can a Salesforce text field be converted to use a global value set?

No. Salesforce rejects converting an existing text field or local picklist to a global value set in place, with the error Cannot change which global value set this picklist uses. The field has to be deleted and recreated on the value set, which means pulling its field-level security first, running a destructive deploy, then restoring the permissions.
