Plugins API

Metadata Processors

MusicBrainz metadata can be post-processed at two levels, album and track. The types of the arguments passed to the processor functions in the following examples are as follows:

  • tagger: picard.tagger.Tagger
  • metadata: picard.metadata.Metadata
  • release: picard.webservice.XmlNode
  • track: picard.webservice.XmlNode

Album metadata example:

      PLUGIN_NAME = "Disc Numbers"
      PLUGIN_AUTHOR = "Lukas Lalinsky"
      PLUGIN_DESCRIPTION = "Moves disc numbers from album titles to tags."

      from picard.metadata import register_album_metadata_processor
      import re

      def remove_discnumbers(tagger, metadata, release):
          matches ="\(disc (\d+)\)", metadata["album"])
          if matches:
              metadata["discnumber"] =
              metadata["album"] = re.sub(r"\(disc \d+\)", "", metadata["album"])


Track metadata example:

      PLUGIN_NAME = "Feat. Artists"
      PLUGIN_AUTHOR = "Lukas Lalinsky"
      PLUGIN_DESCRIPTION = "Removes feat. artists from track titles."

      from picard.metadata import register_track_metadata_processor
      import re

      def remove_featartists(tagger, metadata, release, track):
          metadata["title"] = re.sub(r"\(feat. [^)]*\)", "", metadata["title"])


File Formats


      PLUGIN_NAME = "..."
      PLUGIN_AUTHOR = "..."
      PLUGIN_VERSION = '...'
      PLUGIN_API_VERSIONS = ['...']
      PLUGIN_LICENSE = "..."
      PLUGIN_LICENSE_URL = "..."

      from picard.file import File
      from picard.formats import register_format

      class MyFile(File):
          EXTENSIONS = [".foo"]
          NAME = "Foo Audio"
          def read(self):
          def save(self):


Variables explanation:

  • "PLUGIN_DESCRIPTION" should be as simple as possible, while still describing the main function.
  • "PLUGIN_VERSION" should be filled with the version of Plugin.
  • "PLUGIN_API_VERSIONS" should be set to the version of Picard this plugin to run with.
  • "PLUGIN_LICENSE" should be set with the license name of the plugin. It is welcomed to use any license you like, even it's not written in this license database.
  • "PLUGIN_LICENSE_URL" should be set with the license url.



  • 0.15.1
  • 0.16.0
  • 1.0.0
  • 1.1.0
  • 1.2.0
  • 1.3.0
  • 1.4.0


  • 2.0

Tagger Script Functions

To define new tagger script functions use register_script_function(function, name=None) from the picard.script module. parser is an instance of picard.script.ScriptParser, the rest of the arguments passed to it are the arguments from the function call in the tagger script.


      PLUGIN_NAME = "Initials"
      PLUGIN_AUTHOR = "Lukas Lalinsky"
      PLUGIN_DESCRIPTION = "Provides tagger script function $initials(text)."
      PLUGIN_VERSION = '0.1'
      PLUGIN_API_VERSIONS = ['2.0']
      PLUGIN_LICENSE = "GPL-2.0"

      from picard.script import register_script_function

      def initials(parser, text):
          return "".join(a[:1] for a in text.split(" ") if a[:1].isalpha())


register_script_function supports two optional arguments:

  • eval_args: If this is False, the arguments will not be evaluated before being passed to function.
  • check_argcount: If this is False the number of arguments passed to the function will not be verified.

The default value for both of them is True.

Context Menu Actions

Right-click context menu actions can be added to albums, tracks, files in Unmatched Files, Clusters and the ClusterList (parent folder of Clusters).


      PLUGIN_NAME = u'Remove Perfect Albums'
      PLUGIN_AUTHOR = u'ichneumon, hrglgrmpf'
      PLUGIN_DESCRIPTION = u'''Remove all perfectly matched albums from the selection.'''
      PLUGIN_VERSION = '0.2'
      PLUGIN_API_VERSIONS = ['0.15.1']
      PLUGIN_LICENSE = "GPL-2.0"

      from picard.album import Album
      from picard.ui.itemviews import BaseAction, register_album_action

      class RemovePerfectAlbums(BaseAction):
          NAME = 'Remove perfect albums'

          def callback(self, objs):
              for album in objs:
                  if isinstance(album, Album) and album.is_complete() and album.get_num_unmatched_files() == 0\
                     and album.get_num_matched_tracks() == len(list(album.iterfiles()))\
                     and album.get_num_unsaved_files() == 0 and album.loaded == True:


Use register_x_action where x is album, track, file, cluster or clusterlist.