Pragmatism in the real world

Writing a Lightroom Classic plug-in

As part of my migration to Lightroom Classic from Photos, I needed to recreate my folder and album structure. In Photos, folders container albums (or other folders) and albums contain photos. An album cannot contain a folder. This maps directly to Lightroom Classic which has “collection sets” for folders and “collections” for albums.

To transfer the folder and album information over to Lightroom Classic, I wrote an Apple Script for Photos that added a keyword of the format PhotosExport>folder1>folder2>albumPhotosExport>folder1>folder2>album to each photo. As > is the hierarchical separator in Lightroom Classic, this became a nested set of keywords that I then needed to turn into collections within nested collection sets.

This is done using a plug-in.

The SDK

To create a Lightroom Classic plug-in you need the SDK. Go to the Adobe I/O page for Lightroom Classic and click the “Start building today” button. Click enough times and you’ll end up with a zip file on your hard drive. For me, this file is LR_9.0_201910171147-f2855d44.master_mac_SDK.zip.

Unzip and you get a number of folders. The most useful are the HTML files in API Reference and the Lightroom SDK Guide PDF in Manual. You’ll refer to these resources a lot!

Creating a Plug-in

To create a plug-in you need a directory with an extension of .lrplugin. This is treated as a package by macOS, so you can also use .lrdevplugin so that it works as a standard folder in Finder.

We’ll create hello.devplugin.

The manifest file

Every Lightroom Classic plug-in has a manifest file called Info.lua. This file returns a table with metadata about the plug-in along with menu definitions and information on which files to call when an event happens.

A minimal Info.lua looks like this:

hello.devplugin/Info.lua:

return {
  VERSION = { major=1, minor=0, revision=0, },

  LrSdkVersion = 9.0,
  LrSdkMinimumVersion = 4.0,

  LrToolkitIdentifier = "com.akrabat.hello",
  LrPluginName = "Hello",
  LrPluginInfoUrl="https://akrabat.com/writing-a-lightroom-classic-plug-in",
}

LrSdkVersion, LrToolkitIdentifier & LrPluginName are required and your plug-in will not work without them. See the “Writing standard plug-ins for Lightroom” section of Chapter 2 of the Lightroom SDK Guide for details on all the possible items you can put in Info.lua.

Installing our Plug-in

To install our plug-in, in Lightroom Classic, go to File -> Plug-in Manager…:

  • Click Add at the bottom of the left hand list.
  • Navigate to and select the hello.lrdevplugin directory and click Add Plug-in.
  • Click Enable in the Status section for the Hello plugin.

You’ll see something like this:

Lr plugin manager

This plugin currently does nothing, of course!

For the plug-in I wrote, I simply needed a way to run the code and the way to do this is to add a menu item to Lightroom Classic which will run a file in the plug-in.

To add a menu item, we edit Info.lua and add LrFileMenuItems and/or LrLibraryMenuItems tables to the returned table.

To add a menu item to Library->Plug-in Extras, we add this to the table returned by Info.lua:

  LrLibraryMenuItems = {
    {
      title = "Hello",
      file = "hello.lua",
    },
  },  

LrLibraryMenuItems is a table of tables where each inner table is a menu item consisting of the displayed text and the file to be executed on selection. This code will create a menu item called “Hello” which, when selected, will run the hello.lua file.

Lr plugin manager

Hello.lua file

Let’s create a simple hello.lua to prove it works:

hello.devplugin/hello.lua:

local LrDialogs = import 'LrDialogs'
LrDialogs.message("Hello World")

To use any of the SDK functionality, we have to import it into our file. The list of available tables to import is in the API reference. LrDialogs provides various dialog boxes and message() is the simplest to use, so is a good choice for testing we’re all set up.

Now you can reload the plug-in in the Plugin Manager and the menu item should appear and upon clicking it, you’ll see this dialog:

Lr dialog hello

We have a working plug-in!

If you get an error that hello.lua cannot be found, try restarting Lightroom Classic.

Interacting with the catalog

To interact with the catalog, you must place your code in an async function started by startAsyncTask(). Fortunately, Lua supports closures which makes it easy as you can see in thsi code that gets the filename of the selected photo:

hello.devplugin/hello.lua:

local LrApplication = import 'LrApplication'
local LrDialogs = import 'LrDialogs'
local LrTasks = import 'LrTasks'

