Migrate Multiple URLs from a Spreadsheet into ONE Link Field I'm trying to migrate from Google Sheets into Drupal 8.9+. I have successfully configured most of the migration, but am having trouble migrating two source link fields (Event URL &amp; Ticketing URL) into ONE multi-value links field (field_links) in my target node. I'm trying to shape all the source data in my Google Sheet so I don't have to use custom process plugins.
Here's a link to an example of the Google Spreadsheet I'm using as the source.
I found this Github Gist that led me to believe I could shape the two url fields I have in Google sheets into an array, and have that array be processed by the iterator (now, sub_process) plugin. So I now have a source column called &quot;LinksArray&quot; that merges the data from my &quot;Event URL&quot; and &quot;Ticking URL&quot; columns to create an array that looks exactly like this:
[
  ['title' =&gt; 'Event URL', 'uri' =&gt; 'https://coolvenue.com/etc'],
  ['title' =&gt; 'Ticketing URL', 'uri' =&gt; 'https://tickets.com/etc'],
];

And the relevant migration file looks like this:
...

    - name: src_links_array
      label: &quot;LinksArray&quot;
      selector: linksarray
...

  field_links:
    - plugin: skip_on_empty
      method: process
      source: src_links_array
      message: 'field_links is empty. Skipping.'
    - plugin: sub_process
      process:
        title: title
        uri: uri

When I run the migration, It appears to be successful, with no errors, but the content created is missing the links altogether (no titles, no uri's, nothing), so is obviously not working. I tried reformatting the array with and without outer brackets, with and without trailing commas, etc. Nothing works. Is my manually created array being written incorrectly? Do I need to create the array inside the migration instead? If so, I'd greatly appreciate help with that. Thank you!
The two main ways to approach this with php are via hook or plugin.
The first is with a hook, specifically, hook_migrate_MIGRATION_ID_prepare_row().

Allows adding data to a row for a migration with the specified ID.

For example, you could combine your two link columns to a new column formatted as an array.
function my_module_migrate_MY_MIGRATION_ID_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
  // Assuming your url fields are keyed event_url and ticketing_url in your migration.yml.
  $links_array = [];
  if ($row-&gt;hasSourceProperty('event_url')) {
    $event_url = $row-&gt;getSourceProperty('event_url');
    if (!empty($event_url)) {
      $links_array[] = [
        'title' =&gt; 'Event URL',
        'uri' =&gt; $event_url,
      ];
    }
  }
  if ($row-&gt;hasSourceProperty('ticketing_url')) {
    $ticketing_url = $row-&gt;getSourceProperty('ticketing_url');
    if (!empty($ticketing_url)) {
      $links_array[] = [
        'title' =&gt; 'Ticketing URL',
        'uri' =&gt; $ticketing_url,
      ];
    }
  }

  $row-&gt;setSourceProperty('event_and_ticketing_url', $links_array);
}

You can use your programmatically added source value in your process section like:
process:
  ...
  field_links: event_and_ticketing_url
  ...


I prefer to use plugins over hooks whenever possible for a few reasons, but one is because plugins are more visible to anybody looking at the implementation later. By looking at the migration.yml, a future developer will know that a custom plugin does something to this thing and the plugin can be found in the src/Plugin directory.
An example doing the same thing, but with a plugin might look like:
my_module/src/Plugins/migrate/process/EventAndTicketingLinks.php:
/**
 * Process plugin to combine event and ticketing url sources to one links array.
 *
 * Example Usage:
 *
 * field_my_links_field:
 *   plugin: my_module_event_and_ticketing_links
 *   link_fields:
 *     event: event_url
 *     ticketing: ticketing_url
 *
 * @MigrateProcessPlugin(
 *   id = &quot;my_module_event_and_ticketing_links&quot;,
 * )
 */
class EventAndTicketingLinks extends ProcessPluginBase {

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $config = $this-&gt;configuration;
    $links = [];
    
    if (!empty($config['link_fields']['event'])) {
      $event_url = $row-&gt;getSourceProperty($config['link_fields']['event']);
      if (!empty($event_url)) {
        $links[] = [
          'title' =&gt; 'Event Url',
          'uri' =&gt; $event_url,
        ];
      }
    }
    if (!empty($config['link_fields']['ticketing'])) {
      $ticketing_url = $row-&gt;getSourceProperty($config['link_fields']['ticketing']);
      if (!empty($ticketing_url)) {
        $links[] = [
          'title' =&gt; 'Ticketing Url',
          'uri' =&gt; $ticketing_url,
        ];
      }
    }
    
    return $links;
  }
  
}


Additionally, since you ended up combining the urls into one source column anyway, you could use the hook or a plugin to work with that data too.