Defining Custom Variables - autopkg/autopkg GitHub Wiki
AutoPkg input and output variables that are strings can be used as a substitute for another string value using the %variable_name% syntax. By convention, all variable names in ALL_CAPS are strings. (For more information on naming conventions, see the Input Variables page.)
While processors define a set of required and optional variables that can be used within their arguments, you can define your own custom variables within this structure. This is valuable when you need to use a particular processor multiple times within a recipe but need to persist the collected values, as on each subsequent processor run the previous output variable values get overwritten.
Typically, recipes do not need the previous values of a given output variable to persist. For example, if a recipe needs to find multiple files (FileFinder) and copy each of them to a central location (Copier) in order to create a package installer (.pkg), you can simply run the necessary processors for one file and repeat those processor steps for each additional file. Once you've completed the larger task of copying the files from the collected paths, storing what the previous values were for each item is unnecessary. However, if there is a particular reason you need to find all the files first (e.g., to include multiple values in a postinstall script that are extracted by different runs of the same processor), defining a custom variable(s) to store the result(s) of your earlier runs can be a solution.
You can also define your own variables in the Input section of a recipe or override. This is particularly useful in two main instances:
- When you have a string that appears in more than one Input variable in the exact same form (e.g., the calendar year for a software title that changes annually), that you would prefer to change in only one place;
- When a processor in your recipe supports variable substitution from an external file (notably, the user-defined XML templates that some of the Jamf Uploader processors use).
Finally, you can add documentation to your code via a custom variable (conventionally labelled as Comment).
Defining a custom variable attached in the context of a particular processor call dict is as simple as supplying it as a key in the Arguments dictionary and setting its value to the previously collected output variable.
<dict>
<key>Arguments</key>
<dict>
<key>your_custom_variable</key>
<string>%your_collected_output_variable%</string>
</dict>
<key>Processor</key>
<string>PROCESSOR_NAME</string>
</dict>In the excerpt below (extracted from a Process dict), the FileFinder processor is used twice to collect the paths of two different files.
The first time FileFinder is run (per the processor’s info), the found file path is assigned to the found_filename output variable. The second run of FileFinder sets the previously collected found_filename to a custom previous_file variable and overwrites found_filename with the collected second file path.
Subsequent processors (not listed below) can then use %previous_file% and %found_filename% in their Arguments dictionary.
<dict>
<key>Comment</key>
<string>Collect the first file path.</string>
<key>Arguments</key>
<dict>
<key>pattern</key>
<string>%RECIPE_CACHE_DIR%/%NAME%/*.pkg</string>
</dict>
<key>Processor</key>
<string>FileFinder</string>
</dict>
<dict>
<key>Comment</key>
<string>Take the collected first file path and assign it to the custom previous_file variable. Collect the second file path and overwrite the previously collected output variables.</string>
<key>Arguments</key>
<dict>
<key>pattern</key>
<string>%RECIPE_CACHE_DIR%/%NAME%/*.txt</string>
<key>previous_file</key>
<string>%found_filename%</string>
</dict>
<key>Processor</key>
<string>FileFinder</string>
</dict>You can create arbitrary variables in the Input dict that are not otherwise defined in the recipe proper.
One use of this is to create a kind of constant that gets used in other Input variables. This can be done by the recipe author or by the user in the override. Here is a simple example:
A recipe defines the Input variables NAME, PKG, and POLICY_NAME. The title being deployed brands the app with a year (e.g., Adobe Photoshop 2025). Rather than type the year into each variable that needs it (increasing the possibility of an editing error each year), a new variable VERSION_YEAR is added to the override and variable substitution is used (%VERSION_YEAR%) in the variable values that need it.
<key>Input</key>
<dict>
<key>NAME</key>
<string>Adobe_Photoshop_%VERSION_YEAR%</string>
<key>PKG</key>
<string>/Users/autopkguser/Downloads/Adobe_Photoshop_%VERSION_YEAR%_en_US_MACUNIVERSAL.pkg</string>
<key>POLICY_NAME</key>
<string>Adobe Photoshop %VERSION_YEAR% Latest</string>
<key>VERSION_YEAR</key>
<string>2025</string>
</dict>
Another case is when a processor takes an external file as an argument and parses the text looking for variable substitution. A notable case of this use is by the Jamf Uploader series of processors. For example, JamfPolicyUploader requires that an XML template be supplied by the user via the policy_template Argument. The XML template can include one or more variable substitutions (e.g., %SITE%) as per AutoPkg syntax. The processor ingests the XML file, makes variable substitutions as required, and uses that to create/update the policy in Jamf Pro. These variable names could be defined in the .jamf recipe if you write your own recipe (e.g., SITE would be a reasonable one to include if you use Sites in Jamf Pro). They could also be added after the fact in the override. If a variable substitution specified in the template does not have a corresponding value defined, the recipe will fail, even though the recipe(s) do not show the need for said variable. (This would be an argument for including such a variable in your .jamf recipe rather than adding it exclusively to your override, even if you didn’t need it for all uses of that recipe.)
One other application of custom variables is to add a key/value pair that acts as documentation within your recipe. For those writing recipes in plist format, you may be used to adding comments in HTML or XML with the syntax <!-- [comment] -->. If you run your recipe through a formatting process like plutil --convert xml1 (which is common practice), all of the comments of that type will be stripped.
An effective way to maintain those remarks is to instead add a key/value pair at the same level as Processor and Arguments, where the key name is Comment (by convention) and the string value is the comment you want to include. (This works equally well with YAML recipes.) As long as you maintain valid plist syntax, the comment will be preserved when run through a formatter like plutil --convert xml1 but the contents will functionally be ignored by AutoPkg. The plist snippet in the Processor dict Example above is an example of this usage.
Some recipe authors also apply this concept to the root dict, adding a comment for the recipe as a whole. This lets you add documentation that you do not want displayed when a user asks to view the recipe Description (autopkg info). Other authors choose to contain all such comments in the Description.