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.

How To: Edit a ‘Verified’ Address and Retain Its Status

When editing an address marked as Verified, it’s important to understand how the system handles verification status. By default, any modification to a Verified address will cause its status to revert to Not Checked. This behavior is intentional and helps maintain data integrity during address verification. 

How Address Verification Works 

The address verification tool sets an address status to: 

Verified + a Zero-Width Space (ZWSP) character (U+200B) 

This invisible character allows the system to distinguish between: 

  • An address that was already verified before the current transaction. 
  • An address that was verified as part of the current transaction. 

Trigger Behavior 

When the trigger runs: 

  • It checks for the ZWSP character. 
  • If present, it knows the address has already been verified in the current context. 
  • It removes the ZWSP and does not revert the status to Not Checked. 

How to Preserve ‘Verified’ Status After Editing 

To retain the Verified status after editing an address: 

  1. Check if the status is Verified. 
  2. Append a Zero-Width Space (U+200B) character to the end of the status. 
  3. Save the updated record. 

This ensures the verification system recognizes the address as already verified and prevents the status from resetting. 

Example 

Before saving an updated verified address: 

Status: “Verified​” ← Includes ZWSP (U+200B) at the end
 

Without the ZWSP, the trigger assumes it’s an outdated verification and will reset the status. 

 

How to: solve “Secure query included inaccessible field” within AddressTools

AddressTools’ triggers handle the standardization, validation, and some elements of verification in a Salesforce organization where the solution is configured.

However, in later versions of the package, changes in how permissions are checked may result in them throwing the following error message:

pw_ccpro.ValidateAccountBeforeSave: execution of BeforeUpdate caused by: System.QueryException: Insufficient permissions: secure query included inaccessible field (pw_ccpro)

To resolve this issue, ensure that users have the appropriate object and field permissions assigned. The required permissions are detailed in the table below and are also included in the packaged “AddressTools Premium Standard User” permission set.

ObjectObject PermissionField NameField Permission
UsersN/A

QuickComplete Country

Exempt Validation Errors

Read
CountriesView AllAll fieldsRead
StatesView AllAll fieldsRead
ZIP CodesView AllAll fieldsRead
Time ZonesView AllAll fieldsRead
Alternate Country NamesView AllAll fieldsRead
Alternate State NamesView AllAll fieldsRead
ZIP Code CountiesView AllAll FieldsRead

The user/profile will need access to the following Custom Setting Definitions:

  • CountryComplete Settings
  • AddressTools Fields to Validate

Address Data Fraud: Why Mismatched ZIP Codes Are a Red Flag

Introduction: The Hidden Cost of Inaccurate Address Data

Fraud is on the rise, with online scams becoming increasingly sophisticated. One overlooked form of fraud involves providing incorrect or fake addresses when submitting contact details. For businesses, this can result in missed deliveries, wasted resources, and compromised customer trust.

If your company relies on address data – whether for deliveries, customer verification, taxation or regulatory compliance – you need to be confident that the addresses you receive are valid. Inaccurate or fake address submissions can harm your bottom line and open the door to fraud. Fortunately, real-time address verification can help.

In this post, we’ll explore address data fraud, how fraudsters manipulate address data, and how ProvenWorks’ AddressTools can help U.S. businesses stay one step ahead.

What Is Address Data Fraud?

Address data fraud occurs when a person provides false, incomplete, or manipulated address information to deceive a business. This can happen during online transactions, account sign-ups, or any form submission that requires an address. The goal is often to bypass validation checks, avoid detection, or exploit system weaknesses for fraudulent purposes.

One common tactic involves mismatched ZIP codes and street addresses. For example, a fraudster may enter a legitimate ZIP code but pair it with a street address that doesn’t exist in that area to trick basic address validation systems. Why? To:

  • Make stolen credit card purchases without triggering address verification systems.
  • Bypass geographic restrictions, such as country-specific product availability or shipping rules.
  • Avoid fraud detection systems that rely on matching a billing address to a payment method.

