Overview
What Extract Phone Numbers does
Finds common local and international phone-number formats with optional country codes and extensions, preserves readable formatting, removes equivalent duplicates, and filters obvious dates, IP addresses, postal codes, government-ID patterns, and malformed or overlong numbers.
| Category | Extraction |
|---|---|
| Action type | JavaScript |
| Default result | Copy the result |
| Internet | Not required |
Example
Action and outcome
| Action | Outcome |
|---|---|
| Extract Phone NumbersSelected text: Call +1 (415) 555-2671 or +44 20 7946 0958 ext. 42. | +1 (415) 555-2671 +44 20 7946 0958 ext. 42 |
Workflow
Use Extract Phone Numbers
Follow these steps to extract unique phone numbers from selected text with ActionClip.
- Select the text you want to use.
- Choose Extract Phone Numbers from ActionClip.
- Review the extracted result, then copy it where you need it.
Before you add it
Requirements and privacy
- Before you start: No setup required.
- Your selected text: Runs locally in ActionClip’s JavaScript sandbox. Selected text does not leave your Mac.
Inspect before installing
Action script
The script below is included so you can inspect how the action works before downloading it.
JavaScript definition
function run(selected_text) {
const horizontalSpace = "[\\t \\u00A0\\u202F]";
const candidatePattern = new RegExp(
"(?:(?:\\+|00)" + horizontalSpace + "*(?:"
+ "\\(" + horizontalSpace + "*\\d{1,4}" + horizontalSpace + "*\\)|\\d)"
+ "|(?:\\(" + horizontalSpace + "*\\d{1,4}" + horizontalSpace + "*\\)|\\d))"
+ "[\\d\\t \\u00A0\\u202F().\\/-]{3,}\\d"
+ "(?:" + horizontalSpace + "*(?:(?:ext(?:ension)?)\\.?|x|#)"
+ horizontalSpace + "*\\d{1,6})?",
"giu"
);
const extensionPattern = /[\t \u00A0\u202F]*(?:(?:ext(?:ension)?)\.?|x|#)[\t \u00A0\u202F]*(\d{1,6})$/iu;
function hasSafeBoundaries(match) {
const before = match.index > 0 ? selected_text[match.index - 1] : "";
const afterIndex = match.index + match[0].length;
const after = afterIndex < selected_text.length ? selected_text[afterIndex] : "";
if ((before === "(") !== (after === ")")) return false;
return !/[\p{L}\p{N}_]/u.test(before) && !/[\p{L}\p{N}_]/u.test(after);
}
function hasBalancedPhoneParentheses(value) {
let depth = 0;
for (const character of value) {
if (character === "(") {
depth += 1;
if (depth > 1) return false;
} else if (character === ")") {
depth -= 1;
if (depth < 0) return false;
}
}
if (depth !== 0) return false;
const withoutValidGroups = value.replace(/\([\t \u00A0\u202F]*\d{1,4}[\t \u00A0\u202F]*\)/g, "");
return !/[()]/.test(withoutValidGroups);
}
function isObviousNonPhone(value) {
const compact = value.replace(/[\t \u00A0\u202F]/g, "");
if (/^(?:19|20)\d{2}[./-]\d{1,2}[./-]\d{1,2}$/.test(compact)) return true;
if (/^\d{1,2}[./-]\d{1,2}[./-](?:19|20)\d{2}$/.test(compact)) return true;
if (/^\d{3}-\d{2}-\d{4}$/.test(compact)) return true;
if (/^\d{5}-\d{4}$/.test(compact)) return true;
if (/^97[89](?:[-.]\d+){3,5}$/.test(compact)) return true;
const ipv4 = compact.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
return Boolean(ipv4 && ipv4.slice(1).every((part) => Number(part) <= 255));
}
function parseCandidate(match) {
if (!hasSafeBoundaries(match)) return null;
const readable = match[0]
.replace(/[\u00A0\u202F]/g, " ")
.replace(/[\t ]+/g, " ")
.trim();
const extensionMatch = readable.match(extensionPattern);
const extension = extensionMatch ? extensionMatch[1] : "";
const core = extensionMatch
? readable.slice(0, extensionMatch.index).trim()
: readable;
if (!hasBalancedPhoneParentheses(core) || isObviousNonPhone(core)) return null;
const digits = core.replace(/\D/g, "");
const usesDialOutPrefix = /^00/.test(core.replace(/[\t ]/g, ""));
const phoneDigits = usesDialOutPrefix ? digits.slice(2) : digits;
if (phoneDigits.length < 7 || phoneDigits.length > 15) return null;
if (/^(\d)\1+$/.test(phoneDigits)) return null;
const groups = core
.replace(/^\+/, "")
.replace(/[()]/g, " ")
.trim()
.split(/[\t ./-]+/)
.filter(Boolean);
if (groups.length > 1 && groups.some((group) => group.length > 6)) return null;
if (groups.length > 6) return null;
const canonicalCore = usesDialOutPrefix
? "+" + phoneDigits
: (core.trim().startsWith("+") ? "+" : "") + phoneDigits;
return {
value: readable,
key: canonicalCore + (extension ? "x" + extension : "")
};
}
const seen = new Set();
const phoneNumbers = [];
for (const match of selected_text.matchAll(candidatePattern)) {
const parsed = parseCandidate(match);
if (!parsed || seen.has(parsed.key)) continue;
seen.add(parsed.key);
phoneNumbers.push(parsed.value);
}
return phoneNumbers.length ? phoneNumbers.join("\n") : "No phone numbers found.";
}