entity_get_form_display setComponent throws "plugin does not exist" regardless of type
OK, I've dug around and I think I've got a fix for this myself.

The first thing is to fix the code above; the two 'type' entries SHOULD be different, and should match values available in the dropdowns in the admin UI:

entity_get_form_display('contact', 'organization', 'default')
  -&gt;setComponent('field_phone', ['type' =&gt; 'text_textfield'])
  -&gt;save();
entity_get_display('contact', 'organization', 'default')
  -&gt;setComponent('field_phone', ['type' =&gt; 'string'])
  -&gt;save();


The second thing is that the entity's baseFieldDefinitions() were also defining a 'view' formatter type and a 'form' formatter type, but for an undeletable base field:

$fields['name']-&gt;setSettings([
    'default_value' =&gt; '',
    'max_length' =&gt; 255,
    'text_processing' =&gt; 0,
  ])
  -&gt;setDisplayOptions('view', ['label' =&gt; 'above', 'type' =&gt; 'string'])
  -&gt;setDisplayOptions('form', ['type' =&gt; 'text_textfield'])
  -&gt;setDisplayConfigurable('form', TRUE)
  -&gt;setDisplayConfigurable('view', TRUE);


Originally, the form display here was also 'string'. This is obviously nothing to do with the programmatic field adding itself, but when you run:

entity_get_*display(...)-&gt;setComponent(...)


the return value is $this i.e. the display object. So chaining -&gt;save() causes the ENTIRE display to be saved: including base fields. Only at this point did the broken configuration in the base fields suddenly cause a problem.

But, ensuring that:


the 'type' for form display ALWAYS matched a value available in the admin UI for content types
and similar for view display
and ensuring this across both the new field AND base fields


fixed the original problem. Phew.