Public/New-PatPlaylist.ps1

function New-PatPlaylist {
    <#
    .SYNOPSIS
        Creates a new playlist on a Plex server.
 
    .DESCRIPTION
        Creates a new regular (non-smart) playlist on the Plex server. You must specify
        the playlist title and at least one initial item. The Plex API does not support
        creating empty playlists.
 
    .PARAMETER Title
        The title/name of the new playlist. Must be unique on the server.
 
    .PARAMETER Type
        The type of content the playlist will contain. Valid values are:
        - video (default): Movies, TV shows, or other video content
        - audio: Music tracks
        - photo: Photos
 
    .PARAMETER RatingKey
        One or more media item rating keys to add to the playlist upon creation.
        At least one rating key is required. Rating keys can be obtained from
        library browsing commands like Get-PatLibraryItem.
 
    .PARAMETER ServerUri
        The base URI of the Plex server (e.g., http://plex.example.com:32400).
        If not specified, uses the default stored server.
 
    .PARAMETER Token
        The Plex authentication token. Required when using -ServerUri to authenticate
        with the server. If not specified with -ServerUri, requests may fail with 401.
 
    .PARAMETER PassThru
        If specified, returns the created playlist object.
 
    .EXAMPLE
        New-PatPlaylist -Title 'My Favorites' -RatingKey 12345
 
        Creates a new video playlist named 'My Favorites' with one item.
 
    .EXAMPLE
        New-PatPlaylist -Title 'Road Trip Music' -Type audio -RatingKey 67890
 
        Creates a new audio playlist named 'Road Trip Music' with one item.
 
    .EXAMPLE
        New-PatPlaylist -Title 'Weekend Watchlist' -RatingKey 12345, 67890 -PassThru
 
        Creates a playlist with two initial items and returns the created playlist object.
 
    .EXAMPLE
        Get-PatLibraryItem -SectionId 1 | Select-Object -First 5 -ExpandProperty ratingKey |
            New-PatPlaylist -Title 'Top 5' -PassThru
 
        Creates a playlist from the first 5 items in library section 1.
 
    .OUTPUTS
        PlexAutomationToolkit.Playlist (when -PassThru is specified)
 
        Returns the created playlist object with properties:
        - PlaylistId: Unique playlist identifier
        - Title: Name of the playlist
        - Type: Playlist type (video, audio, photo)
        - ItemCount: Number of items in the playlist
        - ServerUri: The Plex server URI
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Title,

        [Parameter(Mandatory = $false)]
        [ValidateSet('video', 'audio', 'photo')]
        [string]
        $Type = 'video',

        [Parameter(Mandatory = $true, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateRange(1, [int]::MaxValue)]
        [int[]]
        $RatingKey,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-PatServerUri -Uri $_ })]
        [string]
        $ServerUri,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Token,

        [Parameter(Mandatory = $false)]
        [switch]
        $PassThru
    )

    begin {
        try {
            $script:serverContext = Resolve-PatServerContext -ServerUri $ServerUri -Token $Token
        }
        catch {
            throw "Failed to resolve server: $($_.Exception.Message)"
        }

        $effectiveUri = $script:serverContext.Uri
        $headers = $script:serverContext.Headers

        # Get machine identifier for URI construction (required for playlist creation)
        try {
            $serverInfoUri = Join-PatUri -BaseUri $effectiveUri -Endpoint '/'
            $serverInfo = Invoke-PatApi -Uri $serverInfoUri -Headers $headers -ErrorAction 'Stop'
            $machineIdentifier = $serverInfo.machineIdentifier
            Write-Verbose "Server machine identifier: $machineIdentifier"
        }
        catch {
            throw "Failed to retrieve server machine identifier: $($_.Exception.Message)"
        }

        # Collect all rating keys from pipeline
        $allRatingKeys = [System.Collections.ArrayList]::new()
    }

    process {
        # Collect rating keys from pipeline
        if ($RatingKey) {
            foreach ($key in $RatingKey) {
                $null = $allRatingKeys.Add($key)
            }
        }
    }

    end {
        if (-not $PSCmdlet.ShouldProcess($Title, 'Create playlist')) {
            return
        }

        try {
            # Build the creation URI with query parameters
            $queryParts = @(
                "type=$Type",
                "title=$([System.Uri]::EscapeDataString($Title))",
                'smart=0'
            )

            # RatingKey is mandatory, so we always have items to add
            if (-not $machineIdentifier) {
                throw "Cannot create playlist: server machine identifier not available."
            }

            # Build the library URI format Plex expects
            # Format: server://machineIdentifier/com.plexapp.plugins.library/library/metadata/ratingKey
            $itemUris = foreach ($key in $allRatingKeys) {
                "server://$machineIdentifier/com.plexapp.plugins.library/library/metadata/$key"
            }
            $uriParam = $itemUris -join ','
            $queryParts += "uri=$([System.Uri]::EscapeDataString($uriParam))"

            $queryString = $queryParts -join '&'
            $uri = Join-PatUri -BaseUri $effectiveUri -Endpoint '/playlists' -QueryString $queryString

            Write-Verbose "Creating playlist '$Title' of type '$Type'"

            $result = Invoke-PatApi -Uri $uri -Method 'POST' -Headers $headers -ErrorAction 'Stop'

            if ($PassThru -and $result) {
                # The API returns the created playlist in Metadata array
                $playlist = if ($result.Metadata) {
                    $result.Metadata | Select-Object -First 1
                }
                else {
                    $result
                }

                [PSCustomObject]@{
                    PSTypeName  = 'PlexAutomationToolkit.Playlist'
                    PlaylistId  = [int]$playlist.ratingKey
                    Title       = $playlist.title
                    Type        = $playlist.playlistType
                    ItemCount   = [int]$playlist.leafCount
                    Duration    = [long]$playlist.duration
                    Smart       = ($playlist.smart -eq '1' -or $playlist.smart -eq 1)
                    Composite   = $playlist.composite
                    AddedAt     = if ($playlist.addedAt) {
                        [DateTimeOffset]::FromUnixTimeSeconds([long]$playlist.addedAt).LocalDateTime
                    } else { $null }
                    UpdatedAt   = if ($playlist.updatedAt) {
                        [DateTimeOffset]::FromUnixTimeSeconds([long]$playlist.updatedAt).LocalDateTime
                    } else { $null }
                    ServerUri   = $effectiveUri
                }
            }
        }
        catch {
            throw "Failed to create playlist '$Title': $($_.Exception.Message)"
        }
    }
}