Blog

Granting Permissions to AddressTools Premium Users

Applies to: AddressTools v10.2 and later.

Overview

With AddressTools installed in your org, you need to grant users the correct permissions before they can use the solution. AddressTools uses Salesforce Permission Sets to control access to its features, custom objects, and fields.

This article explains which permission sets to assign and how they work together.

Permission Sets

AddressTools ships with two core permission sets and a set of granular address field permission sets.

Core Permission Sets

Permission Set API Name Purpose
AddressTools Premium Admin User AddressToolsPremiumAdminUser For administrators who configure and manage AddressTools
AddressTools Premium Standard User AddressToolsPremiumStdUser For everyday users who interact with AddressTools features

Admin User grants full create, read, update, and delete access to all AddressTools custom objects, plus access to the AddressTools Administration and Batch Address Verification tabs.

Standard User grants read-only access to AddressTools reference data (countries, states, time zones, ZIP codes) and access to the interactive address features.

Note: Users assigned the Admin User permission set should also be assigned Standard User to ensure access to all runtime functionality.

Address Field Edit Permission Sets

AddressTools adds custom fields (Address Status and County) to standard objects. To allow users to edit these fields, assign the relevant permission sets for the address types they work with:

Permission Set API Name Object Fields Granted
AddressTools Account Billing Address Edit AddressTools_Account_BillingAddress_Edit Account Billing Address Status, Billing County
AddressTools Account Shipping Address Edit AddressTools_Account_ShippingAddress_Edit Account Shipping Address Status, Shipping County
AddressTools Contact Mailing Address Edit AddressTools_Contact_MailingAddress_Edit Contact Mailing Address Status, Mailing County
AddressTools Contact Other Address Edit AddressTools_Contact_OtherAddress_Edit Contact Other Address Status, Other County
AddressTools Contract Billing Address Edit AddressTools_Contract_BillingAddress_Edit Contract Billing Address Status, Billing County
AddressTools Contract Shipping Address Edit AddressTools_Contract_ShippingAddress_Edit Contract Shipping Address Status, Shipping County
AddressTools Lead Address Edit AddressTools_Lead_Address_Edit Lead Address Status, County

Assign only the permission sets relevant to each user’s role. For example, a sales user working with Leads and Accounts may only need the Lead and Account permission sets.

Assigning Permission Sets

We recommend deploying AddressTools permissions using Permission Set Groups. This allows you to bundle the core permission set with the relevant address field edit sets into a single assignable group per user role – simplifying ongoing user management.

Using Permission Set Groups (Recommended)

  1. Navigate to Setup > Permission Set Groups.
  2. Create a new Permission Set Group (e.g. “AddressTools Standard Users”).
  3. Add the relevant permission sets to the group (see Recommended Assignments below).
  4. Assign the group to users.

This approach ensures consistent permissions across your user base and makes it straightforward to add or remove access as your requirements change.

Recommended Assignments

User Role Permission Sets to Assign
Administrator Admin User + Standard User + relevant Address Edit sets
Standard user Standard User + Address Edit sets for objects they use
Integration / API user Standard User + relevant Address Edit sets

Assigning Individual Permission Sets

If you prefer to assign permission sets individually:

  1. Navigate to Setup > Users > Permission Sets.
  2. Select the permission set you want to assign.
  3. Click Manage Assignments, then Add Assignment.
  4. Select the users and click Assign.

License Allocation

After installing AddressTools in production, a managed package license must be allocated to each user before they can access package features.

To manage licenses:

  1. Navigate to Setup > Installed Packages.
  2. Find AddressTools and click Manage Licenses.
  3. Add the users who require access.

Note: In a sandbox environment, this step can be skipped as a site license is applied automatically.

API Enabled Requirement

Users who need to use the interactive address features (type-ahead search and address verification) require the API Enabled permission on their profile. This is standard on most Salesforce licenses but may need to be enabled for certain user types.

Notes

  • The permission set labelled “AddressTools Premium Standard User (Deprecated)” (AddressToolsPremiumStandardUser) is a legacy permission set from an earlier version. Use AddressToolsPremiumStdUser instead.
  • Guest users are not supported by the packaged permission sets. If you require guest user access, create a custom permission set granting read access to the AddressTools custom objects and Apex classes prefixed with pw_ccpro__.

Back to the AddressTools Premium installation walkthrough

Install AddressTools Premium with an LLM Copilot

Don’t have AddressTools Premium installed yet? You can ask your copilot to handle the installation directly from the AppExchange — no need to navigate the browser-based installer.

Example Prompts

Paste the headless setup guide link along with your request:

https://provenworks.com/addresstools-premium-headless-setup-guide-for-llm-copilots/

Direct install

“Install AddressTools Premium from the AppExchange into my org”

“Install AddressTools Premium into my sandbox uat-org

Intent-based

If you describe what you’re trying to achieve, the copilot will work out that AddressTools Premium is what you need:

“I want to implement address verification in my Salesforce org”

“I need to validate and standardize addresses on Contacts and Leads — what do I need to install?”

“I want to add country and state standardization to my org”

What Happens

The headless setup guide contains the package version ID and install command your copilot needs. When you share it, the copilot will:

  1. Check whether AddressTools Premium (pw_ccpro) is already installed
  2. If not, install it using the Salesforce CLI with the package version ID from the guide
  3. Confirm the installation succeeded and the package version
  4. If it’s already installed, skip straight to configuration

After Installation — Manual Setup Required

Once the package is installed, there are two one-time initialisation steps that must be completed in the browser before the copilot can configure anything. There is no command-line option for these.

  1. Open AddressTools Administration in your Salesforce org: https://<your-salesforce-domain>/lightning/n/pw_ccpro__AddressToolsAdmin
  2. On the Installation tab:
    • Create an Authentication Token — click Create Token. This generates the credentials AddressTools needs to communicate with ProvenWorks services.
    • Data Installation — click Get Started. This loads the default country, state, ZIP code, and time zone reference data, along with configurations for standard address blocks. Allow up to 10 minutes.

Your copilot will check for this data and prompt you if it hasn’t been done yet.


Next Step

Ready to configure? Head to Configure AddressTools Premium with an LLM Copilot for setup instructions, tips, and example prompts.

Configure AddressTools Premium with an LLM Copilot

Stop clicking through Setup menus. With an LLM copilot (GitHub Copilot, Agentforce Vibes, Cursor, Windsurf, or similar) you can install and configure AddressTools Premium — create address blocks, assign permissions, tune settings, and manage reference data — just by describing what you need in plain English.

Early access: This capability is under active development — it covers a focused set of configuration tasks and has been tested with a small pool of models, so results may vary. We’re expanding coverage and improving reliability with each release. If you hit a gap or something doesn’t work as expected, let us know.


Documentation Map

These guides are for you (the human). The Headless Setup Guide is for your copilot — you don’t need to read it, just paste the link.

Guide Audience What it covers
This page You How this works, prerequisites, tips, and FAQ
Install AddressTools Premium You Getting the package installed via your copilot
Example Prompts You Copy-paste prompts organised by task
Headless Setup Guide Your copilot Technical reference — paste this link into your copilot’s chat

Prerequisites

  • VS Code (or your editor of choice) with an LLM copilot installed
  • Salesforce CLI (sf) authenticated against the target org
  • System Administrator access on the target org

How It Works

  1. Open your copilot’s chat in VS Code (or your editor of choice)
  2. Paste this link into your prompt so the copilot has the technical context it needs:
    https://provenworks.com/addresstools-premium-headless-setup-guide-for-llm-copilots/
    
  3. Describe what you want in plain English — for example:

    “Use this guide — https://provenworks.com/addresstools-premium-headless-setup-guide-for-llm-copilots/ — I want to create a new address block on Contact for a Seasonal address with verification.”

  4. Review and approve the terminal commands your copilot proposes
  5. Done — no Setup menus, no manual metadata wrangling

Not sure what to ask? See the Example Prompts guide for ready-to-use prompts by task category.


Tips for Better Results

  1. Be specific about your org. Include the org alias in your prompt if you have multiple orgs authenticated: “On production-org, disable the Account trigger for the Data Migration profile.”

  2. State what capabilities you need. When creating an address block, say whether you want verification, lookup population, and/or geocoding. If you don’t specify, the copilot will ask.

  3. Name the object and address type. For standard objects, mention “Mailing”, “Billing”, “Shipping”, or “Other”. For custom objects, mention the API name (e.g. Property__c).

  4. Reference the guide URL. Paste the guide link in your first message so the copilot has the full context: https://provenworks.com/addresstools-premium-headless-setup-guide-for-llm-copilots/

  5. Let the copilot run commands. It will use sf CLI commands and anonymous Apex — just approve the terminal commands when prompted. No manual Setup clicks required.


Frequently Asked Questions

Do I need to know Apex or the Salesforce CLI? Not strictly — the copilot writes and runs the commands for you. That said, we recommend having at least a basic familiarity with the Salesforce CLI and Apex so you can follow along with what the copilot is doing, understand the changes being made, and catch anything that doesn’t look right.

Will this work with any LLM, or just GitHub Copilot? The Headless Setup Guide is designed for any LLM copilot that can execute CLI commands — including GitHub Copilot, Cursor, Windsurf, and Salesforce’s own Agentforce Vibes (in Act mode). If your tool can run terminal commands and read web content, it should work.

Is this safe for production? The guide enforces safe practices: it checks before creating, uses anonymous Apex (which leaves no code behind), and creates dedicated permission sets rather than modifying managed-package metadata. That said, we strongly recommend running these tasks in a sandbox first, verifying the results, and then promoting the metadata to production through your normal deployment process. Always review the commands proposed before approving them in your terminal.

Can I undo what the copilot creates? Yes. Custom fields, permission sets, and address block records are all standard Salesforce metadata and data — you can delete or modify them through Setup or the CLI at any time. You can also ask your copilot to roll back the changes it made in the same session.

Does the package require a license for production? For production orgs, the package starts a 14-day free trial. Contact sales@provenworks.com for licensing after the trial.

Where do I get help?


Glossary

Term Meaning
Address block A record that tells AddressTools which fields on which object to validate, standardise, and geocode
Anonymous Apex Apex code executed on-the-fly via the CLI — it runs and disappears, leaving no code in the org
FLS Field-Level Security — controls which users can read/write specific fields
Geocoding Converting an address into latitude/longitude coordinates
Hierarchy custom setting A Salesforce setting with org-wide, profile, and user-level overrides
Namespace (pw_ccpro) The managed package prefix — all AddressTools objects and fields start with this
Permission set A Salesforce construct that grants users access to specific fields and features
PLAV Premise-Level Address Verification — validates addresses down to the building number
sf CLI The Salesforce CLI — command-line tool for interacting with Salesforce orgs

Example Prompts: What to Ask Your AddressTools Copilot

Below are ready-to-use prompts organised by task. Your copilot will ask clarifying questions if it needs more detail (like which org to target), then carry out the work.

For setup instructions and tips on writing effective prompts, see the Configure guide.

Create Address Blocks

“Add a custom address block on Contact for a Seasonal address with verification, lookups, and geocoding”

“Set up address validation on the Lead standard address fields”

