Change Movies Date Added to file creation date?

I recently moved all my movies and only changed the Plex docker host path. But not the Plex docker path, which remained the same at: ‘/movies’

As a result, all the movies ‘date added’ dates changed to the date I moved my all the movies (used mv cmd) and no longer has the date the movies were actually added to my library.

I want to address this by updating the date added on all my movies to match the media file date created date.

I’m considering a PHP script like this to accomplish this. Has anyone done something like this or can provide guidance?

<?php
/**
 * Plex Media Timestamp Updater
 * Updates the added_at timestamp in Plex database to match file creation time
 * 
 * IMPORTANT: Stop Plex Media Server before running this script!
 */

// Configuration
$filepath = "."; // Path to your movie files
$plexdb = "/path/to/your/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db";
$dryRun = true; // Set to false to actually update the database

// Validate paths
if (!is_dir($filepath)) {
    exit('ERROR: File path does not exist: ' . $filepath . "\n");
}

if (!is_file($plexdb)) {
    exit('ERROR: Plex database does not exist: ' . $plexdb . "\n");
}

// Display mode
if ($dryRun) {
    echo "========================================\n";
    echo "       DRY RUN MODE - NO CHANGES\n";
    echo "========================================\n";
    echo "This will simulate the process without modifying the database.\n";
    echo "Set \$dryRun = false to perform actual updates.\n\n";
} else {
    echo "========================================\n";
    echo "         LIVE UPDATE MODE\n";
    echo "========================================\n";
    echo "WARNING: This will modify your Plex database!\n";
    echo "Make sure Plex Media Server is STOPPED before continuing!\n";
    echo "Press Enter to continue or Ctrl+C to cancel...\n";
    fgets(STDIN);
}

// Backup Plex database (only if not dry run)
if (!$dryRun) {
    $backupPath = $plexdb . '-backup-' . time();
    if (copy($plexdb, $backupPath)) {
        echo "Database backed up to: $backupPath\n\n";
    } else {
        exit("ERROR: Failed to backup database\n");
    }
} else {
    echo "Dry run mode: Skipping database backup\n\n";
}

// Open database (read-only for dry run)
try {
    if ($dryRun) {
        $db = new SQLite3($plexdb, SQLITE3_OPEN_READONLY);
    } else {
        $db = new SQLite3($plexdb);
    }
} catch (Exception $e) {
    exit('ERROR: Could not open database: ' . $e->getMessage() . "\n");
}

// Enable error handling
$db->enableExceptions(true);

$processedCount = 0;
$errorCount = 0;

// Scan directory
$files = scandir($filepath);
if ($files === false) {
    $db->close();
    exit("ERROR: Could not read directory: $filepath\n");
}

foreach ($files as $file) {
    if ($file === '.' || $file === '..') {
        continue;
    }
    
    $fileFullPath = $filepath . DIRECTORY_SEPARATOR . $file;
    
    // Skip directories
    if (!is_file($fileFullPath)) {
        continue;
    }
    
    // Get file creation time
    // Note: filectime() returns creation time on Windows, inode change time on Unix/Linux
    // For true creation time on all platforms, you may need additional extensions
    $fileCreateTime = filectime($fileFullPath);
    
    if ($fileCreateTime === false) {
        echo "Processing $file\n\tERROR: Could not get file time\n\n";
        $errorCount++;
        continue;
    }
    
    echo "Processing $file\n";
    echo "\tFile creation/change time: " . date('Y-m-d H:i:s', $fileCreateTime) . "\n";
    
    try {
        // Escape the filename for safe SQL usage
        $escapedFile = SQLite3::escapeString($file);
        
        // Find media_item_id by file path
        // Using LIKE with directory separator to better match full paths
        $query = "SELECT media_item_id FROM media_parts WHERE file LIKE '%" . SQLite3::escapeString(DIRECTORY_SEPARATOR) . "$escapedFile'";
        $result = $db->querySingle($query, true);
        
        if (!$result) {
            echo "\tmedia_item_id not found in Plex database.\n\n";
            $errorCount++;
            continue;
        }
        
        $media_item_id = $result['media_item_id'];
        echo "\tFound media_item_id: $media_item_id\n";
        
        // Find metadata_item_id by media_item_id
        $escapedMediaId = SQLite3::escapeString($media_item_id);
        $result = $db->querySingle("SELECT metadata_item_id FROM media_items WHERE id = '$escapedMediaId'", true);
        
        if (!$result) {
            echo "\tmetadata_item_id not found.\n\n";
            $errorCount++;
            continue;
        }
        
        $metadata_item_id = $result['metadata_item_id'];
        echo "\tFound metadata_item_id: $metadata_item_id\n";
        
        // Get current added_at value
        $escapedMetadataId = SQLite3::escapeString($metadata_item_id);
        $currentResult = $db->querySingle("SELECT added_at FROM metadata_items WHERE id = '$escapedMetadataId'", true);
        
        if ($currentResult && isset($currentResult['added_at'])) {
            $currentTimestamp = $currentResult['added_at'];
            echo "\tCurrent added_at: " . date('Y-m-d H:i:s', $currentTimestamp) . "\n";
            echo "\tWould update to: " . date('Y-m-d H:i:s', $fileCreateTime) . "\n";
            
            $timeDiff = abs($fileCreateTime - $currentTimestamp);
            if ($timeDiff > 0) {
                echo "\tTime difference: " . round($timeDiff / 86400, 1) . " days\n";
            } else {
                echo "\tNo change needed (timestamps match)\n";
            }
        }
        
        if ($dryRun) {
            echo "\t[DRY RUN] Would update added_at timestamp.\n\n";
            $processedCount++;
        } else {
            // Update added_at field to file creation time (as Unix epoch)
            $updateQuery = "UPDATE metadata_items SET added_at = $fileCreateTime WHERE id = '$escapedMetadataId'";
            $updateResult = $db->exec($updateQuery);
            
            if ($updateResult === false) {
                echo "\tERROR: Failed to update - " . $db->lastErrorMsg() . "\n\n";
                $errorCount++;
            } else {
                echo "\tSuccessfully updated added_at timestamp.\n\n";
                $processedCount++;
            }
        }
        
    } catch (Exception $e) {
        echo "\tERROR: " . $e->getMessage() . "\n\n";
        $errorCount++;
    }
}

