Skip to content

DIGITALLNature/DigitallValidators

Repository files navigation

DigitallValidators

A Microsoft Dataverse managed solution that adds Custom APIs for validating and formatting IBANs, phone numbers, and email addresses directly inside Dataverse — from model-driven apps, Power Automate flows, plugins, or the Web API.

Motivation

Dataverse doesn't ship with built-in validation for IBANs, international phone numbers, or email addresses. Teams often solve this by calling third-party validation services over the internet, which introduces several problems:

  • 🔒 Data privacy / compliance risk — sensitive data (bank account details, phone numbers) leaves your Dataverse environment and is sent to an external service.
  • 🌐 No offline / restricted-network support — external HTTP calls can fail or be blocked entirely in locked-down environments.
  • 💸 Cost & rate limits — many 3rd party validation APIs are metered or rate-limited.
  • 🐌 Latency & reliability — an extra network hop adds latency and another point of failure.

DigitallValidators solves this by validating entirely "offline", inside the Dataverse sandbox, without any outbound calls to external services. It is built on top of two well-established, actively maintained open source libraries:

  • IbanNet — IBAN parsing, validation and formatting according to the IBAN registry.
  • libphonenumber-csharp — a C# port of Google's libphonenumber, for parsing, validating and formatting phone numbers.
  • MimeKit — RFC 5322 compliant email address parsing.

Email domain typo-detection (e.g. suggesting gmail.com for gmial.com) is a small, purpose-built helper included in this solution rather than a third-party library — see dgt_ValidateEmailAddress below.

All of the above ship with the solution and run as part of the plugin assembly, so no data ever leaves your Dataverse environment.

Getting Started

Prerequisites

  • A Microsoft Dataverse environment you have System Administrator/Customizer access to.
  • The GitHub CLI (gh) (optional, for downloading releases from the terminal) or a browser.
  • Power Platform CLI (pac) (optional, for importing the solution from the command line).

1. Download the managed solution

Every push to main is released automatically via semantic-release and publishes a DigitallValidators.zip asset to GitHub Releases.

Using the GitHub CLI:

# Download the latest release asset into the current directory
gh release download --repo DIGITALLNature/DigitallValidators --pattern "DigitallValidators.zip"

Or download the DigitallValidators.zip asset manually from the latest release.

2. Import the solution into Dataverse

Option A — Power Platform CLI:

pac auth create --url https://<your-org>.crm.dynamics.com
pac solution import --path .\DigitallValidators.zip

Option B — Power Platform admin center / maker portal:

  1. Go to Solutions in the Power Apps maker portal.
  2. Select Import solution and choose the downloaded DigitallValidators.zip.
  3. Follow the import wizard and confirm.

Once imported, the three Custom APIs described below (dgt_ValidateIban, dgt_ValidatePhoneNumber, and dgt_ValidateEmailAddress) are available in your environment and can be called from plugins, Power Automate ("Perform an unbound action"), Power Fx, or the Dataverse Web API.

API Documentation

All Custom APIs are unbound, non-function actions (i.e. called via POST) and can be invoked at:

POST [Organization URI]/api/data/v9.2/<CustomApiName>

with header Content-Type: application/json.

Localization

The Message output parameter of all three APIs is localized. Pass LanguageCode (an LCID, matching how Dataverse represents languages elsewhere) to pick the language explicitly. If omitted, the calling user's configured Dataverse UI language (usersettings.uilanguageid) is used instead. If neither yields a supported language, messages default to English. Currently English (en) and German (de) are supported; unsupported languages fall back to English. ErrorCode values are always stable, untranslated identifiers suitable for programmatic handling — only Message changes with language.

dgt_ValidateIban

Validates an IBAN and returns it in electronic and print format. Validation is based on the IBAN registry (structure, length, check digits, country support), with optional allow/reject lists per country.

Request Parameters

