Velocitip: Reformat phone numbers for local display — and “deformat” numbers for tel: links

Level 10 - Community Moderator
Level 10 - Community Moderator

Phone fields are typically just strings. Yes, validation of a specific format may be enforced on forms, but the back end database rarely enforces a format.[1]

So across list imports, API inserts, and direct data entry, you get lots of variations:



That's not necessarily bad! It can make sense for an app to “play dumb” and store + display numbers exactly as entered, especially if the person dialing the number (i.e. sales) is the same person who entered it.

But then there’s the whole design system thing, where — as on your website — you probably want phone numbers in signatures formatted just one way. And the click-to-call thing, too.


Deformatting numbers for click-to-call

The only non-digit “visual separators” supported in a tel: URI, per the official standard, are -.() and the leading + for international dialing. That is, spaces are not allowed. And only 4 punctuation symbols are allowed, not decorative characters like bullets.

So there’s a major difference between 212-555-1212 and (212) 555-1212 and 212·555·1212: only the first one is valid in the <a href="tel:"> context.


The code (for the US)

Building a full international formatting library like libphonenumber in Velocity wouldn’t be feasible (well, unless you paid me a lot 😛). So this example is for US numbers only, but will get you started:

 * Reformat/deformat US phone numbers
 * @version v1 2022-08-31
 * @author Sanford Whiteman, TEKNKL
#set( $originalPhone = $lead.Lead_Owner_Phone_Number )
## remove leading plus sign (if present) followed by digit 1
#set( $unprefixedPhone = $originalPhone.trim().replaceFirst("^\+?1","") )
## remove non-digits
#set( $unpunctuatedPhone = $unprefixedPhone.replaceAll("\D","") )
## format as 123.456.7890
#set( $displayPhone = $unpunctuatedPhone.replaceAll("(\d{3})(\d{3})(\d{4})","$1\.$2\.$3") )
## raw form in href, friendly form in text
<a class="mktNoTrack" href="tel:+1${unpunctuatedPhone}">${displayPhone}</a>

The final replaceAll is key. Here we split into groups of 3, 3, and 4 digits and then use a replacement expression to insert dots.



[1] And this makes sense, since there are too many “correct” formats for an international company to require just one. Another option is to store only the digits, but you must populate another field with locale so you can format upon output. Not a bad idea by any means, but easier said than adopted firm-wide.