“Create a Billing address block on our Property__c custom object — just core fields and verification, no geocoding”

“Wire up the Site__c custom object to AddressTools with full verification, lookup population, and geocoding”

Copilot will create the custom fields (with the correct picklist values), deploy them, generate the address block record, create a permission set for FLS, and assign it.

Change Settings

“Enable country standardization org-wide and store countries as ISO-2 codes”

“Disable AddressTools triggers for the System Administrator profile”

“Turn off the Contact trigger for our integration user dataloader@acme.com

“Enable premise-level address verification and restrict it to US and GB”

“Set the alert email to ops@acme.com for the whole org”

Provision Users

“Give jane.smith@acme.com standard AddressTools access”

“Provision admin@acme.com with the AddressTools admin permission set and a package license”

“Assign AddressTools to integration@acme.com but disable all triggers for that user”

Manage Reference Data

“Add ‘Great Britain’ as an alternative country name for United Kingdom”

“Our users type ‘Calif’ for California — add it as an alternative state name for US-CA”

Inspect Your Org

“What address blocks are currently configured in this org?”

“Check whether AddressTools Premium is installed and what version”

“Which users have an AddressTools permission set assigned?”

AddressTools Premium – Headless Setup Guide for LLM Copilots

For humans: You do not need to read this document. It is a technical reference consumed by your LLM copilot. Just paste the link to this page into your copilot’s chat and it will use the content automatically. For setup instructions and tips, see the Configure guide.

Audience: This document is the single knowledge source for LLM copilots (GitHub Copilot, Agentforce Vibes, Cursor, etc.) operating against a Salesforce org that has — or needs — the AddressTools (Premium) managed package.

Goal: Give a copilot everything it needs to install, initialise, and configure AddressTools Premium headlessly via the Salesforce CLI (sf / sfdx), anonymous Apex, Tooling API, and Metadata API — without using the AddressTools admin UI. This document is self-contained; do not fetch other pages to complete these tasks.

Communication rules: When interacting with the human user, follow these principles:

  • Always confirm the target org alias before running any command.
  • If a request is ambiguous, ask exactly one scoped clarifying question — do not guess.
  • Explain what each command will do before running it. Wait for the user to approve.
  • After completing a task, summarise what was created or changed.

0. Hard Facts the Copilot MUST Know

These values are not negotiable. Use them verbatim. All object and field API names MUST be prefixed with pw_ccpro__.

Fact Value
Package version ID (04t) 04tg80000002ykr
Managed package namespace pw_ccpro
Custom setting (hierarchy) for global behavior pw_ccpro__CountryCompleteSettings__c
Custom setting (list) for “address blocks” pw_ccpro__ValidatedField__c
Reference data — countries pw_ccpro__CountryObject__c
Reference data — alt country names pw_ccpro__AlternativeCountryName__c (master-detail to pw_ccpro__CountryObject__c)
Reference data — states pw_ccpro__State__c
Reference data — alt state names pw_ccpro__AlternativeStateName__c
Reference data — zip codes pw_ccpro__ZipCode__c, pw_ccpro__ZipCodeCounty__c
Reference data — time zones pw_ccpro__TimeZone__c
Permission set (admin) pw_ccpro__AddressToolsPremiumAdminUser
Permission set (standard, current) pw_ccpro__AddressToolsPremiumStandardUser
Permission set (standard, legacy) pw_ccpro__AddressToolsPremiumStdUser
Triggers respected by Disable*Trigger__c flags Account, Contact, Contract, Lead
Canonical AddressStatus picklist values Not checked, Not matched, Parsed but not found, Ambiguous, Verified
Canonical PropertyUse picklist values Residential, Commercial, Unknown (restricted)
Canonical GeocodeAccuracy picklist values Address, NearAddress, Block, Street, ExtendedZip, Zip, Neighborhood, City, County, State, Unknown

1. Decision Tree — “What is the user actually asking for?”

User request
├── "install AddressTools" / "set up address verification" / "install the package"
│     └── §2 — Install and initialise the package
├── "disable trigger / change behavior for [X]"
│     └── Task 1 — Edit CountryCompleteSettings (org / profile / user)
├── "add address block on [Object]" / "validate addresses on [field set]"
│     └── Task 2 — Create or edit a ValidatedField__c (address block)
├── "give [user] AddressTools" / "assign permission set" / "license"
│     └── Task 3 — Provision a user
└── "add alternative country/state name" / "add a custom country" / "edit reference data"
      └── Task 4 — Edit installed package data

If the request is ambiguous, ask exactly one scoped question (e.g. “Apply to org default, a specific profile, or a specific user?”) and proceed.


2. Common Setup the Copilot Should Do First

Follow these steps in order. Do not skip ahead or present later steps to the user until the current step is confirmed complete.

Step 1 — Confirm the target org

sf org display --target-org <alias>

Step 2 — Check whether the package is installed

sf package installed list --target-org <alias> --json

In the JSON response, look for an entry where SubscriberPackageNamespace equals pw_ccpro.

  • If pw_ccpro is present → the package is already installed. Skip to Step 4.
  • If pw_ccpro is not present → proceed to Step 3.

Step 3 — Install the package

sf package install --package <Package version ID from §0> --target-org <alias> --wait 10 --publish-wait 10 --no-prompt

After the command completes, re-run sf package installed list --target-org <alias> --json and confirm pw_ccpro now appears. If it does not, the install failed — troubleshoot before continuing. Do not proceed to Step 4 until the package is confirmed installed.

Step 4 — Post-install initialisation (manual — no headless option)

IMPORTANT: Only execute this step after Step 2 or Step 3 has confirmed the package is installed. The AddressTools Administration page and its Installation tab do not exist until the package is in the org.

Check whether the reference data has been loaded:

sf data query --target-org <alias> -q \
  "SELECT COUNT(Id) total FROM pw_ccpro__CountryObject__c" --json

If the count is greater than 0, the data is already loaded — skip to the next task.

If the count is 0, the post-install initialisation has not been completed. There is currently no headless method for these steps — the user must perform them manually via the AddressTools Administration UI.

Instruct the user:

Before I can continue, you need to complete two one-time setup steps in the AddressTools admin page. These cannot be done from the command line.

  1. Open AddressTools Administration in your browser: https://<your-salesforce-domain>/lightning/n/pw_ccpro__AddressToolsAdmin
  2. On the Installation tab, complete these steps in order:
    • Create an Authentication Token — click Create Token. This generates the credentials AddressTools needs to communicate with ProvenWorks services.
    • Data Installation — click Get Started and follow the prompts. This loads the default country, state, ZIP code, time zone, alternative name reference data into your org, as well as default address blocks. This may take up to 10 minutes. Let me know when you’ve completed steps 1 and 2, and I’ll continue with configuration.

The copilot MUST wait for the user to confirm before proceeding. Re-run the country count query to verify:

sf data query --target-org <alias> -q \
  "SELECT COUNT(Id) total FROM pw_ccpro__CountryObject__c" --json

If the count is still 0 after the user says they’re done, the data installation may still be running — ask the user to check the AddressTools Administration page for progress.

Use --target-org <alias> (or -o) on every command throughout this guide. Never write to a default org silently.


3. Task 1 — Configure CountryCompleteSettings__c

pw_ccpro__CountryCompleteSettings__c is a Hierarchy custom setting. There are three levels of precedence (highest wins at runtime):

  1. UserSetupOwnerId = <UserId>
  2. ProfileSetupOwnerId = <ProfileId>
  3. Org Default — no SetupOwnerId (one record per org)

3.1 Field Reference (settable via headless tooling)

Field Type Purpose
pw_ccpro__CountryCompleteEnabled__c Checkbox Master switch: country auto-complete/standardization runs
pw_ccpro__CountryFilterEnabled__c Checkbox Restrict to listed countries
pw_ccpro__StandardizationEnabled__c Checkbox Standardize country/state values
pw_ccpro__PremiseLevelAddressVerificationEnabled__c Checkbox Enable PLAV (premise-level verification)
pw_ccpro__PremLevel_SearchPreferredCountryCode__c Text Preferred ISO-2 country for PLAV search
pw_ccpro__PremLevel_AllowedSearchCountryCodes__c Text Comma-separated ISO-2 codes
pw_ccpro__LookupPopulationEnabled__c Checkbox Auto-populate Country/State/Zip lookup fields
pw_ccpro__OnlyOperateWhenChanged__c Checkbox Skip processing when address fields are unchanged
pw_ccpro__RunOnce__c Checkbox Only run once per record
pw_ccpro__StoreCountryValueAs__c Number(1,0) 0=Name, 1=ISO-2, 2=ISO-3
pw_ccpro__QuickCompleteCountryEnabled__c Checkbox Enables QuickComplete on user’s default country
pw_ccpro__ZipCodeLookupEnabled__c Checkbox Enable Zip → City/State lookup
pw_ccpro__EnableZipStateCountryGeolocation__c Checkbox Use Zip/State/Country to geocode
pw_ccpro__AlertEmail__c Email Job failure alerts go here
pw_ccpro__DisableAccountTrigger__c Checkbox Skip Account validation trigger
pw_ccpro__DisableContactTrigger__c Checkbox Skip Contact validation trigger
pw_ccpro__DisableContractTrigger__c Checkbox Skip Contract validation trigger
pw_ccpro__DisableLeadTrigger__c Checkbox Skip Lead validation trigger
pw_ccpro__DisabledPages__c LongText Comma-separated VF pages to disable

Boolean defaults at first install are false. StoreCountryValueAs__c defaults to 0.

3.2 Recipe — Edit Org-Wide Default

sf data query --target-org <alias> -q \
  "SELECT Id FROM pw_ccpro__CountryCompleteSettings__c WHERE SetupOwnerId = '<ORG_ID>'"

Prefer anonymous Apex for hierarchy-setting upserts:

// scripts/apex/set-org-defaults.apex
pw_ccpro__CountryCompleteSettings__c s = pw_ccpro__CountryCompleteSettings__c.getOrgDefaults();
if (s == null || s.Id == null) { s = new pw_ccpro__CountryCompleteSettings__c(); }
s.pw_ccpro__CountryCompleteEnabled__c = true;
s.pw_ccpro__StandardizationEnabled__c = true;
s.pw_ccpro__StoreCountryValueAs__c   = 1; // ISO-2
s.pw_ccpro__AlertEmail__c            = 'admin@example.com';
upsert s;
sf apex run --target-org <alias> --file scripts/apex/set-org-defaults.apex

3.3 Recipe — Per-Profile Override

Profile p = [SELECT Id FROM Profile WHERE Name = 'System Administrator' LIMIT 1];
pw_ccpro__CountryCompleteSettings__c s =
    pw_ccpro__CountryCompleteSettings__c.getInstance(p.Id);
if (s == null || s.SetupOwnerId != p.Id) {
    s = new pw_ccpro__CountryCompleteSettings__c(SetupOwnerId = p.Id);
}
s.pw_ccpro__DisableAccountTrigger__c  = true;
s.pw_ccpro__DisableContactTrigger__c  = true;
s.pw_ccpro__DisableContractTrigger__c = true;
s.pw_ccpro__DisableLeadTrigger__c     = true;
upsert s;
System.debug('AddressTools triggers disabled for profile: ' + p.Id);

