diff --git a/plugins/tasks/Extractor.php b/plugins/tasks/Extractor.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9b7d81364a41eee4b29e49539956b206fdab760
--- /dev/null
+++ b/plugins/tasks/Extractor.php
@@ -0,0 +1,399 @@
+<?php
+
+class Extractor implements EndpointInterface
+{
+  private TaskGateway $gateway;
+
+  public function __construct (TaskGateway $gateway)
+  {
+    $this->gateway = $gateway;
+  }
+
+  /**
+   * @return array
+   * Part of the interface of orchestrator plugin to treat GET method
+   */
+  public function processEndPointGet (): array
+  {
+    // Retrieve tasks of type 'extract'
+    return $this->gateway->getObjectTypeTask('extract');
+  }
+
+  /**
+   * @param array|null $data
+   * @return array
+   * Note: Part of the interface of orchestrator plugin to treat POST method
+   */
+  public function processEndPointPost (array $data = NULL): array
+  {
+    return [];
+  }
+
+  /**
+   * @param array|null $data
+   * @return array
+   * Note: Part of the interface of orchestrator plugin to treat DELETE method
+   */
+  public function processEndPointDelete (array $data = NULL): array
+  {
+    return [];
+  }
+
+  /**
+   * @param array|null $data
+   * @return array
+   * @throws Exception
+   * Note: Part of the interface of orchestrator plugin to treat PATCH method
+   */
+  public function processEndPointPatch (array $data = NULL): array
+  {
+    $result = [];
+    $extractTasks = $this->gateway->getObjectTypeTask('extract');
+
+    // Path is now expected in the JSON body ($data)
+    $path = $data['path'] ?? '/srv/orchestrator/';
+
+    foreach ($extractTasks as $task) {
+      try {
+        if (!$this->gateway->statusAndScheduleCheck($task)) {
+          // Skip this task if it does not meet the status and schedule criteria
+          continue;
+        }
+
+        // Get the main task configuration
+        $mainTaskConfig = $this->getExtractMainTaskConfig($task['fdtasksgranularmaster'][0]);
+
+        // Get user DN from the task
+        $userDn = $task['fdtasksgranulardn'][0];
+
+        // Get user attributes
+        $userAttributes = $this->getUserAttributes($userDn, $mainTaskConfig);
+
+        // Format comes from the main task configuration
+        $format = isset($mainTaskConfig[0]['fdextractortaskformat']) ?
+                 strtolower($mainTaskConfig[0]['fdextractortaskformat'][0]) : 'csv';
+
+        // Create directory if it doesn't exist
+        $this->ensureDirectoryExists($path);
+
+        // Get main task CN for filename
+        $mainTaskCn = $this->getMainTaskCn($task['fdtasksgranularmaster'][0]);
+
+        // Determine filename with main task name and date with hour (no minutes or seconds)
+        $date = date('Y-m-d_H');  // Using only year-month-day_hour format
+        $filename = isset($data['filename']) ?
+                   $path . $data['filename'] . '_' . $date . '.' . $format :
+                   $path . $mainTaskCn . '_' . $date . '.' . $format;
+
+        // Extract and write to file
+        $success = $this->extractToFile($userAttributes, $filename, $format);
+
+        if ($success) {
+          $result[$task['dn']]['result'] = "User attributes successfully extracted to $filename";
+          $this->gateway->updateTaskStatus($task['dn'], $task['cn'][0], '2');
+        } else {
+          throw new Exception("Failed to write data to $filename");
+        }
+
+      } catch (Exception $e) {
+        $result[$task['dn']]['result'] = "Error extracting user attributes: " . $e->getMessage();
+        $this->gateway->updateTaskStatus($task['dn'], $task['cn'][0], $e->getMessage());
+      }
+    }
+
+    return $result;
+  }
+
+  /**
+   * @param string $mainTaskDn
+   * @return array
+   * Note: Retrieve the configuration from the main extract task.
+   */
+  private function getExtractMainTaskConfig (string $mainTaskDn): array
+  {
+    return $this->gateway->getLdapTasks(
+      '(objectClass=fdExtractorTasks)',
+      ['fdExtractorTaskFormat', 'cn'],
+      '',
+      $mainTaskDn
+    );
+  }
+
+  /**
+   * @param string $userDn
+   * @param array $mainTaskConfig
+   * @return array
+   * Note: Get all user attributes from the user DN.
+   */
+  private function getUserAttributes (string $userDn, array $mainTaskConfig): array
+  {
+    // Get all user data from LDAP
+    $userData = $this->gateway->getLdapTasks(
+      '(objectClass=*)',
+      ['*'],
+      '',
+      $userDn
+    );
+
+    // Process and return user data
+    $this->gateway->unsetCountKeys($userData);
+    return $userData;
+  }
+
+  /**
+   * @param string $path
+   * @return bool
+   * @throws Exception
+   * Note: Create directory if it doesn't exist.
+   */
+  private function ensureDirectoryExists (string $path): bool
+  {
+    if (!is_dir($path)) {
+      if (!mkdir($path, 0755, TRUE)) {
+        throw new Exception("Failed to create directory: $path");
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * @param array $userAttributes
+   * @param string $filename
+   * @param string $format
+   * @return bool
+   * @throws Exception
+   * Note: Extract user attributes to a file.
+   */
+  private function extractToFile (array $userAttributes, string $filename, string $format): bool
+  {
+    switch (strtolower($format)) {
+      case 'csv':
+        return $this->exportToCsv($userAttributes, $filename);
+      case 'json':
+        return $this->exportToJson($userAttributes, $filename);
+      case 'xml':
+        return $this->exportToXml($userAttributes, $filename);
+      default:
+        return $this->exportToCsv($userAttributes, $filename);
+    }
+  }
+
+  /**
+   * @param array $userAttributes
+   * @param string $filename
+   * @return bool
+   * @throws Exception
+   * Note: Export user attributes to CSV, preventing duplicate UIDs and handling new attributes.
+   */
+  private function exportToCsv (array $userAttributes, string $filename): bool
+  {
+    if (empty($userAttributes)) {
+      return TRUE; // No attributes to write
+    }
+
+    $user = $userAttributes[0];
+    $userData = [];
+    $allColumns = [];
+    $existingData = [];
+    $uidKey = 'uid'; // The attribute to check for duplicates
+    $newUserUid = '';
+
+    // Extract UID and prepare user data
+    foreach ($user as $attribute => $values) {
+      if (is_array($values)) {
+        foreach ($values as $key => $value) {
+          if (is_numeric($key)) {
+            $userData[$attribute]   = $value;
+            $allColumns[$attribute] = TRUE; // Use as associative array to avoid duplicates
+
+            if (strtolower($attribute) === $uidKey) {
+              $newUserUid = $value;
+            }
+
+            break; // Only take the first value for simplicity
+          }
+        }
+      }
+    }
+
+    // If no UID found, generate a random one
+    if (empty($newUserUid)) {
+      $newUserUid = 'user_' . uniqid();
+      $userData[$uidKey] = $newUserUid;
+      $allColumns[$uidKey] = TRUE;
+    }
+
+    // Read existing file if it exists
+    if (file_exists($filename)) {
+      $handle = fopen($filename, 'r');
+      if ($handle !== FALSE) {
+        // Read headers
+        $headers = fgetcsv($handle);
+        if ($headers !== FALSE) {
+          // Add existing headers to all columns
+          foreach ($headers as $header) {
+            $allColumns[$header] = TRUE;
+          }
+
+          // Read existing data
+          while (($row = fgetcsv($handle)) !== FALSE) {
+            $rowData = [];
+            foreach ($headers as $index => $header) {
+              $rowData[$header] = $row[$index] ?? '';
+            }
+            // Only add to existing data if it's not the same UID as new user
+            if (isset($rowData[$uidKey]) && $rowData[$uidKey] !== $newUserUid) {
+              $existingData[] = $rowData;
+            }
+          }
+        }
+        fclose($handle);
+      }
+    }
+
+    // Convert all columns associative array to indexed array
+    $finalColumns = array_keys($allColumns);
+
+    // Add new user data to existing data
+    $existingData[] = $userData;
+
+    // Write to file
+    $handle = fopen($filename, 'w'); // 'w' to overwrite with complete data
+    if ($handle === FALSE) {
+      throw new Exception("Could not open file: $filename");
+    }
+
+    try {
+      // Write headers
+      fputcsv($handle, $finalColumns);
+
+      // Write data rows
+      foreach ($existingData as $row) {
+        $outputRow = [];
+        foreach ($finalColumns as $column) {
+          $outputRow[] = $row[$column] ?? ''; // Use empty string if attribute not found
+        }
+        fputcsv($handle, $outputRow);
+      }
+
+      return TRUE;
+    } finally {
+      fclose($handle);
+    }
+  }
+
+  /**
+   * @param array $userAttributes
+   * @param string $filename
+   * @return bool
+   * Note: Export user attributes to JSON with duplicate UID handling.
+   */
+  private function exportToJson (array $userAttributes, string $filename): bool
+  {
+    $existingData = [];
+    $uidKey = 'uid';
+    $newUserUid = '';
+
+    // Get UID of new user
+    if (!empty($userAttributes[0][$uidKey][0])) {
+      $newUserUid = $userAttributes[0][$uidKey][0];
+    }
+
+    // Read existing data if file exists
+    if (file_exists($filename)) {
+      $existingJson = file_get_contents($filename);
+      if (!empty($existingJson)) {
+        $jsonData = json_decode($existingJson, TRUE) ?? [];
+
+        // Filter out any entries with the same UID
+        foreach ($jsonData as $entry) {
+          if (!isset($entry[$uidKey][0]) || $entry[$uidKey][0] !== $newUserUid) {
+            $existingData[] = $entry;
+          }
+        }
+      }
+    }
+
+    // Add new data
+    $existingData[] = $userAttributes[0];
+
+    // Write back to file
+    return file_put_contents($filename, json_encode($existingData, JSON_PRETTY_PRINT)) !== FALSE;
+  }
+
+  /**
+   * @param array $userAttributes
+   * @param string $filename
+   * @return bool
+   * Note: Export user attributes to XML with duplicate UID handling.
+   */
+  private function exportToXml (array $userAttributes, string $filename): bool
+  {
+    $dom = new DOMDocument('1.0', 'UTF-8');
+    $dom->formatOutput = TRUE;
+    $uidKey = 'uid';
+    $newUserUid = '';
+
+    // Get UID of new user
+    if (!empty($userAttributes[0][$uidKey][0])) {
+      $newUserUid = $userAttributes[0][$uidKey][0];
+    }
+
+    // Create or load XML document
+    if (file_exists($filename)) {
+      $dom->load($filename);
+      $root = $dom->documentElement;
+
+      // Remove any existing user with the same UID
+      if (!empty($newUserUid)) {
+        $xpath = new DOMXPath($dom);
+        $users = $xpath->query("/users/user[{$uidKey}='{$newUserUid}']");
+        if ($users->length > 0) {
+          foreach ($users as $user) {
+            $root->removeChild($user);
+          }
+        }
+      }
+    } else {
+      $root = $dom->createElement('users');
+      $dom->appendChild($root);
+    }
+
+    // Add new user element
+    $user = $dom->createElement('user');
+
+    foreach ($userAttributes[0] as $attribute => $values) {
+      if (is_array($values)) {
+        foreach ($values as $key => $value) {
+          if (is_numeric($key)) {
+            $attr = $dom->createElement($attribute, htmlspecialchars($value));
+            $user->appendChild($attr);
+          }
+        }
+      }
+    }
+
+    $root->appendChild($user);
+
+    // Write to file
+    return $dom->save($filename) !== FALSE;
+  }
+
+  /**
+   * Get the CN (Common Name) of the main task
+   *
+   * @param string $mainTaskDn
+   * @return string
+   */
+  private function getMainTaskCn (string $mainTaskDn): string
+  {
+    $mainTask = $this->gateway->getLdapTasks(
+      '(objectClass=*)',
+      ['cn'],
+      '',
+      $mainTaskDn
+    );
+
+    return $mainTask[0]['cn'][0] ?? 'extract';
+  }
+}
\ No newline at end of file