diff --git a/flysystem_s3.js b/flysystem_s3.js
index 2202512..d1fe9b2 100644
--- a/flysystem_s3.js
+++ b/flysystem_s3.js
@@ -156,11 +156,11 @@
   Drupal.behaviors.flySystemS3CorsUpload = {
     attach: function (context) {
       var $context = $(context);
-      $context.find('.js-form-type-flysystem-s3-cors-managed-file .js-form-submit').once('flysystem-s3-cors-file-upload').on('mousedown', Drupal.flysystemS3.submitCorsUpload);
+      $context.find('.js-form-type-managed-file .flysystem-s3-cors .js-form-submit').on('mousedown', Drupal.flysystemS3.submitCorsUpload);
     },
     detach: function (context) {
       var $context = $(context);
-      $context.find('.js-form-type-flysystem-s3-cors-managed-file .js-form-submit').off('mousedown', Drupal.flysystemS3.submitCorsUpload);
+      $context.find('js-form-type-managed-file .flysystem-s3-cors .js-form-submit').off('mousedown', Drupal.flysystemS3.submitCorsUpload);
     }
   };
 
diff --git a/flysystem_s3.module b/flysystem_s3.module
new file mode 100644
index 0000000..f321722
--- /dev/null
+++ b/flysystem_s3.module
@@ -0,0 +1,68 @@
+<?php
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function flysystem_s3_element_info_alter(array &$types) {
+  $types['managed_file']['#attached']['library'][] = 'flysystem_s3/drupal.s3_cors_upload';
+  $types['managed_file']['#process'][] = 'flysystem_s3_process_managed_file_cors_upload';
+}
+
+function flysystem_s3_is_scheme_s3($scheme) {
+  // There can be multiple S3 schemes, so check the flysystem settings.
+  /** @var \Drupal\flysystem\FlysystemFactory $factory */
+  $flysystemFactory = \Drupal::service('flysystem_factory');
+  $settings = $flysystemFactory->getSettings($scheme);
+  return !empty($settings['driver']) && $settings['driver'] === 's3';
+}
+
+/**
+ * Render API callback: Expands the managed_file element type.
+ *
+ * Expands the file type to include Upload and Remove buttons, as well as
+ * support for a default value.
+ */
+function flysystem_s3_process_managed_file_cors_upload(&$element, FormStateInterface $form_state, &$complete_form) {
+  if (empty($element['#upload_location']) || (isset($element['#s3_cors']) && !$element['#s3_cors'])) {
+    return $element;
+  }
+
+  $scheme = file_uri_scheme($element['#upload_location']);
+  if ($scheme && flysystem_s3_is_scheme_s3($scheme) && \Drupal::currentUser()->hasPermission('use S3 CORS upload')) {
+
+    // Disable the default progress indicator.
+    foreach (Element::children($element) as $key) {
+      if (isset($element[$key]['#ajax']['progress'])) {
+        unset($element[$key]['#ajax']['progress']);
+      }
+    }
+    unset($element['upload_button']['#ajax']['progress']['url']);
+
+    $element['#attributes']['class'][] = 'flysystem-s3-cors';
+
+    // If a specific ACL has not been set on this upload, fetch the default
+    // ACL from the Flysystem scheme.
+    if (!isset($element['#acl'])) {
+      $scheme = file_uri_scheme($element['#upload_location']);
+      /** @var \Drupal\flysystem\FlysystemFactory $factory */
+      $flysystemFactory = \Drupal::service('flysystem_factory');
+      $scheme_settings = $flysystemFactory->getSettings($scheme);
+      if (isset($scheme_settings['config']['options']['ACL'])) {
+        $element['#acl'] = $scheme_settings['config']['options']['ACL'];
+      }
+      else {
+        $element['#acl'] = 'private';
+      }
+    }
+
+    // Add data attributes that are used by flysystem_s3.js to submit the
+    // AJAX request to sign the upload.
+    $element['upload']['#attributes']['data-s3-acl'] = $element['#acl'];
+    $element['upload']['#attributes']['data-s3-destination'] = $element['#upload_location'];
+  }
+
+  return $element;
+}
diff --git a/src/Element/S3CorsManagedFile.php b/src/Element/S3CorsManagedFile.php
deleted file mode 100644
index f1910a6..0000000
--- a/src/Element/S3CorsManagedFile.php
+++ /dev/null
@@ -1,192 +0,0 @@
-<?php
-
-namespace Drupal\flysystem_s3\Element;
-
-use Drupal\Component\Utility\Crypt;
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Render\Element\FormElement;
-use Drupal\Core\Site\Settings;
-use Drupal\file\Element\ManagedFile;
-use Drupal\file\Entity\File;
-
-/**
- * Provides an AJAX/progress aware widget for uploading and saving a file.
- *
- * @FormElement("flysystem_s3_cors_managed_file")
- */
-class S3CorsManagedFile extends ManagedFile {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getInfo() {
-    $class = get_called_class();
-    return [
-      '#input' => TRUE,
-      '#process' => [
-        [$class, 'processManagedFile'],
-      ],
-      '#element_validate' => [
-        [$class, 'validateManagedFile'],
-      ],
-      '#pre_render' => [
-        [$class, 'preRenderManagedFile'],
-      ],
-      '#theme' => 'file_managed_file',
-      '#theme_wrappers' => ['form_element'],
-      '#progress_indicator' => 'bar',
-      '#progress_message' => NULL,
-      '#upload_validators' => [],
-      '#upload_location' => NULL,
-      '#size' => 22,
-      '#multiple' => FALSE,
-      '#extended' => FALSE,
-      '#attached' => [
-        'library' => ['flysystem_s3/drupal.s3_cors_upload'],
-      ],
-    ];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
-    // Find the current value of this field.
-    $fids = !empty($input['fids']) ? explode(' ', $input['fids']) : [];
-    foreach ($fids as $key => $fid) {
-      $fids[$key] = (int) $fid;
-    }
-    $force_default = FALSE;
-
-    // Process any input and save new uploads.
-    if ($input !== FALSE) {
-      $input['fids'] = $fids;
-      $return = $input;
-
-      // Uploads take priority over all other values.
-      if ($files = file_managed_file_save_upload($element, $form_state)) {
-        if ($element['#multiple']) {
-          $fids = array_merge($fids, array_keys($files));
-        }
-        else {
-          $fids = array_keys($files);
-        }
-      }
-      else {
-        // Check for #filefield_value_callback values.
-        // Because FAPI does not allow multiple #value_callback values like it
-        // does for #element_validate and #process, this fills the missing
-        // functionality to allow File fields to be extended through FAPI.
-        if (isset($element['#file_value_callbacks'])) {
-          foreach ($element['#file_value_callbacks'] as $callback) {
-            $callback($element, $input, $form_state);
-          }
-        }
-
-        // Load files if the FIDs have changed to confirm they exist.
-        if (!empty($input['fids'])) {
-          $fids = [];
-          foreach ($input['fids'] as $fid) {
-            if ($file = File::load($fid)) {
-              $fids[] = $file->id();
-              // Temporary files that belong to other users should never be
-              // allowed.
-              if ($file->isTemporary()) {
-                if ($file->getOwnerId() != \Drupal::currentUser()->id()) {
-                  $force_default = TRUE;
-                  break;
-                }
-                // Since file ownership can't be determined for anonymous users,
-                // they are not allowed to reuse temporary files at all. But
-                // they do need to be able to reuse their own files from earlier
-                // submissions of the same form, so to allow that, check for the
-                // token added by $this->processManagedFile().
-                elseif (\Drupal::currentUser()->isAnonymous()) {
-                  $token = NestedArray::getValue($form_state->getUserInput(), array_merge($element['#parents'], array('file_' . $file->id(), 'fid_token')));
-                  if ($token !== Crypt::hmacBase64('file-' . $file->id(), \Drupal::service('private_key')->get() . Settings::getHashSalt())) {
-                    $force_default = TRUE;
-                    break;
-                  }
-                }
-              }
-            }
-          }
-          if ($force_default) {
-            $fids = [];
-          }
-        }
-      }
-    }
-
-    // If there is no input or if the default value was requested above, use the
-    // default value.
-    if ($input === FALSE || $force_default) {
-      if ($element['#extended']) {
-        $default_fids = isset($element['#default_value']['fids']) ? $element['#default_value']['fids'] : [];
-        $return = isset($element['#default_value']) ? $element['#default_value'] : ['fids' => []];
-      }
-      else {
-        $default_fids = isset($element['#default_value']) ? $element['#default_value'] : [];
-        $return = ['fids' => []];
-      }
-
-      // Confirm that the file exists when used as a default value.
-      if (!empty($default_fids)) {
-        $fids = [];
-        foreach ($default_fids as $fid) {
-          if ($file = File::load($fid)) {
-            $fids[] = $file->id();
-          }
-        }
-      }
-    }
-
-    $return['fids'] = $fids;
-    return $return;
-  }
-
-  /**
-   * Render API callback: Expands the managed_file element type.
-   *
-   * Expands the file type to include Upload and Remove buttons, as well as
-   * support for a default value.
-   */
-  public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
-    $use_cors = \Drupal::currentUser()->hasPermission('use S3 CORS upload');
-
-    if ($use_cors) {
-      // Disable the default progress indicator.
-      $element['#progress_indicator'] = 'none';
-    }
-
-    $element = parent::processManagedFile($element, $form_state, $complete_form);
-
-    if ($use_cors) {
-      $element['#attributes']['class'][] = 'flysystem-s3-cors';
-
-      // If a specific ACL has not been set on this upload, fetch the default
-      // ACL from the Flysystem scheme.
-      if (!isset($element['#acl'])) {
-        $scheme = file_uri_scheme($element['#upload_location']);
-        /** @var \Drupal\flysystem\FlysystemFactory $factory */
-        $flysystemFactory = \Drupal::service('flysystem_factory');
-        $scheme_settings = $flysystemFactory->getSettings($scheme);
-        if (isset($scheme_settings['config']['options']['ACL'])) {
-          $element['#acl'] = $scheme_settings['config']['options']['ACL'];
-        }
-        else {
-          $element['#acl'] = 'private';
-        }
-      }
-
-      // Add data attributes that are used by flysystem_s3.js to submit the
-      // AJAX request to sign the upload.
-      $element['upload']['#attributes']['data-s3-acl'] = $element['#acl'];
-      $element['upload']['#attributes']['data-s3-destination'] = $element['#upload_location'];
-    }
-
-    return $element;
-  }
-
-}
diff --git a/src/Plugin/Field/FieldWidget/S3CorsUploadWidget.php b/src/Plugin/Field/FieldWidget/S3CorsUploadWidget.php
deleted file mode 100644
index 0b07c77..0000000
--- a/src/Plugin/Field/FieldWidget/S3CorsUploadWidget.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-
-namespace Drupal\flysystem_s3\Plugin\Field\FieldWidget;
-
-use Drupal\Core\Field\FieldDefinitionInterface;
-use Drupal\Core\Field\FieldItemListInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Render\Element;
-use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
-
-/**
- * Plugin implementation of the 's3_cors_upload' widget.
- *
- * @FieldWidget(
- *   id = "s3_cors_upload",
- *   label = @Translation("S3 CORS File Upload"),
- *   field_types = {
- *     "file",
- *     "image"
- *   }
- * )
- */
-class S3CorsUploadWidget extends FileWidget {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function isApplicable(FieldDefinitionInterface $field_definition) {
-    // The file field must be using the S3 upload scheme.
-    $scheme = $field_definition->getFieldStorageDefinition()
-      ->getSetting('uri_scheme');
-    return static::isSchemeS3($scheme);
-  }
-
-
-  public static function isSchemeS3($scheme) {
-    // There can be multiple S3 schemes, so check the flysystem settings.
-    /** @var \Drupal\flysystem\FlysystemFactory $factory */
-    $flysystemFactory = \Drupal::service('flysystem_factory');
-    $settings = $flysystemFactory->getSettings($scheme);
-    return !empty($settings['driver']) && $settings['driver'] === 's3';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function defaultSettings() {
-    $settings = [];
-    $settings['s3_acl'] = NULL;
-    return $settings;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsForm(array $form, FormStateInterface $form_state) {
-    $element = [];
-
-    $element['s3_acl'] = [
-      '#type' => 'select',
-      '#title' => $this->t('ACL'),
-      '#description' => $this->t('In addition to whatever options you select here the object owner will always be granted full control of all objects. See the <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html">API documentation</a> for more information.'),
-      '#options' => [
-        // http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
-        'private' => $this->t('No one else has access. (private)'),
-        'public-read' => $this->t('All users get READ access. (public-read)'),
-        'public-read-write' => $this->t('All users get READ & WRITE access. Not recommended. (public-read-write)'),
-        'authenticated-read' => $this->t('Authenticated users get READ access. (authenticated-read)'),
-        'bucket-owner-read' => $this->t('Bucket owner gets READ access. (bucket-owner-read)'),
-        'bucket-owner-full-control' => $this->t('Bucket owner has full control. (bucket-owner-full-control)'),
-      ],
-      '#empty_option' => $this->getSetting('Use scheme default'),
-      '#default_value' => $this->getSetting('s3_acl'),
-    ];
-
-    return $element;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsSummary() {
-    $summary = [];
-    $summary[] = $this->t('ACL: @acl', array('@acl' => $this->getSetting('s3_acl') ?: $this->t('Use scheme default')));
-    return $summary;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
-    $element = parent::formElement($items, $delta, $element, $form, $form_state);
-    $element['#type'] = 'flysystem_s3_cors_managed_file';
-    $element['#acl'] = $this->getSetting('s3_acl');
-
-    $element_info = $this->elementInfo->getInfo('flysystem_s3_cors_managed_file');
-    $element['#process'] = array_merge($element_info['#process'], array(array(get_called_class(), 'process')));
-
-    return $element;
-  }
-}