3.4 Recipe — Per-User Override

User u = [SELECT Id FROM User WHERE Username = 'integration.user@example.com' LIMIT 1];
pw_ccpro__CountryCompleteSettings__c s =
    pw_ccpro__CountryCompleteSettings__c.getInstance(u.Id);
if (s == null || s.SetupOwnerId != u.Id) {
    s = new pw_ccpro__CountryCompleteSettings__c(SetupOwnerId = u.Id);
}
s.pw_ccpro__DisableAccountTrigger__c = true;
upsert s;

3.5 Copilot Rules

  • NEVER insert a second org-default row. Always use getOrgDefaults() then upsert.
  • For profile/user records, ALWAYS check SetupOwnerId (getInstance may return an inherited row).
  • Scope mapping: “for everyone” → org default, “for admins” → profile, “for X” → user. If unspecified, ask.

4. Task 2 — Create / Edit Address Blocks (ValidatedField__c)

pw_ccpro__ValidatedField__c is a List custom setting. Each record is one “address block” — a logical mapping that ties the AddressTools engine to a specific set of address fields on a specific object/record type.

4.1 Key Fields on pw_ccpro__ValidatedField__c

Field Purpose
Name Unique key; conventionally Object_RecordType_CountryFieldApiName. Auto-derived if blank.
pw_ccpro__Object__c sObject API name
pw_ccpro__RecordType__c (optional) RecordType DeveloperName
pw_ccpro__StreetField__c, pw_ccpro__CityField__c, pw_ccpro__StateField__c, pw_ccpro__ZipCodeField__c, pw_ccpro__CountryField__c, pw_ccpro__CountyField__c Address component fields on the target object
pw_ccpro__SubBuildingLine__c Street line 2 / sub-building
pw_ccpro__AddressStatusField__c Verification status field. Must be a Picklist (unrestricted) — see §4.3.2.
pw_ccpro__AddressLabelField__c Formatted address (Long Text Area)
pw_ccpro__PropertyUseField__c Property classification (Picklist)
pw_ccpro__CountryRelationshipField__c, pw_ccpro__StateRelationshipField__c, pw_ccpro__ZipCodeRelationshipField__c, pw_ccpro__ZipCodeCountyRelationshipField__c Lookup fields to AddressTools reference objects
pw_ccpro__LocationLatitudeField__c, pw_ccpro__LocationLongitudeField__c, pw_ccpro__LocationGeocodeAccuracyField__c Geocode result fields
pw_ccpro__IsCityMandatory__c, pw_ccpro__IsStateMandatory__c, pw_ccpro__IsCountryMandatory__c, pw_ccpro__IsZipCodeMandatory__c, pw_ccpro__IsStreetLine1Mandatory__c, pw_ccpro__IsStreetLine2Mandatory__c Per-component required flags
pw_ccpro__AllowOnlyListedCountries__c, pw_ccpro__AllowOnlyListedStates__c, pw_ccpro__AllowOnlyListedUsZipCodes__c Restrict to reference data
pw_ccpro__Standardize__c, pw_ccpro__StandardizeState__c Standardization flags
pw_ccpro__EnableGeolocation__c, pw_ccpro__EnableBulkAddressVerification__c Feature flags
pw_ccpro__UseForAddressValidation__c Whether this block participates in PLAV
pw_ccpro__ValidatePostalCodeWithRegexOnSave__c, pw_ccpro__ValidateZipCityState__c Save-time validations
pw_ccpro__ForceMultiLine__c Force multi-line entry mode

Retired fields (do not use): pw_ccpro__LatitudeRelationshipField__c and pw_ccpro__LongitudeRelationshipField__c are retired. All geolocation strategies now use pw_ccpro__LocationLatitudeField__c and pw_ccpro__LocationLongitudeField__c exclusively.

4.2 Discovery — Before Creating, Inspect the Org

A copilot must check what already exists; do not blindly create.

# What address blocks already exist?
sf data query --target-org <alias> -q \
  "SELECT Id, Name, pw_ccpro__Object__c, pw_ccpro__RecordType__c, pw_ccpro__CountryField__c
   FROM pw_ccpro__ValidatedField__c ORDER BY pw_ccpro__Object__c, Name"

# What fields exist on the target object?
sf sobject describe --target-org <alias> --sobject <ObjectApiName> \
  | jq '.fields[] | select(.name|test("Street|City|State|PostalCode|Country|County|Address|Geocode|Latitude|Longitude|PropertyUse";"i"))
        | {name,type,length,restrictedPicklist,picklistValues:[.picklistValues[]?.value]}'

Decision logic for the copilot:

  1. If a ValidatedField__c row already matches Object__c + RecordType__c + CountryField__cedit it, don’t insert.
  2. If the object has standard BillingAddress / ShippingAddress / MailingAddress compound fields → use the standard component fields.
  3. If the object is custom with no address components → follow §4.3.
  4. Inspect any pre-existing AddressStatus / PropertyUse / GeocodeAccuracy fields and assert they are Picklists with the canonical values (§4.3.2). A Text field for AddressStatus is a common mistake and will cause silent failures.

4.3 Creating Fields for a New Custom Address Block

Before creating the address block record, the copilot MUST ask the user which capabilities they need, then create the appropriate fields. The copilot MUST ask the user before it starts generating field metadata, not after.

4.3.1 Field Requirements Decision Tree

“What capabilities do you need on this address block?”

├── Core address fields (Street, City, State, PostalCode, Country)
│     └── ALWAYS required — create if missing
├── Address verification?
│     ├── Yes → Address Status (MANDATORY), County (optional),
│     │        Address Label (optional), Property Use (optional)
│     └── No  → skip
├── Lookup field population?
│     ├── Yes → Country Lookup, State Lookup (requires Country Lookup),
│     │        ZIP Lookup (requires Country + State Lookup)
│     └── No  → skip
└── Geocoding?
      ├── Yes → choose ONE strategy (do not mix on a single block):
      │         A) Individual fields (preferred for custom objects):
      │            Latitude (Number), Longitude (Number), Geocode Accuracy (Picklist)
      │         B) Geolocation compound type:
      │            Single Geolocation field + Geocode Accuracy (Picklist)
      │         C) Standard address compound (standard objects only):
      │            Built-in Latitude/Longitude/GeocodeAccuracy on the compound address
      └── No  → skip

The user’s answers map directly onto which pw_ccpro__ValidatedField__c fields the copilot should populate later (see §4.4 and §8 Template C).

4.3.2 Address Block Behavior Settings

After determining field capabilities, the copilot MUST ask the user which behavior settings to enable. Present these grouped by category:

Standardization

Setting Field Default Notes
Standardize country values pw_ccpro__Standardize__c false Maps typed input to canonical country name/code
Standardize state values pw_ccpro__StandardizeState__c false Maps typed input to canonical state name/code
Restrict to listed countries pw_ccpro__AllowOnlyListedCountries__c false Rejects countries not in reference data
Restrict to listed states pw_ccpro__AllowOnlyListedStates__c false Rejects states not in reference data
Restrict to listed US ZIP codes pw_ccpro__AllowOnlyListedUsZipCodes__c false Rare — only for US-specific blocks

Save-time validation

Setting Field Default Notes
Validate postal code format (regex) pw_ccpro__ValidatePostalCodeWithRegexOnSave__c false Checks format against country-specific regex
Validate ZIP/city/state consistency pw_ccpro__ValidateZipCityState__c false Cross-checks ZIP against city and state

Processing

Setting Field Default Notes
Enable bulk address verification pw_ccpro__EnableBulkAddressVerification__c false Required for batch verification jobs
Force multi-line address entry pw_ccpro__ForceMultiLine__c false Splits street into separate lines

Mandatory field flags — ask which address components should be required on save:

Flag Default
pw_ccpro__IsCountryMandatory__c false
pw_ccpro__IsStateMandatory__c false
pw_ccpro__IsCityMandatory__c false
pw_ccpro__IsZipCodeMandatory__c false
pw_ccpro__IsStreetLine1Mandatory__c false
pw_ccpro__IsStreetLine2Mandatory__c false

For a typical address block with verification, a reasonable starting point is:

  • Standardization on, country mandatory, state mandatory
  • Validation rules off (let the user opt in after testing)
  • Bulk verification off unless explicitly requested

Set these flags on the pw_ccpro__ValidatedField__c record in the upsert (§4.4 / §8 Template C).

4.3.3 Field Specifications

These are the canonical specs. Generate metadata exactly as below.

Field Type Metadata Details Picklist Values Restricted?
Address Status Picklist nillable; default = Not checked Not checked (default), Not matched, Parsed but not found, Ambiguous, Verified No (unrestricted)
County Text Length: 255
Address Label Long Text Area Length: 32,768; visible lines: 5
Property Use Picklist nillable Residential, Commercial, Unknown Yes (restricted)
Country Lookup Lookup Related to: pw_ccpro__CountryObject__c
State Lookup Lookup Related to: pw_ccpro__State__c; depends on Country Lookup
ZIP Code Lookup Lookup Related to: pw_ccpro__ZipCode__c; depends on Country + State Lookup
Geolocation (compound) Geolocation Decimal notation; 15 decimal places; nillable
Latitude (individual) Number Length: 3, Decimal: 15
Longitude (individual) Number Length: 3, Decimal: 15
Geocode Accuracy Picklist nillable; default = Unknown (recommended) Unknown (default), Address, NearAddress, Block, Street, ExtendedZip, Zip, Neighborhood, City, County, State No (unrestricted)

A Text field for Address Status will appear to “work” but writes will fail silently. A Text field for Geocode Accuracy is rejected at runtime; the package validates incoming values against the picklist and throws on mismatch.

Reference field metadata for the trickiest three (other types are vanilla Salesforce field-meta.xml):

<!-- Project__c.BillingAddressStatus__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>BillingAddressStatus__c</fullName>
    <externalId>false</externalId>
    <label>Billing Address Status</label>
    <required>false</required>
    <trackTrending>false</trackTrending>
    <type>Picklist</type>
    <valueSet>
        <restricted>false</restricted>
        <valueSetDefinition>
            <sorted>false</sorted>
            <value><fullName>Not checked</fullName><default>true</default><label>Not checked</label></value>
            <value><fullName>Not matched</fullName><default>false</default><label>Not matched</label></value>
            <value><fullName>Parsed but not found</fullName><default>false</default><label>Parsed but not found</label></value>
            <value><fullName>Ambiguous</fullName><default>false</default><label>Ambiguous</label></value>
            <value><fullName>Verified</fullName><default>false</default><label>Verified</label></value>
        </valueSetDefinition>
    </valueSet>