LrTasks.startAsyncTask(function ()
  local catalog = LrApplication.activeCatalog()

  local photo = catalog:getTargetPhoto()
  if photo == nil then
    LrDialogs.message("Hello World", "Please select a photo")
    return
  end

  local filename = photo:getFormattedMetadata("fileName")
  local msg = string.format("The selected photo's filename is %q", filename)
  LrDialogs.message("Hello World", msg)
end)

Run the plug-in by selecting the Library -> Plug-in Extras -> Hello menu item and you should see a dialog like this:

Lr selected filename

Writing to the catalog

If you want to write to the catalog to say create a keyword, collection, or collection set, etc. then you must do this within a withWriteAccessDo() function like this:

hello.devplugin/hello.lua:

local LrApplication = import 'LrApplication'
local LrDialogs = import 'LrDialogs'
local LrTasks = import 'LrTasks'

LrTasks.startAsyncTask(function ()
  local catalog = LrApplication.activeCatalog()
  local collection

  catalog:withWriteAccessDo("Create _Hello collection", function()
    collection = catalog:createCollection("_Hello", nil, true)
  end)

  catalog:withWriteAccessDo("Add selected photos to the _Hello collection", function()
    local photos = catalog:getTargetPhotos();
    collection:addPhotos(photos)
    local msg = string.format("Added selected photos to %q", collection:getName())
    LrDialogs.message("Hello World", msg)
  end)
end)

Note that most of the time you have to start a separate withWriteAccessDo() block if you want to operate on the thing you’ve created, though there are some exceptions. The API Reference has this note about it:

As of version 3.0, changes you make to the database within this call do not have immediate effect, but are written to the database upon successful completion of the callback function. This means, in general, that if you create a new item (for instance, by calling catalog:createCollection()), you cannot retrieve information about that item until the with___AccessDo has finished. There are some special cases where you can reference the newly-created item; for instance, after creating a collection set, you can create a collection within that set. These cases are noted in the API documentation.

In this example, I create a top level collection called “_Hello” in the first withWriteAccessDo() block and then in the second, I add all the selected photos to it. Be aware of your local scope when doing this sort of thing though.

Logging

I’ve found that logging is vital for developing a plug-in. The LrLogger table allows for writing messages to the operating system’s console or to file. The simplest way to set this up is to add this near the top of your file:

local LrLogger = import 'LrLogger'
local logger = LrLogger('HelloWorldPlugin')
logger:enable("print")
local log = logger:quickf('info')

When you call enable() can you pass in "print" to output the console or "logfile" to write to disk. The log file is named after your logger’s name and written to the ~/Documents/lrClassicLogs/ directory (or equivalent on Windows). In this case, the full filename is ~/Documents/lrClassicLogs/HelloWorldPlugin.log. Further details are in the “Debugging your plug-in” section of the Lightroom SDK Guide.

You can now write log messages using the string.format style:

log("Created collection %q", collection.getName())

In conclusion

I’ve found that it is relatively easy to get started writing a plug-in for Lightroom Classic and the docs are quite good. The Internet is a good resource for further information though you need to craft your queries carefully to get answers about the SDK specifically rather than about how to use the Lightroom Classic GUI. Also, note that Lightroom Classic used to be called just plain Lightroom, so most SDK resources reference the old name.

I’ve published one Lightroom Classic plug-in so far. My collection-creator plug-in creates collections within nested collection sets from a keyword hierarchy and was useful for migrating from Apple Photos. It’s on GitHub, so you have a look at how I approached it. There are also many other Lightroom plug-ins on GitHub which are useful to look at.

Have a play – this is a good way to automate work within Lightroom Classic!

6 thoughts on “Writing a Lightroom Classic plug-in

  1. Hi, great to know! I'm thinking about creating a plugin to change temperature and other sliders using the Touch Bar , I'm wondering, is there a way for me to trigger a plugin like The Fader with a hotkey ? I tried Mac own shortcut creator but it doesn't trigger the plugin

  2. Hi! Could you please help me make a plugin for Auto-importing files from a folder without having to move the jpeg files? The Auto-import function in LR is messing up my system when I'm using a wget script to auto-download jpegs from my GoPro, because LR keeps moving them..

  3. Hi Rob,

    After using your instructions I'm not able to enable the logging function.

    There is no folder called: …/Documents/lrClassicLogs
    Also there is no Config.lua file on my system

    I have tried several things but no log is appearing on my system.

    Do you know what might be the problem?

  4. Thanks for the example, but is there anywhere a list of valid keys, I may use instead of "filename" for getFormattedMetadata?
    I try to find out, how I can get Info about any exif-data of a photo.

Comments are closed.