How to automatically add http prefix to links in a form?
Thanks to those who provided answers; I couldn't quite get those to work, but they got me to my answer. My final solution ended up being this:
function my_module_form_alter(&amp;$form, $form_state, $form_id) {
  // add a global validator to fix stupid Link fields - add to all Link fields
  // only worry about entity forms
  if (isset($form['#entity_type'])) {
    $bundle = $form_state-&gt;getFormObject()-&gt;getEntity()-&gt;bundle();
    $entity_type = $form['#entity_type'];
    $entityFieldManager = \Drupal::service('entity_field.manager');
    $fields = $entityFieldManager-&gt;getFieldDefinitions($entity_type, $bundle);
    foreach ($fields as $field) {
      if ($field-&gt;getType() == 'link') {
        $field_name = $field-&gt;getName();
        $form[$field_name]['widget'][0]['#element_validate'][] = '_fix_link_field_value';
        array_unshift($form[$field_name]['widget'][0]['uri']['#element_validate'][0], '_fix_link_field_value');
        $form[$field_name][&quot;widget&quot;][0][&quot;uri&quot;][&quot;#description&quot;] = t('This must be an external URL such as http://example.com, https://example.com, example.com, www.example.com.');
      }
    }
  }
}

and then the validator/fixer:
function _fix_link_field_value(&amp;$element, FormStateInterface $form_state, &amp;$complete_form) {
  $url = $form_state-&gt;getValue($element['#parents'][0]);
  $urlOriginal = $url[0]['uri'];
  if (!$urlOriginal) return;

  if (!stristr($url[0]['uri'], '//')) {
    $url[0]['uri'] = 'http://' . $url[0]['uri'];
  }
  if (filter_var($url[0]['uri'], FILTER_VALIDATE_URL) === FALSE) {
    $form_state-&gt;setErrorByName($element['#parents'][0], t('%url is not a valid URL format. This must be in a format like
      http://example.com, https://example.com, example.com, www.example.com.',
      ['%url' =&gt; $urlOriginal])
    );
  }
  else {
    $form_state-&gt;setValue($element['#parents'][0], $url);
  }
}

The array_unshift isn't right but doing an unset() didn't seem to cut it. I am sure all this is doing is preventing the original validator from running; which is good as the error message it generates is stupid. Not sure why 5 years into D8 they still don't have this fixed.
Also, this will need an extra loop if you are dealing with multi-valued fields.