diff --git a/photoswipe.theme.inc b/photoswipe.theme.inc
index 2082f63..5ed10ee 100644
--- a/photoswipe.theme.inc
+++ b/photoswipe.theme.inc
@@ -100,6 +100,13 @@ function template_preprocess_photoswipe_image_formatter(array &$variables)
         }
         break;
 
+      case 'custom':
+        $entity_type = $entity->getEntityTypeId();
+        $caption = \Drupal::token()->replace($settings['photoswipe_caption_custom'],
+          [$entity_type => $entity, 'file' => $item],
+          ['clear' => TRUE, 'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId()]);
+        break;
+
       default:
         // Assume the user wants to use another node field as the caption
         $field_view['#view_mode'] = ($settings['photoswipe_view_mode']) ? $settings['photoswipe_view_mode'] : 'default';
diff --git a/src/Plugin/Field/FieldFormatter/PhotoswipeFieldFormatter.php b/src/Plugin/Field/FieldFormatter/PhotoswipeFieldFormatter.php
index a99aea7..90ad7b4 100644
--- a/src/Plugin/Field/FieldFormatter/PhotoswipeFieldFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/PhotoswipeFieldFormatter.php
@@ -29,6 +29,7 @@ class PhotoswipeFieldFormatter extends FormatterBase {
       'photoswipe_node_style' => '',
       'photoswipe_image_style' => '',
       'photoswipe_caption' => '',
+      'photoswipe_caption_custom' => '',
       'photoswipe_view_mode' => '',
     ] + parent::defaultSettings();
   }
@@ -70,6 +71,7 @@ class PhotoswipeFieldFormatter extends FormatterBase {
       'title' => $this->t('Image title tag'),
       'alt' => $this->t('Image alt tag'),
       'node_title' => $this->t('Entity title'),
+      'custom' => $this->t('Custom (with tokens)'),
     ];
     // Add the other node fields as options.
     if (!empty($form['#fields'])) {
@@ -88,6 +90,43 @@ class PhotoswipeFieldFormatter extends FormatterBase {
       '#description' => $this->t('Field that should be used for the caption.'),
     ];
 
+    $element['photoswipe_caption_custom'] = [
+      '#title' => $this->t('Custom caption'),
+      '#type' => 'textarea',
+      '#default_value' => $this->getSetting('photoswipe_caption_custom'),
+      '#states' => [
+        'visible' => [
+          ':input[name$="[settings][photoswipe_caption]"]' => ['value' => 'custom'],
+        ],
+      ],
+    ];
+    if (\Drupal::moduleHandler()->moduleExists('token')) {
+      $element['photoswipe_token_caption'] = [
+        '#type' => 'fieldset',
+        '#title' => t('Replacement patterns'),
+        '#theme' => 'token_tree_link',
+        // A KLUDGE! Need to figure out current entity type in both entity display and views contexts.
+        '#token_types' => ['file', 'node'],
+        '#states' => [
+          'visible' => [
+            ':input[name$="[settings][photoswipe_caption]"]' => ['value' => 'custom'],
+          ],
+        ],
+      ];
+    }
+    else {
+      $element['photoswipe_token_caption'] = [
+        '#type' => 'fieldset',
+        '#title' => $this->t('Replacement patterns'),
+        '#description' => '<strong class="error">' . $this->t('For token support the <a href="@token_url">token module</a> must be installed.', ['@token_url' => 'http://drupal.org/project/token']) . '</strong>',
+        '#states' => [
+          'visible' => [
+            ':input[name$="[settings][photoswipe_caption]"]' => ['value' => 'custom'],
+          ],
+        ],
+      ];
+    }
+
     // Add the current view mode so we can control view mode for node fields.
     $element['photoswipe_view_mode'] = [
       '#type' => 'hidden',
@@ -140,6 +179,7 @@ class PhotoswipeFieldFormatter extends FormatterBase {
         'alt' => $this->t('Image alt tag'),
         'title' => $this->t('Image title tag'),
         'node_title' => $this->t('Entity title'),
+        'custom' => $this->t('Custom (with tokens)'),
       ];
       if (array_key_exists($this->getSetting('photoswipe_caption'), $caption_options)) {
         $caption_setting = $caption_options[$this->getSetting('photoswipe_caption')];