Another tactic involves entering an incorrect country or state to access services or pricing not available in their actual location. For instance, a fraudster might select “United States” on a form to unlock shipping options or promotions reserved for U.S. customers, while providing a non-U.S. address.

Common Examples of Address Data Fraud:

  1. Fake addresses: Fraudsters use non-existent or random addresses to avoid detection.
  2. Mismatched ZIP codes: A valid ZIP code paired with an incorrect street address to make it harder to verify.
  3. Incomplete addresses: Missing key components (like apartment numbers) to delay delivery or verification.

Why U.S. Businesses Need to Worry About Address Data Fraud

For U.S.-based businesses, inaccurate address data can have serious implications. Beyond the immediate cost of failed deliveries, incorrect addresses can lead to regulatory issues, damaged brand reputation, and reduced operational efficiency.

According to USPS, 15% of addresses entered online contain errors, which can lead to billions in lost revenue annually. The rise of e-commerce and remote transactions has made address validation more critical than ever.

Here’s how address data fraud impacts businesses:

The Risks of Inaccurate Address Data:

  • Lost revenue: Deliveries that can’t be completed due to incorrect addresses lead to wasted shipping costs.
  • Fraudulent transactions: Fraudsters use fake addresses to carry out scams.
  • Regulatory non-compliance: Certain industries require accurate address data for compliance purposes.
  • Damaged customer trust: Failed deliveries and incorrect data can harm your brand’s reputation.

How Address Verification Helps Prevent Fraud

The good news? Address verification at the point of entry can prevent fraudulent address submissions before they cause damage. By verifying that an address is real, complete, and accurately matches the ZIP code, businesses can reduce fraud risk and improve data quality.

The Key to Address Verification: Real-Time Detection

Real-time address verification ensures that addresses are checked as they are entered, preventing fraudulent or incorrect submissions from making it into your database. This proactive approach stops bad data at the source, reducing the need for costly cleanup later.

How AddressTools by ProvenWorks Can Help

For Salesforce users, ProvenWorks’ AddressTools is a powerful solution to ensure address accuracy and prevent address data fraud.

AddressTools provides:

1. Real-Time Address Verification

Addresses are verified as they are entered, ensuring they are real and match the ZIP code. The tool checks against trusted CASS™ certified datasets to mitigate incorrect or fraudulent entries.

2. Type-Ahead Address Suggestions

When users enter incomplete or incorrect addresses, AddressTools provides suggested corrections. This helps reduce manual errors and ensures that your database remains accurate and complete.

3. Standardized Address Data

AddressTools standardizes addresses to ensure consistency across your Salesforce instance. This improves reporting accuracy and makes it easier to comply with regulations.

4. Address Data Enrichment

AddressTools can auto-populate additional data points for the address, for example time zone, sales territory or U.S. county, which can be then utilized in other parts of the business logic.

5. Seamless Integration with Salesforce

AddressTools integrates directly into your existing Salesforce workflows, providing a user-friendly experience without disrupting operations.

Why Accurate Address Data Matters for U.S. Businesses

Accurate address data isn’t just about reducing fraud – it’s also about improving operational efficiency and customer satisfaction.

Here’s why:

1. Improved Operational Efficiency

Accurate address data means fewer failed deliveries, fewer returned packages, and fewer manual data corrections. This saves your business both time and money.

2. Enhanced Customer Trust

When deliveries arrive on time and at the correct address, customers are more likely to trust your business. Building trust leads to repeat business and positive word-of-mouth.

3. Regulatory Compliance

For industries like finance and healthcare, maintaining accurate address data is essential for regulatory compliance. AddressTools helps businesses avoid penalties and stay compliant with legal requirements.

Conclusion: Protect Your Business with ProvenWorks’ AddressTools

Address data fraud is a growing concern for U.S. businesses. Mismatched ZIP codes and fake addresses can lead to financial losses, missed deliveries, and damage to your brand’s reputation.

With real-time address verification from ProvenWorks’ AddressTools, you can prevent fraudulent address submissions, improve data accuracy, and enhance your business operations.

Want to see how AddressTools can work for your business?