Standalone Publishing with templates example

The question of how to publish with the standalone publisher and have it copy the files to a templated location has been asked a few times, and I thought I’d put an example together on how to achieve this.

One of the reasons why this behaviour isn’t included out of the box is that it’s hard to implement without making some assumptions. My example does make assumptions, and may not be exactly how you would like it to work, but should hopefully prove a useful starting point.

Please note that the code provided in this example is not tested in production and is not guaranteed to be stable, it is intended as an example that can be built upon.


To test my example as is, you can follow these steps, (it assumes your config is based on tk-config-default2):

  1. Download (9.0 KB) and place it inside your config’s hook folder, like I have done in the screenshot below. Note I’ve nested mine inside a tk-multi-publish2 folder:

  2. Open your .../env/includes/settings/tk-multi-publish2.yml and find the section. Modify the publish plugin hook setting to use the copied hook, so that in the end your section looks like this:
      collector: "{self}/"
      - name: Publish to Shotgun
        hook: "{self}/{config}/tk-multi-publish2/"
        settings: {}
      - name: Upload for review
        hook: "{self}/"
        settings: {}
      help_url: *help_url
      location: ""
  3. Now you need to configure the templates. To do this you add a Publish Template key in the settings section, and then under that you add a list of dictionaries. Each dictionary should contain:

    • ext key, whose value should be a list of extensions.
    • entity_type, whose value should be the entity type for the context you wish this template to apply for.
    • template, with a value of the name matching the template you would like to use.

    So for example yours could look like this:
      collector: "{self}/"
      - name: Publish to Shotgun
        hook: "{self}/{config}/tk-multi-publish2/"
          Publish Template:
          - ext: ["ma", "mb"]
            entity_type: "Asset"
            template: "maya_asset_publish"
          - ext: ["ma", "mb"]
            entity_type: "Shot"
            template: "maya_shot_publish"
          - ext: ["nk"]
            entity_type: "Shot"
            template: "nuke_shot_publish"
      - name: Upload for review
        hook: "{self}/"
        settings: {}
      help_url: *help_url
      location: ""
  4. And that’s it! Drag or select a file that has an extension that matches the one you’ve configured templates for, select the context, and publish!


The example I’ve provided has the following limitations:

  • It doesn’t support sequences of files. To enable support for sequences, the behaviour would need to be modified in the _copy_work_to_publish() method.

  • It won’t try to extract details from the path if the source file does not conform to a template. Depending on your workflow, the source file might be coming from a non toolkit folder structure where context could still be derived. To implement this, you would want to take over the publish collector hook, and parse the path your self to figure out the associated entity and set the item.context. Something like:

    # We should have already created the item by this point.
    # The path variable will be passed to the `_collect_file()` method 
    # in the collector, but for the sake of this example snippet the path will 
    # be the following.
    path = "/import/seq-100/sh-25/Comp/seq-100_sh-25_backplate.nk"
    # This would usually be imported at the top of the script.
    import re
    # Perform a regex to extract the sequence, shot and task names
    # from the path.
    # You will want to change the regex to parse the path in a way
    # that matches your structure.
    match ='(seq-\d+)/(sh-\d+)/(.*)/', path)
    if match:
        # Now we found matches, look up the Task in Shotgun that 
        # matches those names.
        sequence_name, shot_name, task_name = match.groups()
        filters = [
            ["content", "is", task_name],
            ["entity.Shot.code", "is", shot_name],
            ["entity.Shot.sg_sequence.Sequence.code", "is", sequence_name]
        task = sg.find_one("Task",filters)
        # Get a context from the task.
        context = self.sgtk.context_from_entity(task["type"], task["id"])
        # And now set the publish item's context.
        item.context = context
  • The example code silently falls back to publishing the source file in place, if a templated path couldn’t be generated. However, you may wish to implement a validation check to make sure a templated path was generated and not allow your user to publish without one.

  • There are probably others I haven’t thought of :smiley: .

Breakdown of hook code

When writing your own publish hooks, you have two options.

  1. Derive/inherit from our hooks and add your changes on top.
  2. Don’t inherit and completely handle the whole publish plugin’s publish process.

In my example publish plugin we are deriving from the hook that comes with the tk-multi-publish2, since we are only wanting to tweak the process and not completely change it. This means we don’t need to write as much code, and can still benefit from any updates to the default hook that might get released.

I’ve overridden 3 methods and one property to make this change:

  • settings
  • _copy_work_to_publish()
  • get_publish_template()
  • get_publish_path()

settings is a property that is defined in the base hook and therefore the publisher expects. And the other three methods are ones the default plugin implements.

Settings property

In here we are defining a setting that can then be customized via the environment yaml settings for the app. We add a Publish Template setting, but without any default value, since we will add that in the environment yaml files. Our plugin will then be able to query the settings at run time.

I made the choice here that I would structure the Publish Template settings in a way where each template was associated with a file type and entity type, to enable me to appropriately pick one. You may wish to categorize or group them differently for example even going as deep as to have different templates based on task and entity type, or even skip using settings entirely and have your code figure out the template via some other means.


This method will be called by the hook we are deriving from, and needs to return a Toolkit Template. It is here that we are querying the settings, to find an appropriate template based upon file extension and entity type of the context.


Now by default this method will generate a publish path if the collected item has a work_template as well as the publish_template we defined in the get_publish_template().
Now if you’re only ever wanting to handle files that already conform to a Toolkit work template path, then all you need to do here is get the work template, perhaps using sgtk.template_from_path.
In my example code, it handles both accepting files from within the Toolkit structure and outside of it. The key thing to make sure here is that you handle any keys that your template requires. My example handles the common ones, i.e. the ones that can be resolved from the context, and the name, version, and extension.

If we can’t resolve a templated publish path, then we return back the original source path.


The implementation in the default hook for this method only copies the file if a work and publish template is provided. In my example, I only require the publish template to copy the file and make use of calling the get_publish_path() again to get the destination path. As mentioned above I’m not handling sequence paths here, but it can certainly be updated to do so, and the default implementation does, so you can use that for inspiration.


And that concludes my example, I hope this is helpful to people. I’m sure some of you out there have already implemented similar behaviours, and perhaps have better approaches, please feel free to share!