$db->close();

// Summary
echo "===========================================\n";
echo "Processing complete!\n";
if ($dryRun) {
    echo "Mode: DRY RUN (no changes made)\n";
    echo "Files that would be updated: $processedCount\n";
} else {
    echo "Mode: LIVE UPDATE\n";
    echo "Successfully updated: $processedCount files\n";
}
echo "Errors encountered: $errorCount files\n";
echo "===========================================\n";

if ($dryRun) {
    echo "\nTo perform actual updates, set \$dryRun = false in the script.\n";
} else {
    echo "\nREMEMBER: Restart Plex Media Server and refresh your library.\n";
}
?>

It’s worth noting, Jellyfin was not affected by the “date added” timestamp change because, unlike Plex, Jellyfin generally tracks media using its internal file database and inode information rather than relying solely on the host path mapping of the Docker container.

Any help validating this script or a better approach to fix this inplex would be greatly appreciated.

So I performed a dry-run and one thing is not adding up. Why does the Plex webUI show date added of two days ago (2025-09-28) when I moved my movie files), BUT when I do a dry-run of the script it returns “Current added_at in database” with the correct date it was added—yet this is not what shows in the Plex webUI?

Here is the dry-run output when I ran the script:

This will simulate the process without modifying the database.
Set $dryRun = false to perform actual updates.

Dry run mode: Skipping database backup

Processing DEFCON 20 Documentary Full Version.mp4
	File creation/change time: 2025-09-28 06:30:23
	Found media_item_id: 248066
	Found metadata_item_id: 234669
	Current added_at in database: 2020-04-24 12:22:40 (epoch: 1587745360)
	File creation/change time: 2025-09-28 06:30:23 (epoch: 1759055423)
	Time difference: 1982.8 days (file is NEWER than Plex date)
	[DRY RUN] Would update added_at timestamp.

How should I change the script so the “date added” in the Plex webUI shows/uses the actual date/time it was added to the database?

I can’t help with the script but did you create a new library for this? If so, pms uses the file/folder data on the initial scan of a new library. Subsequent scans will use the system date.

Also, i don’t know what data you are looking at but there is an added_at for each episode, each season, and the show. The ui shows the date depending on which you are looking at.

I tried “Plex Dance” where I deleted the movies library and re-added it. When I sort by “Date added” it still does not show the actual file created date and instead continues to show the date the library was scanned instead…

I am trying to figure out what DB field the Plex webUI uses for “date added’ since all the original dates are correct in my database for the other fields.

As I mentioned, the file date is only used on the first scan of a new library. All other scans use the current system date.

As I stated, the file date is not being used on the first scan after the library recreation.

How do I get the date added value, when sorting by date added, to show the real date it was added which is the file creation date?

Ah sorry. You said plex dance which isn’t the same.

Maybe I got my terminology mixed up, when I couldn’t figure out where the Plex webUI was pulling the date added value, I went ahead and removed my movies library and then re-added it.

It is still not taking the file creation date or the ‘Current added_at in database’ in the webUI. It appears it’s using the date that the library was last scanned…

Anyone know what field in the database the Plex web UI is using for “date added”?

My goal is to restore the originally date added based on the ‘Current added_at in database’ value (see DB script query dry run output above), so when sorting movies by ‘date added’ it uses the date the movies were first added.