Name Type Required Description
Iban String Yes The IBAN to validate.
AllowedCountries String[] No Only allow the specified countries (ISO 3166-1 alpha-2, e.g. ["DE", "AT"]).
RejectedCountries String[] No Reject the specified countries (ISO 3166-1 alpha-2, e.g. ["US"]).
LanguageCode Whole Number No The LCID to localize Message into (e.g. 1031 for German). If omitted, the calling user's configured Dataverse UI language is used; falls back to English if that can't be determined.

Response Properties

Name Type Description
IsValid Boolean Whether the provided IBAN is valid.
ElectronicFormat String IBAN in electronic / machine readable format (without spaces). Only set when valid.
PrintFormat String IBAN in print / human readable format (with spaces). Only set when valid.
ErrorCode String Error code identifying the validation failure (see below). Only set when invalid.
Message String Human readable error message. Only set when invalid.

ErrorCode corresponds to an IbanNet validation result type name, e.g.:

ErrorCode Meaning
InvalidLengthResult The IBAN has an unexpected length for its country.
InvalidStructureResult The IBAN does not match the expected structure/pattern.
InvalidCheckDigitsResult The IBAN check digits do not validate (checksum failure).
IllegalCharactersResult The IBAN contains characters that are not allowed.
IllegalCountryCodeCharactersResult The country code portion contains invalid characters.
UnknownCountryCodeResult The country code is not a known/supported IBAN country.
CountryNotAcceptedResult The country was excluded via AllowedCountries/RejectedCountries.

Sample request — valid IBAN

POST [Organization URI]/api/data/v9.2/dgt_ValidateIban
Content-Type: application/json

{
  "Iban": "DE89370400440532013000"
}

Sample response (200 OK):

{
  "IsValid": true,
  "ElectronicFormat": "DE89370400440532013000",
  "PrintFormat": "DE89 3704 0044 0532 0130 00",
  "ErrorCode": null,
  "Message": null
}

Sample request — invalid IBAN (bad check digits)

POST [Organization URI]/api/data/v9.2/dgt_ValidateIban
Content-Type: application/json

{
  "Iban": "DE89370400440532013012"
}

Sample response (200 OK):

{
  "IsValid": false,
  "ElectronicFormat": null,
  "PrintFormat": null,
  "ErrorCode": "InvalidCheckDigitsResult",
  "Message": "The IBAN's check digits are invalid."
}

Sample request — restrict allowed countries

POST [Organization URI]/api/data/v9.2/dgt_ValidateIban
Content-Type: application/json

{
  "Iban": "DE75512108001245126199",
  "AllowedCountries": ["FR"]
}

Sample response (200 OK):

{
  "IsValid": false,
  "ElectronicFormat": null,
  "PrintFormat": null,
  "ErrorCode": "CountryNotAcceptedResult",
  "Message": "The IBAN's country is not in the accepted list of countries."
}

dgt_ValidatePhoneNumber

Validates a phone number and returns it formatted according to the requested format, based on libphonenumber-csharp.

Request Parameters

Name Type Required Description
PhoneNumber String Yes The phone number to validate. Can be in national format (requires DefaultRegion) or international format (e.g. +491775628291).
DefaultRegion String No Two letter ISO 3166 country code (e.g. DE, US) used as a default/hint to parse numbers that are given in national format (no leading +). It is not enforced against the parsed result — a number with an explicit country code that differs from DefaultRegion is still considered valid. If omitted, PhoneNumber must be in international format (leading +).
AllowedRegions String[] No List of two letter ISO 3166 country codes. If provided, the number's detected region must be in this list, otherwise validation fails with REGION_NOT_ALLOWED.
RejectedRegions String[] No List of two letter ISO 3166 country codes. If provided and the number's detected region is in this list, validation fails with REGION_REJECTED.
Format String No Output format for FormattedPhoneNumber. One of E164 (default), INTERNATIONAL, NATIONAL, RFC3966.
LanguageCode Whole Number No The LCID to localize Message into (e.g. 1031 for German). If omitted, the calling user's configured Dataverse UI language is used; falls back to English if that can't be determined.