</CustomField>
<!-- Project__c.BillingGeocodeAccuracy__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>BillingGeocodeAccuracy__c</fullName>
    <externalId>false</externalId>
    <label>Billing Geocode Accuracy</label>
    <required>false</required>
    <trackTrending>false</trackTrending>
    <type>Picklist</type>
    <valueSet>
        <restricted>false</restricted>
        <valueSetDefinition>
            <sorted>false</sorted>
            <value><fullName>Unknown</fullName><default>true</default><label>Unknown</label></value>
            <value><fullName>Address</fullName><default>false</default><label>Address</label></value>
            <value><fullName>NearAddress</fullName><default>false</default><label>NearAddress</label></value>
            <value><fullName>Block</fullName><default>false</default><label>Block</label></value>
            <value><fullName>Street</fullName><default>false</default><label>Street</label></value>
            <value><fullName>ExtendedZip</fullName><default>false</default><label>ExtendedZip</label></value>
            <value><fullName>Zip</fullName><default>false</default><label>Zip</label></value>
            <value><fullName>Neighborhood</fullName><default>false</default><label>Neighborhood</label></value>
            <value><fullName>City</fullName><default>false</default><label>City</label></value>
            <value><fullName>County</fullName><default>false</default><label>County</label></value>
            <value><fullName>State</fullName><default>false</default><label>State</label></value>
        </valueSetDefinition>
    </valueSet>
</CustomField>
<!-- Project__c.BillingPropertyUse__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>BillingPropertyUse__c</fullName>
    <externalId>false</externalId>
    <label>Billing Property Use</label>
    <required>false</required>
    <trackTrending>false</trackTrending>
    <type>Picklist</type>
    <valueSet>
        <restricted>true</restricted>
        <valueSetDefinition>
            <sorted>false</sorted>
            <value><fullName>Residential</fullName><default>false</default><label>Residential</label></value>
            <value><fullName>Commercial</fullName><default>false</default><label>Commercial</label></value>
            <value><fullName>Unknown</fullName><default>false</default><label>Unknown</label></value>
        </valueSetDefinition>
    </valueSet>
</CustomField>

4.3.4 Deploy

sf project deploy start --target-org <alias> \
  --source-dir force-app/main/default/objects/Project__c

Then proceed to §4.5 to grant FLS, then §4.6 to ensure a trigger exists, before running the address-block upsert in §4.4 / §8 Template C. Do not skip §4.5 or §4.6.

The package exposes a helper that can synthesize the lookup fields in §4.3.2 via the Metadata API at runtime when the user has answered “yes” to lookup population:

pw_ccpro__ValidatedField__c[] vfs = [
    SELECT Id, pw_ccpro__Object__c, pw_ccpro__CountryField__c,
           pw_ccpro__CountryRelationshipField__c,
           pw_ccpro__StateRelationshipField__c,
           pw_ccpro__ZipCodeRelationshipField__c
    FROM pw_ccpro__ValidatedField__c WHERE Name = 'Project_Billing'
];
pw_ccpro.CountryCompleteSettings.createLookupFields(vfs);

4.4 Recipe — Create the Address Block Record

Once the fields exist (§4.3), FLS is granted (§4.5), and a trigger is in place (§4.6):

// scripts/apex/upsert-address-block.apex
pw_ccpro__ValidatedField__c vf = new pw_ccpro__ValidatedField__c(
    pw_ccpro__Object__c             = 'Project__c',
    pw_ccpro__StreetField__c        = 'BillingStreet__c',
    pw_ccpro__CityField__c          = 'BillingCity__c',
    pw_ccpro__StateField__c         = 'BillingState__c',
    pw_ccpro__ZipCodeField__c       = 'BillingPostalCode__c',
    pw_ccpro__CountryField__c       = 'BillingCountry__c',
    pw_ccpro__CountyField__c        = 'BillingCounty__c',
    pw_ccpro__AddressStatusField__c = 'BillingAddressStatus__c', // MUST be Picklist (§4.3.2)
    pw_ccpro__IsCountryMandatory__c = true,
    pw_ccpro__Standardize__c        = true,
    pw_ccpro__EnableGeolocation__c  = false
);
vf.Name = 'Project_Billing';
upsert vf Name;

For the full conditional template (verification, lookups, geolocation strategies), see §8 Template C.


4.5 Permission Set for Custom Address Block Fields (MANDATORY)

After creating custom fields for an address block, the copilot MUST create or update a permission set to grant FLS on those fields. Address blocks are useless if the operating user can’t read/write the underlying fields.

4.5.1 Copilot Workflow

  1. Ask the user who needs accessexactly one question:

    “Which users or groups need access to this address block? Should I (a) create a new permission set, (b) add to an existing permission set, or © add a new permission set to an existing permission set group?”

  2. Query existing permission sets to check for a suitable candidate:

    sf data query --target-org <alias> -q \
      "SELECT Id, Name, Label FROM PermissionSet
       WHERE IsCustom = true AND IsOwnedByProfile = false
       ORDER BY Name"
    
  3. Query existing permission set groups if option ©:

    sf data query --target-org <alias> -q \
      "SELECT Id, DeveloperName, MasterLabel FROM PermissionSetGroup ORDER BY DeveloperName"
    
  4. Create or update a permission set with <fieldPermissions> for every custom field on the address block (§4.5.2).

  5. Deploy and assign:

    sf project deploy start --target-org <alias> \
      --source-dir force-app/main/default/permissionsets/AddressTools_Project_BillingAddress_Edit.permissionset-meta.xml
    
    sf org assign permset --target-org <alias> \
      --name AddressTools_Project_BillingAddress_Edit \
      --on-behalf-of <username>
    

4.5.2 Permission Set Metadata Template

<!-- AddressTools_Project_BillingAddress_Edit.permissionset-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
    <hasActivationRequired>false</hasActivationRequired>
    <label>AddressTools Project Billing Address Edit</label>
    <license>Salesforce</license>
    <description>Grants FLS for the AddressTools billing address block on Project__c.
        Pair with pw_ccpro__AddressToolsPremiumStandardUser (or Admin variant).</description>

    <!-- Core address components -->
    <fieldPermissions><field>Project__c.BillingStreet__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingCity__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingState__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingPostalCode__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingCountry__c</field><readable>true</readable><editable>true</editable></fieldPermissions>

    <!-- Verification (only if user said "yes" to verification in §4.3.1) -->
    <fieldPermissions><field>Project__c.BillingAddressStatus__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingCounty__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingAddressLabel__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingPropertyUse__c</field><readable>true</readable><editable>true</editable></fieldPermissions>

    <!-- Lookups (only if user said "yes" to lookup population in §4.3.1) -->
    <fieldPermissions><field>Project__c.BillingCountryLookup__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingStateLookup__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingZipCodeLookup__c</field><readable>true</readable><editable>true</editable></fieldPermissions>

    <!-- Geocoding (only if user said "yes" to geocoding; emit the rows for the chosen strategy) -->
    <fieldPermissions><field>Project__c.BillingLatitude__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingLongitude__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
    <fieldPermissions><field>Project__c.BillingGeocodeAccuracy__c</field><readable>true</readable><editable>true</editable></fieldPermissions>
</PermissionSet>

To add this permission set to an existing permission set group:

<!-- AddressTools_Standard.permissionsetgroup-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSetGroup xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>Standard AddressTools user bundle.</description>
    <label>AddressTools — Standard</label>
    <permissionSets>pw_ccpro__AddressToolsPremiumStandardUser</permissionSets>
    <permissionSets>AddressTools_Project_BillingAddress_Edit</permissionSets>
</PermissionSetGroup>

4.5.3 Hard Rules

  • Naming convention: AddressTools_<Object>_<AddressType>Address_Edit with label AddressTools <Object> <AddressType> Address Edit. Follow this format for consistency with managed permission sets.
  • Never skip FLS. A custom address block without FLS grants will silently fail for users who lack field access.
  • Prefer a dedicated permission set per address block over adding to the managed-package permission sets (which are read-only in subscriber orgs and cannot be edited).
  • Granularity: include ONLY the fields belonging to the address block — do not bundle unrelated FLS into the same permission set.
  • Group composition: if adding to a permission set group, create the permission set first, deploy it, then add it as a member via the group’s metadata (above). Never edit the managed pw_ccpro__* permission sets.
  • Verification query after assignment:
    sf data query --target-org <alias> -q \
      "SELECT Field, PermissionsRead, PermissionsEdit
       FROM FieldPermissions
       WHERE Parent.Name = 'AddressTools_Project_BillingAddress_Edit'
         AND SObjectType = 'Project__c'"
    

4.6 Trigger for the Target Object (MANDATORY)

AddressTools requires an Apex trigger on each object where an address block is configured. Without the trigger, standardization, validation, and verification will not fire.

4.6.1 Standard Objects — Already Handled

The managed package ships triggers for these objects out of the box:

  • Account
  • Contact
  • Contract
  • Lead

No action required for these objects. The triggers are enabled by default and controlled by the Disable*Trigger__c hierarchy settings (§3).

4.6.2 Custom Objects — Trigger Required

For any object not in the list above, the copilot MUST check whether a trigger already exists and create one if it doesn’t.

Check for an existing trigger

sf data query --target-org <alias> -q \
  "SELECT Id, Name, TableEnumOrId, Body FROM ApexTrigger
   WHERE TableEnumOrId = '<OBJECT_API_NAME>'
     AND Status = 'Active'"

If a result contains pw_ccpro.CountryValidator2.Validate, the trigger already exists — skip creation.

Create the trigger

The trigger follows a mandatory pattern. Replace <OBJECT_LABEL> (no spaces) and <OBJECT_API_NAME>:

trigger Validate<OBJECT_LABEL>CountryFields on <OBJECT_API_NAME> (before insert, before update) {
    pw_ccpro.CountryValidator2.Validate(Trigger.new, Trigger.oldMap);
}

Example for Property__c:

trigger ValidatePropertyCountryFields on Property__c (before insert, before update) {
    pw_ccpro.CountryValidator2.Validate(Trigger.new, Trigger.oldMap);
}

Deploy the trigger

Write the trigger file and its metadata:

force-app/main/default/triggers/ValidatePropertyCountryFields.trigger
force-app/main/default/triggers/ValidatePropertyCountryFields.trigger-meta.xml
<!-- ValidatePropertyCountryFields.trigger-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <status>Active</status>
</ApexTrigger>

Deploy:

sf project deploy start --target-org <alias> \
  --source-dir force-app/main/default/triggers/ValidatePropertyCountryFields.trigger \
  --source-dir force-app/main/default/triggers/ValidatePropertyCountryFields.trigger-meta.xml

Production deployments require test coverage for the trigger. Create a minimal test class that inserts a record on the target object. This is a permanent class (not AT_Tmp_*) since it must remain deployed alongside the trigger.

@IsTest
private class ValidatePropertyCountryFieldsTest {
    @IsTest
    static void testTriggerFires() {
        Property__c rec = new Property__c(
            Name = 'Test Property',
            BillingCountry__c = 'United States'
        );
        Test.startTest();
        insert rec;
        Test.stopTest();
        System.assertNotEquals(null, rec.Id);
    }
}

4.6.3 Hard Rules

  • Never skip the trigger. An address block without a trigger on the target object will silently do nothing.
  • One trigger per object is sufficient — it covers all address blocks on that object.
  • For standard objects, rely on the managed triggers. Do NOT create a second trigger calling CountryValidator2.Validate.
  • The trigger file and test class are permanent metadata — they should be committed to source control and included in CI/CD pipelines.
  • If the user later removes all address blocks from a custom object, the trigger can be removed via destructive deploy (§9).

