diff --git a/islandora.routing.yml b/islandora.routing.yml
index 5387e9a4..9215a360 100644
--- a/islandora.routing.yml
+++ b/islandora.routing.yml
@@ -37,7 +37,7 @@ islandora.add_member_to_node_page:
     _entity_create_any_access: 'node'
 
 islandora.upload_children:
-  path: '/node/{node}/members/upload'
+  path: '/node/{node}/members/upload_old'
   defaults:
     _form: '\Drupal\islandora\Form\AddChildrenForm'
     _title: 'Upload Children'
@@ -46,6 +46,18 @@ islandora.upload_children:
   requirements:
     _custom_access: '\Drupal\islandora\Form\AddChildrenForm::access'
 
+islandora.upload_children_wizard:
+  path: '/node/{node}/members/upload/{step}'
+  defaults:
+    _wizard: '\Drupal\islandora\Form\AddChildrenWizard\Form'
+    _title: 'Upload Children'
+    step: 'child_type'
+  options:
+    _admin_route: 'TRUE'
+  requirements:
+    #_custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Form::access'
+    _custom_access: '\Drupal\islandora\Form\AddChildrenForm::access'
+
 islandora.add_media_to_node_page:
   path: '/node/{node}/media/add'
   defaults:
diff --git a/src/Form/AddChildrenForm.php b/src/Form/AddChildrenForm.php
index 0ff72496..528b4283 100644
--- a/src/Form/AddChildrenForm.php
+++ b/src/Form/AddChildrenForm.php
@@ -229,7 +229,7 @@ class AddChildrenForm extends AddMediaForm {
    * @param \Drupal\Core\Routing\RouteMatch $route_match
    *   The current routing match.
    *
-   * @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden
+   * @return \Drupal\Core\Access\AccessResultInterface
    *   Whether we can or can't show the "thing".
    */
   public function access(RouteMatch $route_match) {
diff --git a/src/Form/AddChildrenWizard/FileSelectionForm.php b/src/Form/AddChildrenWizard/FileSelectionForm.php
index 5ea6581d..f056d7d6 100644
--- a/src/Form/AddChildrenWizard/FileSelectionForm.php
+++ b/src/Form/AddChildrenWizard/FileSelectionForm.php
@@ -2,24 +2,198 @@
 
 namespace Drupal\islandora\Form\AddChildrenWizard;
 
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Batch\BatchBuilder;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\Entity\BaseFieldOverride;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemList;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\WidgetInterface;
+use Drupal\Core\Field\WidgetPluginManager;
 use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Queue\QueueFactory;
+use Drupal\Core\Queue\QueueInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\file\FileInterface;
+use Drupal\islandora\IslandoraUtils;
+use Drupal\media\MediaSourceInterface;
+use Drupal\media\MediaTypeInterface;
+use Drupal\node\NodeInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 
 class FileSelectionForm extends FormBase {
 
+  protected ?EntityTypeManagerInterface $entityTypeManager;
+
+  /**
+   * The widget plugin manager service.
+   *
+   * @var WidgetPluginManager
+   */
+  protected ?PluginManagerInterface $widgetPluginManager;
+
+  protected ?EntityFieldManagerInterface $entityFieldManager;
+
+  protected ?Connection $database;
+
+  protected ?AccountProxyInterface $currentUser;
+
+  public static function create(ContainerInterface $container) {
+    $instance =  parent::create($container);
+
+    $instance->entityTypeManager = $container->get('entity_type.manager');
+    $instance->widgetPluginManager = $container->get('plugin.manager.field.widget');
+    $instance->entityFieldManager = $container->get('entity_field.manager');
+    $instance->database = $container->get('database');
+    $instance->currentUser = $container->get('current_user');
+
+    return $instance;
+  }
+
   public function getFormId() {
     return 'islandora_add_children_wizard_file_selection';
   }
 
-  public function buildForm(array $form, FormStateInterface $form_state) {
+  protected function getMediaType(FormStateInterface $form_state) : MediaTypeInterface {
+    return $this->doGetMediaType($form_state->getTemporaryValue('wizard'));
+  }
+
+  protected function doGetMediaType(array $values) : MediaTypeInterface {
+    /** @var MediaTypeInterface $media_type */
+    return  $this->entityTypeManager->getStorage('media_type')->load($values['media_type']);
+  }
+
+  protected function getField(FormStateInterface $form_state) : FieldDefinitionInterface {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+
+    $field = $this->doGetField($cached_values);
+    $field->getFieldStorageDefinition()->set('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+
+    return $field;
+  }
+
+  protected function doGetField(array $values) : FieldDefinitionInterface {
+    $media_type = $this->doGetMediaType($values);
+    $media_source = $media_type->getSource();
+    $source_field = $media_source->getSourceFieldDefinition($media_type);
+
+    $fields = $this->entityFieldManager->getFieldDefinitions('media', $media_type->id());
+
+    return isset($fields[$source_field->getFieldStorageDefinition()->getName()]) ?
+      $fields[$source_field->getFieldStorageDefinition()->getName()] :
+      $media_source->createSourceField();
+  }
+
+  protected function getWidget(FormStateInterface $form_state) : WidgetInterface {
+    return $this->widgetPluginManager->getInstance([
+      'field_definition' => $this->getField($form_state),
+      'form_mode' => 'default',
+      'prepare' => TRUE,
+    ]);
+  }
+
+  public function buildForm(array $form, FormStateInterface $form_state) : array {
     // TODO: Using the media type selected in the previous step, grab the
     // media bundle's "source" field, and create a multi-file upload widget
     // for it, with the same kind of constraints.
+
+    $field = $this->getField($form_state);
+    $items = FieldItemList::createInstance($field, $field->getName(), $this->getMediaType($form_state)->getTypedData());
+
+    $form['#tree'] = TRUE;
+    $form['#parents'] = [];
+    $widget = $this->getWidget($form_state);
+    $form['files'] = $widget->form(
+      $items,
+      $form,
+      $form_state
+    );
+
     return $form;
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    // TODO: Implement submitForm() method.
-    $form_state->setError($form, 'Oh no!');
+    $cached_values = $form_state->getTemporaryValue('wizard');
+
+    dsm($form_state);
+
+    $builder = (new BatchBuilder())
+      ->setTitle($this->t('Creating children...'))
+      ->setInitMessage($this->t('Initializing...'))
+      ->setFinishCallback([$this, 'batchProcessFinished']);
+    foreach ($form_state->getValue($this->doGetField($cached_values)->getName()) as $file) {
+      $builder->addOperation([$this, 'batchProcess'], [$file, $cached_values]);
+    }
+    batch_set($builder->toArray());
   }
+
+  public function batchProcess($fid, array $values, &$context) {
+    $transaction = \Drupal::database()->startTransaction();
+
+    try {
+      $taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
+
+      /** @var FileInterface $file */
+      $file = $this->entityTypeManager->getStorage('file')->load($fid);
+      $file->setPermanent();
+      if ($file->save() !== SAVED_UPDATED) {
+        throw new \Exception("Failed to update file '{$file->id()}' to be permanent.");
+      }
+
+      $node_storage = $this->entityTypeManager->getStorage('node');
+      $parent = $node_storage->load($values['node']);
+
+      // Create a node (with the filename?) (and also belonging to the target node).
+      $node = $node_storage->create([
+        'type' => $values['bundle'],
+        'title' => $file->getFilename(),
+        IslandoraUtils::MEMBER_OF_FIELD => $parent,
+        'uid' => $this->currentUser->id(),
+        'status' => NodeInterface::PUBLISHED,
+        IslandoraUtils::MODEL_FIELD => $values['model'] ?
+          $taxonomy_term_storage->load($values['model']) :
+          NULL,
+      ]);
+      if ($node->save() !== SAVED_NEW) {
+        throw new \Exception("Failed to create node for file '{$file->id()}'.");
+      }
+
+      // Create a media with the file attached and also pointing at the node.
+      $media = $this->entityTypeManager->getStorage('media')->create([
+        'bundle' => $values['media_type'],
+        'name' => $file->getFilename(),
+        IslandoraUtils::MEDIA_OF_FIELD => $node,
+        IslandoraUtils::MEDIA_USAGE_FIELD => $values['use'] ?
+          $taxonomy_term_storage->loadMultiple($values['use']) :
+          NULL,
+        $this->doGetField($values)->getName() => $file->id(),
+      ]);
+      if ($media->save() !== SAVED_NEW) {
+        throw new \Exception("Failed to create media for file '{$file->id()}.");
+      }
+    }
+    catch (HttpExceptionInterface $e) {
+      $transaction->rollBack();
+      throw $e;
+    }
+    catch (\Exception $e) {
+      $transaction->rollBack();
+      throw new HttpException(500, $e->getMessage(), $e);
+    }
+  }
+
+  public function batchProcessFinished() {
+    // TODO: Dump out status message of some sort?
+  }
+
 }
diff --git a/src/Form/AddChildrenWizard/Form.php b/src/Form/AddChildrenWizard/Form.php
index 2be973be..d689fd37 100644
--- a/src/Form/AddChildrenWizard/Form.php
+++ b/src/Form/AddChildrenWizard/Form.php
@@ -2,15 +2,83 @@
 
 namespace Drupal\islandora\Form\AddChildrenWizard;
 
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\TempStore\SharedTempStoreFactory;
 use Drupal\ctools\Wizard\FormWizardBase;
+use Drupal\islandora\IslandoraUtils;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 class Form extends FormWizardBase {
 
+  protected IslandoraUtils $utils;
+  protected string $nodeId;
+  protected RouteMatchInterface $currentRoute;
+  protected AccountProxyInterface $currentUser;
+
+  /**
+   * Constructor.
+   */
+  public function __construct(
+    SharedTempStoreFactory $tempstore,
+    FormBuilderInterface $builder,
+    ClassResolverInterface $class_resolver,
+    EventDispatcherInterface $event_dispatcher,
+    RouteMatchInterface $route_match,
+    $tempstore_id,
+    IslandoraUtils $utils,
+    RouteMatchInterface $current_route_match,
+    AccountProxyInterface $current_user,
+    $machine_name = NULL,
+    $step = NULL
+  ) {
+    parent::__construct($tempstore, $builder, $class_resolver, $event_dispatcher, $route_match, $tempstore_id,
+      $machine_name, $step);
+
+    $this->utils = $utils;
+    $this->currentRoute = $current_route_match;
+    $this->nodeId = $this->currentRoute->getParameter('node');
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getParameters() : array {
+    return array_merge(
+      parent::getParameters(),
+      [
+        'utils' => \Drupal::service('islandora.utils'),
+        'tempstore_id' => 'islandora.upload_children',
+        //'machine_name' => 'islandora_add_children_wizard',
+        'current_route_match' => \Drupal::service('current_route_match'),
+        'current_user' => \Drupal::service('current_user'),
+      ]
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMachineName() {
+    return strtr("islandora_add_children_wizard__{userid}__{nodeid}", [
+      '{userid}' => $this->currentUser->id(),
+      '{nodeid}' => $this->nodeId,
+    ]);
+  }
+
   /**
    * {@inheritdoc}
    */
   public function getOperations($cached_values) {
-    // TODO: Implement getOperations() method.
     return [
       'child_type' => [
         'title' => $this->t('Type of children'),
@@ -22,4 +90,18 @@ class Form extends FormWizardBase {
       ]
     ];
   }
+
+  public function getNextParameters($cached_values) {
+    return parent::getNextParameters($cached_values) + ['node' => $this->nodeId];
+  }
+
+  public function getPreviousParameters($cached_values) {
+    return parent::getPreviousParameters($cached_values) + ['node' => $this->nodeId];
+  }
+
+  public function finish(array &$form, FormStateInterface $form_state) {
+    parent::finish($form, $form_state); // TODO: Change the autogenerated stub
+    dsm($form_state->getTemporaryValue('wizard'));
+  }
+
 }
diff --git a/src/Form/AddChildrenWizard/TypeSelectionForm.php b/src/Form/AddChildrenWizard/TypeSelectionForm.php
index 41882e8e..fffb2ae9 100644
--- a/src/Form/AddChildrenWizard/TypeSelectionForm.php
+++ b/src/Form/AddChildrenWizard/TypeSelectionForm.php
@@ -133,12 +133,19 @@ class TypeSelectionForm extends FormBase {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
-    $this->cacheableMetadata = CacheableMetadata::createFromRenderArray($form);
+    $this->cacheableMetadata = CacheableMetadata::createFromRenderArray($form)
+      ->addCacheContexts([
+        'url',
+        'url.query_args',
+      ]);
+    $cached_values = $form_state->getTemporaryValue('wizard');
 
     $form['bundle'] = [
       '#type' => 'select',
       '#title' => $this->t('Content Type'),
       '#description' => $this->t('Each child created will have this content type.'),
+      '#empty_value' => '',
+      '#default_value' => $cached_values['bundle'] ?? '',
       '#options' => $this->getNodeBundleOptions(),
       '#required' => TRUE,
     ];
@@ -149,6 +156,8 @@ class TypeSelectionForm extends FormBase {
       '#title' => $this->t('Model'),
       '#description' => $this->t('Each child will be tagged with this model.'),
       '#options' => iterator_to_array($this->getModelOptions()),
+      '#empty_value' => '',
+      '#default_value' => $cached_values['model'] ?? '',
       '#states' => [
         'visible' => [
           ':input[name="bundle"]' => $model_states,
@@ -162,6 +171,8 @@ class TypeSelectionForm extends FormBase {
       '#type' => 'select',
       '#title' => $this->t('Media Type'),
       '#description' => $this->t('Each media created will have this type.'),
+      '#empty_value' => '',
+      '#default_value' => $cached_values['media_type'] ?? '',
       '#options' => $this->getMediaBundleOptions(),
       '#required' => TRUE,
     ];
@@ -173,6 +184,7 @@ class TypeSelectionForm extends FormBase {
         ':url' => 'https://pcdm.org/2015/05/12/use',
       ]),
       '#options' => iterator_to_array($this->getMediaUseOptions()),
+      '#default_value' => $cached_values['use'] ?? [],
       '#states' => [
         'visible' => [
           ':input[name="media_type"]' => $use_states,
@@ -191,6 +203,17 @@ class TypeSelectionForm extends FormBase {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    // TODO: Implement submitForm() method.
+    $keys = [
+      'bundle',
+      'model',
+      'media_type',
+      'use',
+    ];
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    foreach ($keys as $key) {
+      $cached_values[$key] = $form_state->getValue($key);
+    }
+    $form_state->setTemporaryValue('wizard', $cached_values);
   }
+
 }