Response Properties

Name Type Description
IsValid Boolean Whether the provided phone number is valid (and, if specified, allowed by AllowedRegions/RejectedRegions).
FormattedPhoneNumber String The phone number formatted according to Format. Only set when valid.
ErrorCode String Error code identifying the validation failure (see below). Only set when invalid.
Message String Human readable message describing the validation error. Only set when invalid.

ErrorCode values:

ErrorCode Meaning
INVALID_COUNTRY_CODE The number's country calling code could not be determined (e.g. no DefaultRegion given and number not in international format).
NOT_A_NUMBER The input string could not be parsed as a phone number at all.
TOO_SHORT_AFTER_IDD The string is too short after an IDD (international dialing prefix).
TOO_SHORT_NSN The national significant number is too short to be a valid number.
TOO_LONG The number is too long to be a valid phone number.
REGION_NOT_ALLOWED The number's detected region is not in the AllowedRegions list.
REGION_REJECTED The number's detected region is in the RejectedRegions list.
INVALID_PHONE_NUMBER The number is well-formed but not a valid, in-use phone number.

An invalid DefaultRegion/AllowedRegions/RejectedRegions entry (not a valid ISO 3166 two-letter code) or invalid Format value results in an HTTP error response (InvalidPluginExecutionException) rather than an IsValid: false result, since these are caller/programming errors rather than validation outcomes.

Sample request — valid number with default region and default format (E164)

POST [Organization URI]/api/data/v9.2/dgt_ValidatePhoneNumber
Content-Type: application/json

{
  "PhoneNumber": "01775628291",
  "DefaultRegion": "DE"
}

Sample response (200 OK):

{
  "IsValid": true,
  "FormattedPhoneNumber": "+491775628291",
  "ErrorCode": null,
  "Message": null
}

Sample request — valid number formatted as INTERNATIONAL

POST [Organization URI]/api/data/v9.2/dgt_ValidatePhoneNumber
Content-Type: application/json

{
  "PhoneNumber": "01775628291",
  "DefaultRegion": "DE",
  "Format": "INTERNATIONAL"
}

Sample response (200 OK):

{
  "IsValid": true,
  "FormattedPhoneNumber": "+49 177 5628291",
  "ErrorCode": null,
  "Message": null
}

Sample request — number's region not in AllowedRegions

POST [Organization URI]/api/data/v9.2/dgt_ValidatePhoneNumber
Content-Type: application/json

{
  "PhoneNumber": "+491778261829",
  "AllowedRegions": ["US", "CA"]
}

Sample response (200 OK):

{
  "IsValid": false,
  "FormattedPhoneNumber": null,
  "ErrorCode": "REGION_NOT_ALLOWED",
  "Message": "The phone number's region 'DE' is not in the allowed list (US, CA)."
}

Sample request — number without a region and without international prefix

POST [Organization URI]/api/data/v9.2/dgt_ValidatePhoneNumber
Content-Type: application/json

{
  "PhoneNumber": "017728371992"
}

Sample response (200 OK):

{
  "IsValid": false,
  "FormattedPhoneNumber": null,
  "ErrorCode": "INVALID_COUNTRY_CODE",
  "Message": "The country calling code is invalid."
}

dgt_ValidateEmailAddress

Validates an email address's format (RFC 5322, via MimeKit), normalizes it (lowercased, IDN/punycode-encoded domain), and optionally restricts or rejects specific domains. It also suggests corrections for common domain typos (e.g. gmial.comgmail.com) using a small built-in curated domain list, entirely offline — no DNS/MX lookups or outbound calls are made, consistent with this solution's design.

Request Parameters