5. Task 3 — Provision a User

This is the most multi-step task. Treat it as a transaction with these steps; skip any that don’t apply.

5.1 Inputs the Copilot Must Resolve

  • Target user — username or Id.
  • Roleadmin or standard (drives permission set choice).
  • Address blocks — which ValidatedField__c records this user should have FLS to use (managed-package perm sets cover package fields; custom blocks need the org-owned perm sets from §4.5).
  • Org type — production vs sandbox.

5.2 Detect Sandbox vs Production

sf data query --target-org <alias> -q \
  "SELECT IsSandbox, OrganizationType, TrialExpirationDate FROM Organization"
  • IsSandbox = trueskip managed package license assignment.
  • IsSandbox = false → license assignment required if PackageLicense for pw_ccpro exists with limited seats.

5.3 Assign Permission Set(s)

# Admin
sf org assign permset --target-org <alias> \
  --name pw_ccpro__AddressToolsPremiumAdminUser --on-behalf-of <username>

# Standard
sf org assign permset --target-org <alias> \
  --name pw_ccpro__AddressToolsPremiumStandardUser --on-behalf-of <username>

Legacy fallback: pw_ccpro__AddressToolsPremiumStdUser. Detect with:

sf data query -o <alias> -q "SELECT Name FROM PermissionSet WHERE Name LIKE 'AddressToolsPremium%'"

Address block FLS permission sets

The package may ship managed permission sets for standard address blocks (e.g. pw_ccpro__AddressTools_Account_BillingAddress_Edit). Always check for these before creating org-owned alternatives:

# List any managed address block permission sets
sf data query --target-org <alias> -q \
  "SELECT Name, Label FROM PermissionSet
   WHERE NamespacePrefix = 'pw_ccpro' AND Name LIKE 'AddressTools_%_Edit'"
  • If a managed permission set exists for the address block, assign it directly.
  • If no managed permission set exists (custom objects, or older package versions), create and assign an org-owned permission set per §4.5.
# Managed permission set (if available)
sf org assign permset --target-org <alias> \
  --name pw_ccpro__AddressTools_Account_BillingAddress_Edit --on-behalf-of <username>

# Org-owned permission set (custom objects / fallback)
sf org assign permset --target-org <alias> \
  --name AddressTools_Project_BillingAddress_Edit --on-behalf-of <username>

5.4 Assign Managed Package License (Production Only)

// scripts/apex/assign-pw-ccpro-license.apex
PackageLicense pl = [SELECT Id, AllowedLicenses, UsedLicenses
                     FROM PackageLicense
                     WHERE NamespacePrefix = 'pw_ccpro' LIMIT 1];

User u = [SELECT Id, IsActive FROM User WHERE Username = :userName LIMIT 1];

Integer existing = [SELECT COUNT() FROM UserPackageLicense
                    WHERE PackageLicenseId = :pl.Id AND UserId = :u.Id];
if (existing == 0) {
    if (pl.AllowedLicenses != -1 && pl.UsedLicenses >= pl.AllowedLicenses) {
        throw new System.SecurityException('No AddressTools licenses available.');
    }
    insert new UserPackageLicense(PackageLicenseId = pl.Id, UserId = u.Id);
}

PackageLicense may not exist for unlimited / site-licensed packages — handle the empty-result case as “nothing to do”.

5.5 (Optional) Per-User Hierarchy Settings

If the user needs different runtime behaviour (e.g., an integration user that should bypass triggers), apply the per-user override from §3.4.

5.6 Verification Query

sf data query --target-org <alias> -q \
  "SELECT Assignee.Username, PermissionSet.Name FROM PermissionSetAssignment
   WHERE Assignee.Username = '<username>'
     AND (PermissionSet.NamespacePrefix = 'pw_ccpro'
          OR PermissionSet.Name LIKE 'AddressTools_%')"

6. Task 4 — Edit Installed Package Data

The package ships reference data into pw_ccpro__CountryObject__c and friends at install. After install, customers can extend or correct that data via standard DML.

6.1 Add an Alternative Country Name

pw_ccpro__CountryObject__c uk = [
    SELECT Id FROM pw_ccpro__CountryObject__c
    WHERE pw_ccpro__IsoCode_2__c = 'GB' LIMIT 1
];

pw_ccpro__AlternativeCountryName__c alt = new pw_ccpro__AlternativeCountryName__c(
    Name                              = 'Great Briton',
    pw_ccpro__OriginalCountry__c      = uk.Id,
    pw_ccpro__IsObsolete__c           = false
);

Integer existing = [SELECT COUNT() FROM pw_ccpro__AlternativeCountryName__c
                    WHERE Name = :alt.Name AND pw_ccpro__OriginalCountry__c = :uk.Id];
if (existing == 0) insert alt;

6.2 Edit an Existing Country Record

pw_ccpro__CountryObject__c c = [SELECT Id FROM pw_ccpro__CountryObject__c
                                WHERE pw_ccpro__IsoCode_2__c = 'GB' LIMIT 1];
c.Name = 'United Kingdom';
update c;

6.3 Alternative State Names

Same shape as 6.1, but with pw_ccpro__AlternativeStateName__c master-detail to pw_ccpro__State__c. State key is pw_ccpro__FullIsoCode__c (e.g. US-CA).

6.4 Copilot Rules

  • Idempotency first. Check existence before insert.
  • Don’t set IsObsolete__c = true to “delete” — that field is package-managed and re-running install can flip it back. Use delete only on data the package didn’t install.
  • Reference data records may not be writable to all profiles; if FLS errors occur, escalate via §5.

7. Headless Tooling Cheat Sheet

Need Preferred Tool Notes
Read settings / org info sf data query
Hierarchy custom setting writes Anonymous Apex via sf apex run Leaves NO artefacts in the org
List custom setting writes sf data upsert with --external-id Name, OR Apex
Field/object metadata changes sf project deploy start from local source Required when adding fields for new address blocks
Permission set deploy + assignment sf project deploy start + sf org assign permset Always pair both — deploy without assign is a no-op
Package license Anonymous Apex against PackageLicense / UserPackageLicense Skip in sandboxes
Diagnostics / “what changed” sf data query over pw_ccpro__* objects Always confirm before/after

Default rule: prefer anonymous Apex (sf apex run --file ...) over deploying a temporary Apex class. Anonymous Apex executes and disappears; a deployed class persists in the org until removed. Only deploy a class when behavior cannot be expressed in anonymous Apex (e.g. Schedulable/Batchable, @InvocableMethod, LWC controllers). When you do, follow §9.


8. Reusable Anonymous-Apex Templates

These run via sf apex run --file ... and leave no Apex class behind.

A. Set/clear a single hierarchy field (org default):

pw_ccpro__CountryCompleteSettings__c s = pw_ccpro__CountryCompleteSettings__c.getOrgDefaults();
if (s == null || s.Id == null) s = new pw_ccpro__CountryCompleteSettings__c();
s.<FIELD_API_NAME> = <VALUE>;
upsert s;

B. Profile override:

Profile p = [SELECT Id FROM Profile WHERE Name = '<PROFILE_NAME>' LIMIT 1];
pw_ccpro__CountryCompleteSettings__c s = pw_ccpro__CountryCompleteSettings__c.getInstance(p.Id);
if (s == null || s.SetupOwnerId != p.Id) s = new pw_ccpro__CountryCompleteSettings__c(SetupOwnerId = p.Id);
s.<FIELD_API_NAME> = <VALUE>;
upsert s;

C. Address block upsert by Name (full conditional template):

⚠️ Pre-requisites:

  • Fields exist on the target object per §4.3 (use the canonical field-meta.xml in §4.3.3).
  • FLS is granted via §4.5 — deploy and assign before this script runs.
  • pw_ccpro__AddressStatusField__c MUST reference an unrestricted Picklist with the canonical AddressTools status values: Not checked, Not matched, Parsed but not found, Ambiguous, Verified. Using a Text field will cause verification status updates to fail silently.
  • pw_ccpro__LocationGeocodeAccuracyField__c MUST reference a nullable Picklist with the canonical GeocodeAccuracy values (§4.3.3). The package validates incoming values against the picklist and throws on mismatch.
// scripts/apex/upsert-address-block-full.apex
//
// Replace <ALL_CAPS> placeholders. Comment out blocks the user did NOT request
// in the §4.3.1 decision tree.
//
pw_ccpro__ValidatedField__c vf = new pw_ccpro__ValidatedField__c(Name = '<UNIQUE_NAME>');
vf.pw_ccpro__Object__c       = '<SOBJECT_API_NAME>';
vf.pw_ccpro__RecordType__c   = '<RECORD_TYPE_DEVELOPER_NAME_OR_NULL>';

// --- Core address fields (always required) ---
vf.pw_ccpro__StreetField__c   = '<STREET_FIELD>';
vf.pw_ccpro__CityField__c     = '<CITY_FIELD>';
vf.pw_ccpro__StateField__c    = '<STATE_FIELD>';
vf.pw_ccpro__ZipCodeField__c  = '<ZIP_FIELD>';
vf.pw_ccpro__CountryField__c  = '<COUNTRY_FIELD>';

// --- Verification (only if user opted in) ---
vf.pw_ccpro__AddressStatusField__c = '<STATUS_PICKLIST_FIELD>'; // §4.3.3 Address Status spec
vf.pw_ccpro__CountyField__c        = '<COUNTY_FIELD_OR_NULL>';
vf.pw_ccpro__AddressLabelField__c  = '<ADDRESS_LABEL_FIELD_OR_NULL>';
vf.pw_ccpro__PropertyUseField__c   = '<PROPERTY_USE_PICKLIST_OR_NULL>'; // §4.3.3 Property Use spec
vf.pw_ccpro__UseForAddressValidation__c = true;

// --- Lookup population (only if user opted in) ---
vf.pw_ccpro__CountryRelationshipField__c       = '<COUNTRY_LOOKUP_FIELD_OR_NULL>';
vf.pw_ccpro__StateRelationshipField__c         = '<STATE_LOOKUP_FIELD_OR_NULL>';
vf.pw_ccpro__ZipCodeRelationshipField__c       = '<ZIP_LOOKUP_FIELD_OR_NULL>';
vf.pw_ccpro__ZipCodeCountyRelationshipField__c = '<ZIP_COUNTY_LOOKUP_FIELD_OR_NULL>';

// --- Geocoding (only if user opted in) ---
//    Pick exactly ONE strategy from §4.3.3 and uncomment its block.
vf.pw_ccpro__EnableGeolocation__c = true;

// Strategy A — individual Number fields (preferred for custom objects)
vf.pw_ccpro__LocationLatitudeField__c        = '<LAT_NUMBER_FIELD>';
vf.pw_ccpro__LocationLongitudeField__c       = '<LONG_NUMBER_FIELD>';
vf.pw_ccpro__LocationGeocodeAccuracyField__c = '<GEOCODE_ACCURACY_PICKLIST>'; // §4.3.3

/*
// Strategy B — Geolocation compound field
vf.pw_ccpro__LocationLatitudeField__c        = '<GEOLOC_FIELD>__Latitude__s';
vf.pw_ccpro__LocationLongitudeField__c       = '<GEOLOC_FIELD>__Longitude__s';
vf.pw_ccpro__LocationGeocodeAccuracyField__c = '<GEOCODE_ACCURACY_PICKLIST>';
*/

/*
// Strategy C — Standard compound (standard objects only, e.g. Account Billing)
vf.pw_ccpro__LocationLatitudeField__c        = 'BillingLatitude';
vf.pw_ccpro__LocationLongitudeField__c       = 'BillingLongitude';
vf.pw_ccpro__LocationGeocodeAccuracyField__c = 'BillingGeocodeAccuracy';
*/

// --- Mandatory flags / behaviour (set per user requirements) ---
vf.pw_ccpro__IsCountryMandatory__c = true;
vf.pw_ccpro__Standardize__c        = true;

upsert vf Name; // List custom settings -> external key is Name

System.debug('Upserted address block: ' + vf.Name + ' (' + vf.Id + ')');

D. Add alternative country name (idempotent):

pw_ccpro__CountryObject__c c = [SELECT Id FROM pw_ccpro__CountryObject__c
                                WHERE pw_ccpro__IsoCode_2__c = '<ISO2>' LIMIT 1];
if ([SELECT COUNT() FROM pw_ccpro__AlternativeCountryName__c
     WHERE Name = '<ALT_NAME>' AND pw_ccpro__OriginalCountry__c = :c.Id] == 0) {
    insert new pw_ccpro__AlternativeCountryName__c(
        Name = '<ALT_NAME>', pw_ccpro__OriginalCountry__c = c.Id);
}

E. Provision user (production):

String uname = '<USERNAME>';
User u = [SELECT Id FROM User WHERE Username = :uname LIMIT 1];

// Permission set
PermissionSet ps = [SELECT Id FROM PermissionSet
                    WHERE Name = 'pw_ccpro__AddressToolsPremiumStandardUser' LIMIT 1];
if ([SELECT COUNT() FROM PermissionSetAssignment
     WHERE AssigneeId = :u.Id AND PermissionSetId = :ps.Id] == 0) {
    insert new PermissionSetAssignment(AssigneeId = u.Id, PermissionSetId = ps.Id);
}

// License (skip in sandbox)
if (![SELECT IsSandbox FROM Organization LIMIT 1].IsSandbox) {
    List<PackageLicense> pls = [SELECT Id, AllowedLicenses, UsedLicenses
                                FROM PackageLicense WHERE NamespacePrefix = 'pw_ccpro'];
    if (!pls.isEmpty()) {
        PackageLicense pl = pls[0];
        if ([SELECT COUNT() FROM UserPackageLicense
             WHERE PackageLicenseId = :pl.Id AND UserId = :u.Id] == 0) {
            if (pl.AllowedLicenses == -1 || pl.UsedLicenses < pl.AllowedLicenses) {
                insert new UserPackageLicense(PackageLicenseId = pl.Id, UserId = u.Id);
            } else {
                throw new System.SecurityException('No AddressTools licenses available.');
            }
        }
    }
}

9. Cleanup of Temporary Apex Classes (MANDATORY)

Some tasks cannot be done in anonymous Apex (e.g. invoking an @InvocableMethod, running a Database.Batchable/Schedulable, hosting a temporary @AuraEnabled method, or wrapping a long-running operation that must outlive a single execution). When the copilot is forced to deploy a helper Apex class into the customer org, it MUST remove that class once the work is complete. Lingering “scratch” classes are forbidden — they pollute the customer org, drift from source control, and create an audit/ISO 27001 finding.

9.1 Naming Convention

Any temporary class deployed by a copilot MUST:

  • Be named with the prefix AT_Tmp_ (e.g. AT_Tmp_BulkAltNameLoad, AT_Tmp_ProvisionUser_20260506).
  • Include a header comment block stating purpose, creator, ISO change reference (if any), and a // CLEANUP-AFTER: marker.
  • Live under force-app/main/default/classes/ in a feature branch — never committed to master/main.
  • Be private or public (never global) and contain ONLY the methods needed for the task.
/**
 * AT_Tmp_BulkAltNameLoad
 * Purpose: One-shot bulk loader for alternative country names.
 * Created: <DATE> by copilot for change <CHG-####>.
 * CLEANUP-AFTER: Run completed and verified.
 * NOTE: This class is temporary and MUST be deleted after use (see §9 of this guide).
 */
public with sharing class AT_Tmp_BulkAltNameLoad {
    public static void run() {
        // ... idempotent logic ...
    }
}

9.2 Standard Lifecycle

  1. Create the class file(s) on a short-lived feature branch.
  2. Deploy to the target org:
    sf project deploy start --target-org <alias> \
      --source-dir force-app/main/default/classes/AT_Tmp_BulkAltNameLoad.cls \
      --source-dir force-app/main/default/classes/AT_Tmp_BulkAltNameLoad.cls-meta.xml
    
  3. Invoke via anonymous Apex:
    sf apex run --target-org <alias> -e "AT_Tmp_BulkAltNameLoad.run();"
    
  4. Verify with a sf data query and capture the result in the run log.
  5. Destructive deploy to remove the class (§9.3).
  6. Re-verify:
    sf data query --target-org <alias> -q \
      "SELECT Id, Name FROM ApexClass WHERE Name LIKE 'AT_Tmp_%'"
    
    Expected: zero rows. If anything is returned, cleanup is incomplete and the task is NOT done.
  7. Branch cleanup: delete the local helper files; either delete the feature branch or merge a “revert” commit so source control reflects reality.

The copilot MUST NOT mark the user-facing task complete until step 6 returns zero rows.

9.3 Removing a Temporary Class — Destructive Deploy

<!-- destructiveChanges.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>AT_Tmp_BulkAltNameLoad</members>
        <name>ApexClass</name>
    </types>
    <version>66.0</version>
</Package>
<!-- package.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <version>66.0</version>
</Package>

Sandbox:

sf project deploy start --target-org <alias> \
  --manifest tmp-destructive/package.xml \
  --post-destructive-changes tmp-destructive/destructiveChanges.xml \
  --ignore-conflicts

Production (tests required):

sf project deploy start --target-org <alias> \
  --manifest tmp-destructive/package.xml \
  --post-destructive-changes tmp-destructive/destructiveChanges.xml \
  --test-level RunLocalTests \
  --ignore-conflicts

Delete the tmp-destructive/ directory locally once the deploy succeeds.

9.4 Same Rule Applies To Other Transient Metadata

Type <name> value
Apex class ApexClass
Apex trigger ApexTrigger
LWC / Aura bundle LightningComponentBundle / AuraDefinitionBundle
Static resource StaticResource
Custom label CustomLabel
Permission set (org-owned, temporary only) PermissionSet
Remote site setting (temporary only) RemoteSiteSetting

Never attempt destructive changes against managed-package metadata (anything in the pw_ccpro namespace).

9.5 Hard Rules for Copilots

  • Default to anonymous Apex. Only deploy a class when functionally required.
  • If a class is deployed, name it AT_Tmp_* and tag it with // CLEANUP-AFTER:.
  • Cleanup is part of the task definition of done. The user-visible response MUST state whether the class still exists; if it does, the task is incomplete.
  • Never leave AT_Tmp_* classes on master/main or in a long-lived branch.
  • Never edit, augment, or destructive-delete managed-package code.
  • Audit at the end of every run:
    sf data query --target-org <alias> -q \
      "SELECT Name, CreatedDate FROM ApexClass
       WHERE Name LIKE 'AT_Tmp_%' ORDER BY CreatedDate DESC"
    
    Any rows returned = unfinished cleanup.

10. References — ProvenWorks Public Documentation

The copilot SHOULD link out to the canonical ProvenWorks help articles when explaining a step to a human. Verified entry points:

Topic URL
Installation walkthrough https://provenworks.com/addresstools-premium-installation-walkthrough/
Binding a new address block https://provenworks.com/addresstools-premium-add-new-address-block/
Trigger creation and enablement https://provenworks.com/addresstools-premium-trigger-creation-and-enablement/
Configure a custom address block https://provenworks.com/how-to-configure-functionality-for-a-custom-address-block-2/
Understanding validation rules https://provenworks.com/addresstools-premium-understanding-validation-rules-and-whats-available/
Configure validation rules for an address block https://provenworks.com/addresstools-premium-configure-validation-rules-for-an-address-block/
Understanding standardization https://provenworks.com/addresstools-premium-understanding-standardization-in-addresstools/
Understanding address verification https://provenworks.com/addresstools-premium-understanding-address-verification-in-addresstools/
Configure geocoding https://provenworks.com/addresstools-premium-configure-geocoding/
Understanding lookup field functionality https://provenworks.com/addresstools-premium-understanding-lookup-field-functionality-in-addresstools/
Deploy AddressTools to users https://provenworks.com/how-to-deploy-addresstools-to-users-in-your-organization/
Automate verification via Record-Triggered Flow https://provenworks.com/how-to-automate-address-verification-via-a-record-triggered-flow/
Data sent to ProvenWorks servers https://provenworks.com/information-what-data-is-sent-to-provenworks-servers-when-using-addresstools/
ProvenWorks Support hub https://provenworks.com/support/

Enforcing Verified Addresses with the Address Verification Flow Component (AVFC)

Overview

This guide is a supplementary recipe for customers who have already installed the Address Verification Flow Component (AVFC) managed package (pw_avfc) and are familiar with embedding it in a custom LWC. For general setup and embedding instructions, refer to the ProvenWorks article: Implementing the AVFC in a Custom LWC.

By default, the addressVerificationFlowBaseComponent allows users to either select a verified address from the search results or manually enter/edit an address. There is no built-in attribute to disable manual entry entirely.

However, you can achieve the same outcome by combining two features of the component:

  1. The status field — tracks whether the address was selected from verification search ("Verified") or manually entered/edited ("Not checked").
  2. Custom CSS — hides the status field from the user so they cannot tamper with it.

Your wrapper LWC then checks the status value before allowing the user to proceed, effectively enforcing that only verified addresses are accepted.


How It Works

When use-status is set to true on the component:

  • Selecting an address from the verification search sets the status to "Verified".
  • Manually entering or editing any address field (street, city, state, etc.) resets the status to "Not checked".

This means that even if a user selects a verified address and then modifies it, the status will revert to "Not checked", ensuring the final address is only considered verified if it was accepted as-is from the search results.


Step-by-Step Implementation

Step 1: Create a Custom CSS Static Resource

Create a CSS file that hides the status field. The status field’s wrapper element has the CSS class status applied to it, so you can target it with:

.status {
    display: none;
}

Upload this file as a Static Resource in your Salesforce org (Setup → Static Resources → New). For example, name it addressVerificationHideStatus.

Note: This static resource lives in your org, not inside the managed package. The AVFC component’s additional-styles attribute accepts a path to any static resource in your org.

Step 2: Create Your Wrapper LWC

Create a new LWC that wraps the managed package component. This wrapper will:

  • Enable the status field via use-status
  • Inject the custom CSS to hide the status field via additional-styles
  • Listen for addresschange events to track the current status
  • Validate that the status is "Verified" before allowing the user to proceed