Name Type Required Description
Email String Yes The email address to validate.
AllowedDomains String[] No Only allow the specified domains (e.g. ["company.com"]). Also seeds the typo-suggestion corpus (see below), since the whole point of an allow-list is "these are the only correct domains".
RejectedDomains String[] No Reject the specified domains (e.g. ["mailinator.com"] to block known disposable-email domains). Also excluded from the typo-suggestion corpus, so a rejected domain is never suggested as a correction.
AdditionalKnownDomains String[] No Extra domains to recognize for typo-suggestion purposes only (e.g. partner/client domains), without affecting what's actually considered valid. Merged with the built-in curated domain list.
LanguageCode Whole Number No The LCID to localize Message into (e.g. 1031 for German). If omitted, the calling user's configured Dataverse UI language is used; falls back to English if that can't be determined.

Response Properties

Name Type Description
IsValid Boolean Whether the provided email address is valid (well-formed and, if specified, allowed by AllowedDomains/RejectedDomains).
NormalizedEmail String The email address with its domain lowercased and IDN/punycode-encoded. Only set when the address is well-formed (even if rejected by AllowedDomains/RejectedDomains).
SuggestedEmails String[] Plausible typo-corrections for the domain (e.g. ["user@gmail.com"] for user@gmial.com), ordered by ascending edit distance. Independent of IsValid — a rejected/disallowed domain can still get a suggestion. Empty array if the domain already exactly matches a known domain or if there's no close match.
ErrorCode String Error code identifying the validation failure (see below). Only set when invalid.
Message String Human readable message describing the validation error. Only set when invalid.

ErrorCode values:

ErrorCode Meaning
INVALID_FORMAT The value could not be parsed as a valid, fully-qualified (local-part + domain) email address.
DOMAIN_NOT_ALLOWED The email's domain is not in the AllowedDomains list.
DOMAIN_REJECTED The email's domain is in the RejectedDomains list.

Sample request — valid email

POST [Organization URI]/api/data/v9.2/dgt_ValidateEmailAddress
Content-Type: application/json

{
  "Email": "User@Example.com"
}

Sample response (200 OK):

{
  "IsValid": true,
  "NormalizedEmail": "User@example.com",
  "SuggestedEmails": [],
  "ErrorCode": null,
  "Message": null
}

Sample request — likely typo of a common domain

POST [Organization URI]/api/data/v9.2/dgt_ValidateEmailAddress
Content-Type: application/json

{
  "Email": "user@gmial.com"
}

Sample response (200 OK):

{
  "IsValid": true,
  "NormalizedEmail": "user@gmial.com",
  "SuggestedEmails": ["user@gmail.com", "user@mail.com"],
  "ErrorCode": null,
  "Message": null
}

Sample request — restrict allowed domains, with a typo of the only allowed domain

POST [Organization URI]/api/data/v9.2/dgt_ValidateEmailAddress
Content-Type: application/json

{
  "Email": "user@compnay.com",
  "AllowedDomains": ["company.com"]
}

Sample response (200 OK):

{
  "IsValid": false,
  "NormalizedEmail": "user@compnay.com",
  "SuggestedEmails": ["user@company.com"],
  "ErrorCode": "DOMAIN_NOT_ALLOWED",
  "Message": "The email domain 'compnay.com' is not in the allowed list (company.com)."
}

Sample request — invalid format

POST [Organization URI]/api/data/v9.2/dgt_ValidateEmailAddress
Content-Type: application/json

{
  "Email": "not-an-email"
}

Sample response (200 OK):

{
  "IsValid": false,
  "NormalizedEmail": null,
  "SuggestedEmails": null,
  "ErrorCode": "INVALID_FORMAT",
  "Message": "The value could not be parsed as a valid email address."
}

License

Licensed under the Microsoft Public License (Ms-PL).

About

Collection of generic reusable validators built using Custom APIs

Topics

Resources

License

Stars

0 stars

Watchers

2 watching

Forks

Contributors

Languages