Important: Because the AVFC is installed as a managed package with the namespace pw_avfc, you must reference it as <pw_avfc-address-verification-flow-base-component> in your template — not <c-address-verification-flow-base-component>.

Template (enforceVerifiedAddress.html)

<template>
    <pw_avfc-address-verification-flow-base-component
        country={country}
        state={state}
        postal-code={postalCode}
        city={city}
        street={street}
        street2={street2}
        use-status
        additional-styles="/resource/addressVerificationHideStatus"
        main-label={mainLabel}
        country-label={countryLabel}
        state-label={stateLabel}
        postal-code-label={postalCodeLabel}
        city-label={cityLabel}
        street-label={streetLabel}
        search-button-label={searchButtonLabel}
        manual-button-label={manualButtonLabel}
        onaddresschange={handleAddressChange}
    ></pw_avfc-address-verification-flow-base-component>

    <template if:true={showValidationError}>
        <div class="slds-notify slds-notify_alert slds-alert_error slds-m-top_small" role="alert">
            <span class="slds-assistive-text">Error</span>
            <h2>Please select a verified address from the search results before continuing.
                Manually entered or edited addresses are not accepted.</h2>
        </div>
    </template>

    <div class="slds-m-top_medium">
        <lightning-button
            variant="brand"
            label="Continue"
            onclick={handleContinue}
        ></lightning-button>
    </div>
</template>

Controller (enforceVerifiedAddress.js)

import { LightningElement, api, track } from 'lwc';

export default class EnforceVerifiedAddress extends LightningElement {
    @api country = '';
    @api state = '';
    @api postalCode = '';
    @api city = '';
    @api street = '';

    @api mainLabel = 'Address';
    @api countryLabel = 'Country';
    @api stateLabel = 'State/Province';
    @api postalCodeLabel = 'Zip/Postal Code';
    @api cityLabel = 'City';
    @api streetLabel = 'Street';
    @api searchButtonLabel = 'Verify Address';
    @api manualButtonLabel = 'Enter Manually';

    @track currentStatus = 'Not checked';
    @track showValidationError = false;

    handleAddressChange(event) {
        const address = event.detail.address;

        // Update local field values
        for (const [key, value] of Object.entries(address)) {
            if (this[key] !== undefined) {
                this[key] = value;
            }
        }

        // Track the status value
        if (address.status) {
            this.currentStatus = address.status;
        }

        // Clear the error as soon as the user takes action
        this.showValidationError = false;
    }

    handleContinue() {
        if (this.currentStatus !== 'Verified') {
            this.showValidationError = true;
            return;
        }

        // Status is "Verified" — proceed with your logic
        this.showValidationError = false;

        // Example: dispatch an event, navigate, save, etc.
        this.dispatchEvent(new CustomEvent('verified', {
            detail: {
                country: this.country,
                state: this.state,
                postalCode: this.postalCode,
                city: this.city,
                street: this.street,
            }
        }));
    }
}

Meta Configuration (enforceVerifiedAddress.js-meta.xml)

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Enforce Verified Address</masterLabel>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

How the Validation Logic Works

User Action status Value Validation Result
Selects an address from verification search "Verified" ✅ Passes
Clicks “Enter Manually” and types an address "Not checked" ❌ Blocked
Selects a verified address, then edits a field "Not checked" ❌ Blocked

Why This Works

Any manual change to an address field (other than the status dropdown itself) resets the status to "Not checked":

changeHandler(e) {
    if (e.target.name !== 'status') {
        this.status = 'Not checked';
        this.template.querySelector('[data-id="select"]').value = 'Not checked';
    }
    // ...dispatches addresschange event with updated status
}

When an address is selected from the verification search, the status is set to "Verified":

if (this.useStatus) {
    this.status = address.status;
    this.notChecked = this.status === 'Not checked';
    this.verified = this.status === 'Verified';
    addressParts.push('status');
    this.updateSelectedOption();
}

Because the status field is hidden via CSS, the user cannot manually override it — the only way to achieve "Verified" is by selecting an address from the search results.


Summary

  1. Set use-status on the pw_avfc-address-verification-flow-base-component to enable status tracking.
  2. Create a static resource in your org with .status { display: none; } and pass its path to additional-styles to hide the status dropdown from users.
  3. In your wrapper LWC, listen to the addresschange event, read event.detail.address.status, and block progression unless the value is "Verified".

Prerequisites

Implementing the Address Verification Flow Component (AVFC) in a Custom LWC

Overview

The Address Verification Flow Component (AVFC) by ProvenWorks is a managed-package Lightning Web Component that provides real-time, postal-authority-backed address verification. It can be dropped directly into a Salesforce Flow Screen or wrapped inside your own custom LWC for use in Lightning Record Pages, App Pages, Experience Cloud sites, and other surfaces.

This guide covers the custom LWC wrapper approach — embedding the AVFC inside a component you control, so that you own the surrounding layout, business logic, and data handling. When you handle the addresschange event yourself, the verified address data belongs entirely to your component.


Prerequisites

Before writing any code, ensure the following are in place.

1. Installed Packages

Both of the following AppExchange packages must be installed in your org:

  • AddressTools Premium — manages your Address Verification lookups
  • Address Verification Flow Component (AVFC) — provides the pw_avfc namespace component

2. User Permissions

Each user who will interact with the AVFC wrapper must have access to:

  • All Apex Classes prefixed with pw__avfc
  • The Visualforce Page pw__avfc.SessionIdPage
  • An AVFC licence allocated via Setup → Installed Packages → Manage Licences (unless your org has a site licence)

The easiest way to manage this is to create a Permission Set that grants the above and assign it to the relevant users or profiles.

3. Lightning Web Security (LWS)

Referencing a component from a managed package namespace inside your own LWC requires Lightning Web Security to be enabled:

  1. Go to Setup → Session Settings
  2. Enable Use Lightning Web Security for Lightning web components
  3. Click Save
  4. Clear your browser cache before testing

Note for older orgs: New orgs created after Spring ’23 have LWS enabled by default. If you are on an older org, check this setting first — it is the most common cause of the cross-namespace error.

4. CSP Trusted Site

The AVFC makes outbound calls to ProvenWorks’ verification API. If you are deploying in an Experience Cloud (Digital Experience) site, you must allow this domain:

  1. Go to Setup → CSP Trusted Sites
  2. Find or create an entry for https://addressvalidation.provenworks.com
  3. Set the Context to All
  4. Save

This step is not required for internal Lightning Experience pages, but is mandatory for any Experience Cloud surface.


Component Name Reference

The AVFC lives in the pw_avfc namespace. Its component identifier depends on context:

Context Identifier
Aura / Flow Screen pw_avfc:addressVerificationFlowBaseComponent
LWC HTML template pw_avfc-address-verification-flow-base-component

The LWC version uses kebab-case. This is standard LWC naming: namespace and component name are joined by a hyphen, and any camelCase within the component name is also converted to lowercase-hyphen form.


Creating the Wrapper LWC

Step 1 — Scaffold the Component

Open VS Code with the Salesforce Extension Pack, connect it to your org, then run:

SFDX: New Lightning Web Component

Give it a meaningful name (e.g. avfcWrapper). This creates three files under force-app/main/default/lwc/avfcWrapper/:

avfcWrapper/
├── avfcWrapper.html
├── avfcWrapper.js
└── avfcWrapper.js-meta.xml

Step 2 — Configure the Metadata File

Open avfcWrapper.js-meta.xml. The key settings depend on where you intend to deploy the component.

For a Lightning Record Page, App Page, or Home Page:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

For a Flow Screen:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__FlowScreen</target>
    </targets>
</LightningComponentBundle>

Step 3 — Write the HTML Template

The HTML template references the AVFC component using its kebab-case name. Input attributes are set here. The example below shows a typical configuration — see the AVFC Attribute Reference section for additional available attributes.

<!-- avfcWrapper.html -->
<template>
    <pw_avfc-address-verification-flow-base-component
        use-county="true"
        use-status="true"
        countries="GB,US,CA"
        origin="GB"
        country={countryValue}
        state={stateValue}
        postalcode={postalCodeValue}
        city={cityValue}
        street={streetValue}
        street2={street2Value}
        county={countyValue}
        status={statusValue}
        onaddresschange={handleAddressChange}>
    </pw_avfc-address-verification-flow-base-component>
</template>

Attribute values can be:

  • Static strings in quotes (e.g. countries="GB,US,CA")
  • Bound to reactive JS properties using curly braces (e.g. country={countryValue})
  • Omitted entirely if you don’t need that field

Step 4 — Write the JavaScript Controller

Minimal example — reading verified address data:

// avfcWrapper.js
import { LightningElement, track } from 'lwc';

export default class AvfcWrapper extends LightningElement {

    // Pre-population values (optional — remove if not needed)
    countryValue = '';
    stateValue = '';
    postalCodeValue = '';
    cityValue = '';
    streetValue = '';
    street2Value = '';
    countyValue = '';
    statusValue = '';

    // Stores the verified address returned by the AVFC
    @track verifiedAddress = {};

    handleAddressChange(event) {
        // The AVFC fires an 'addresschange' event.
        // The verified address is available at event.detail["address"].
        this.verifiedAddress = event.detail["address"];

        // From here, do whatever your use case requires:
        // - Save to a record via Apex
        // - Dispatch a custom event to a parent component
        // - Update reactive properties for display
        console.log('Verified address:', JSON.stringify(this.verifiedAddress));
    }
}

Practical example — saving to a Salesforce record via Apex:

import { LightningElement, api, track } from 'lwc';
import saveAddress from '@salesforce/apex/AddressController.saveAddress';

export default class AvfcWrapper extends LightningElement {

    @api recordId; // If on a Record Page, the current record ID is injected automatically
    @track verifiedAddress = {};
    @track isSaving = false;
    @track saveError = null;

    handleAddressChange(event) {
        this.verifiedAddress = event.detail["address"];
    }

    handleSave() {
        this.isSaving = true;
        saveAddress({
            recordId: this.recordId,
            address: JSON.stringify(this.verifiedAddress)
        })
        .then(() => {
            this.isSaving = false;
        })
        .catch(error => {
            this.saveError = error.body.message;
            this.isSaving = false;
        });
    }
}

Practical example — communicating upward via a custom event:

import { LightningElement } from 'lwc';

export default class AvfcWrapper extends LightningElement {

    handleAddressChange(event) {
        const address = event.detail["address"];

        // Dispatch upward to a parent LWC
        this.dispatchEvent(new CustomEvent('addressverified', {
            detail: { address },
            bubbles: true,
            composed: true
        }));
    }
}

The addresschange Event Payload

When a user selects a verified address, the AVFC fires an addresschange event. The payload is accessible at event.detail["address"] and contains the following keys:

Key Description
street First line of the street address
street2 Second line of the street address (if use-second-street-line is enabled)
city City / town
state State or province
postalcode Postcode / ZIP code
country Country
county County (if use-county="true")
status Verification status (if use-status="true")

AVFC Attribute Reference

For the full list of available attributes (boolean toggles, country & locale, pre-population values, label overrides, styling, and events), see Address Verification Flow Component Available Parameters. All attribute names must be in kebab-case when used in an LWC HTML template.


Deployment

Once your three files are ready, deploy the component to your org:

SFDX: Deploy This Source to Org

or via the CLI:

sf project deploy start --source-dir force-app/main/default/lwc/avfcWrapper

Then place your wrapper component on a page:

  • Lightning Record/App Page: Open the Lightning App Builder, search for your component by name, and drag it onto the canvas
  • Flow Screen: Open a Screen Flow, search for your component in the Components panel, and drag it onto a screen element
  • Experience Cloud: Open Experience Builder, find the component in the Components panel, and add it to the page

Troubleshooting

“Attempting to reference cross-namespace module pw_avfc in c”

This error means Lightning Web Security is not enabled. Go to Setup → Session Settings, enable LWS, save, and clear your browser cache.

Component renders but shows no results on search

  • Confirm AddressTools Premium has Address Verification lookups enabled
  • Check user has Apex class and Visualforce page permissions for pw__avfc
  • If in Experience Cloud, confirm the CSP Trusted Site entry is in place
  • Check that a licence has been allocated to the user (Setup → Installed Packages → Address Verification Flow Component → Manage Licences)

Verified status resets after editing the address record

This is expected behaviour. AddressTools appends a Zero-Width Space character (U+200B) to the status value to distinguish freshly-verified addresses from previously-verified ones. If you need to preserve Verified status after a programmatic update, append U+200B to the status value before saving the record.


Implementing in an OmniScript Instead?

If you need to embed the AVFC inside a Vlocity / OmniStudio OmniScript rather than a standalone custom LWC, a different wrapper approach is required. See the ProvenWorks guide: How To Use the Address Verification Flow Component (AVFC) Within an OmniScript.

AddressTools Premium: Configure geocoding

Geocoding in AddressTools Premium is the process of returning longitude and latitude values alongside a verified postal address. Geocoding enables you to place records on maps, calculate distances, support territory management, and automate location-based tasks within Salesforce.

Access to geocoding functionality requires Address and Geocoding lookups alongside your AddressTools Premium licenses. If your organisation does not currently have access to geolocations, or you would like more information about enabling this feature, contact sales@provenworks.com.

AddressTools geocoding vs Salesforce Data.com geocoding

While Salesforce offers native geocoding for standard address fields (Account Billing & Shipping, Contact Mailing, and Lead Address), the Salesforce Data Integration Rules run asynchronously and restrict geocoding to these specific objects. The platform provides limited control over when it generates coordinates or how you customise the integration.

In contrast, AddressTools Premium returns latitude and longitude instantly as part of the address verification process, works consistently across any object you enable it for, and gives you immediate geolocation data tailored to your workflows. For best results on standard objects where Data.com geocoding is available, we recommend disabling the native Salesforce Data Integration Rules geocoding, via Data Integration Rules in setup, to avoid conflicts or delays and rely solely on AddressTools’ geocoding.

How to configure geocoding

Geocoding will be enabled in your environment if: 

  • Your org has Address and Geocode lookups.
  • You have configured your address blocks to include Longitude, Latitude, and Geocode Accuracy fields.

Assuming that your purchase is active or you’re utilising a geocode trial, the following steps will describe how to edit an existing address block to include the geocoding fields.

This example uses the Account Billing Address fields, the fields used are provided as standard out of the box with Salesforce, however the same steps can be used on a custom address field*. 

  1. Navigate to AddressTools Administration | Address Blocks.
  2. Locate the specific address block you wish to configure from the table.
  3. Select the dropdown menu on the right of the row and click Edit.
  4. Find the Location Fields section to map the fields:
    • Latitude Field: Select the Latitude field from the dropdown menu (BillingLatitude).
    • Longitude Field: Select the Longitude field from the dropdown menu (BillingLongitude).
    • Geocode Accuracy Field: Select the Geocode Accuracy field from the dropdown menu (BillingGeocodeAccuracy).
  5. Save the changes to apply the geolocation settings.

*Note: if you are using a custom object without the Custom Address field enabled, then you will need to create these geolocation fields yourself following the guide: Creating additional fields for address verification

Testing AddressTools geocoding

When setup of verification has been complete, you’re able to test geocoding by following the steps below:

  1. Verify an Address:
    Perform address verification via PowerSearch or your configured automated process.
  2. Check the Geocode Fields:
    After the verification completes, inspect the Latitude, Longitude, and Geocode Accuracy fields that you configured in the address block to confirm they contain valid values. You can do this by retrieving the values via an SOQL query. Be aware that verification through PowerSearch will return ‘Unknown’ in the Geocode Accuracy field. Other values will be populated when using automated address verification.
  3. Verify Field Reset Behavior:
    AddressTools will reset geolocation fields when any address field changes. This ensures geolocations values always aligns with the verified address. To test this:
    • Make a modification to an address field (for example, remove or alter the street line), and save the record.
    • The Latitude, Longitude, and Geocode Accuracy fields should now be NULL (empty).

Back to the AddressTools Premium installation walkthrough

How to: Grant Guest Users Access with Sharing Rules

This article will cover how to configure Sharing Settings to allow external Guest users access to the Country object’s records installed with AddressTools. This configuration is essential to ensure your external users can utilise the full functionality of the product.

Note: In Salesforce, external users such as Guest users do not have “View All” permissions by default. Because of this restriction, the standard AddressTools Permission Sets cannot be assigned to these users. You must instead complete the sharing configuration outlined below to provide the required access to the functionality.

Configure the Sharing Rule Criteria

From Setup enter and select Sharing Settings.

Locate the section Country Sharing Rules and click New.

Follow these steps to define the rule:

  • Step 1: Enter a Label and Rule Name that is descriptive (e.g. Share Countries with Guests).
  • Step 2: For the Select your rule type section, select Guest user access, based on criteria.
  • Step 3: In the Select which records to be shared section, set the following:
    • Field: Country Name
    • Operator: not equal to
    • Value: (Leave this box blank)
  • Step 4: In the Select the users to share with section, select the specific user license you need to provide access to (e.g. Partner Community User).
  • Select Save.

Assigning Permissions to Guest Users

If you are still experiencing issues with the Guest user when using AddressTools, you may need to assign additional permissions. The Guest user requires specific permissions to use the tool. A full list of required permissions can be found in: How to: deploy AddressTools Premium to users in your organization – ProvenWorks.

How to: Configure functionality for a Custom Address block

This article will cover how to create and configure your custom address block with AddressTools allowing for all trigger based and interactive functionality. 

Note: Custom address block functionality requires AddressTools Premium. If you use AddressTools Free, start a Premium trial.  

In the following example, we will be covering how to configure a custom address block that we have created on a custom object that uses individual custom fields for each address field. The same steps can be used with a custom address field type; however we recommend using individual field elements due to the limitations that Salesforce State & Country/Territory picklists enforce.  

Task overview: 

  1. Create the address block fields on the object.
  2. Create the address verification fields (Premise-Level only).
  3. Configure the address block and verification in AddressTools.
  4. Create the custom trigger.
  5. Optional: Add the override component.
  6. Optional: Add the on page component.

Create an address block in the custom object

The following steps assume you are taking the approach of using individual fields for each address element. It is recommended to review this step even if you have already created your address fields. This is the ensure each field is of the right type before moving forward with the rest of the configuration.  

  • Go to Setup | Object Manager | [Your Object] | Fields & Relationships.
  • Select New to create each of the following address block fields: 
    • Street – Text Area (255).
    • City – Text (255).
    • State – Text (255).
    • Country – Text (255).
    • Postal Code – Text (255).

Create address verification fields (needed for Premise-Level)

If you want to utilize address verification functionality with the new address block, you will be required to create an Address Status field at a minimum. For optional fields such as County or Address Label that are also populated when using Address Verification, see the article: Creating additional fields for address verification.

  • Go to Setup | Object Manager | [Your Object] | Fields & Relationships. 
  • Select New.
  • Choose the data type: Picklist.
  • Select Next.
  • Add a Field Label (we suggest the address type plus address status, i.e. Billing Address Status) 
  • Select Enter values, with each value separated by a new line.
  • Add the following values to the text area: 
    • Not checked *mark as the default value 
    • Not matched 
    • Parsed but not found 
    • Ambiguous
    • Verified 
  • Uncheck Restrict picklist to the values defined in the value set

  • Select Next.
  • Provide visibility to all the users who will be verifying addresses, this can also be handled in a permission set afterward if preferred. 
  • Select Next and Save.

Configure address block and verification.

With the objects and fields created, it is now essential to map them with AddressTools via the Administration page. 

  • Go to App Launcher | AddressTools Administration | Address Blocks.
  • Select Add, choose [Your Object] from the dropdown.
  • If you desire functionality on a specific record type, choose the record type from the options. Leaving this blank will apply the functionality to all. 
  • Select Next.
  • In Postal Address Fields, complete each dropdown with the corresponding API name from your object.
  • In Additional Address Fields, complete the Status Field (and any additional fields) by choosing the corresponding object API name from the dropdown. 
  • If necessary, in Address Verification Options, select Enable Premise-Level Address Verification.
  • Enable settings as required for the address block and Save

Create custom trigger

A trigger is required on the objects that contain address fields that are configured with AddressTools. Without the trigger, most functionality will not fire and other functionality may appear to not behave as intended. 

  • Go to Setup | Object Manager | [Your Object] | Triggers. 
  • Click New to create a trigger. 
  • Replace the default code in the text field with the following code snippet: 
trigger ValidateOBJECTLABELCountryFields on OBJECTAPI (before insert,
before update) { 
   pw_ccpro.CountryValidator2.Validate (Trigger.new, Trigger.oldMap);
}
  • In this code snippet, replace OBJECTLABEL with the object name and OBJECTAPI with the correct API name. 
  • Save your new trigger. 

How to add the override component 

The AddressTools Override component is an advanced lightning component built to replace the standard “New Record” popup that your users experience when creating a new record in Salesforce. 

To override the New Record model in Salesforce Lightning: 

  • Navigate to Setup | Object Manager | [Object to override] | Buttons, Links and Actions. 
  • For the New button, select the arrow to reveal options and choose Edit. 
  • Set Lightning Experience Override to Lightning Component. 
  • Choose pw_ccpro__AddressToolsOverride from the picklist. 
  • Select Save.

Repeat the above steps for all of the objects you want to override the new record model with. 

How to add the on page component 

The AddressTools Record Page Component is your one-stop widget for handling anything address related on a record. The component is designed to sit on a lightning record page where an address block exists giving your users quick access to everything they need without distraction. 

To add the AddressTools Record Page Component to a lightning record page: 

  • Navigate to Setup | Object Manager | [Object] | Lightning Record Pages
  • Select New or if a Lightning Record Page already exists, select Edit. 
  • Drag the AddressTools Component from the components list onto the layout. 
  • Choose the Address block. 
  • Optionally, type into the Component header title to add a custom title. 
  • Select Save.

If the page has not yet been activated, a prompt will appear asking you to activate it. Follow the steps below to assign the page in the organization. 

  • Select Activate.
  • The page can be assigned based on different levels, for example set the Org Default by pressing Assign as Org Default
  • Review the page assignment changes. 
  • Select Save