Workflow/environment variables in Alfred

This is a brief look at how to get, set and save variables in code (i.e. in Script Filters, Run Script Actions, etc.).

Introduction

In Alfred 2, you had one single variable to work with: the {query} macro. Alfred 3 added the ability to specify as many variables as you want. Alfred's own help provides a great description of working with variables in Alfred's own UI. I'm going to look more closely about getting and setting workflow/environment variables in your own code within a workflow.

First of all, it bears mentioning that all variables are strings. Sure, you can set a variable to a number in JSON or an array, but when it reaches your next script or one of Alfred's Filter Utilities, it will be a string. If you set a variable to an array (e.g. [1, 2, 3, "mach dat Mäh mal ei"]), Alfred will turn it into a single tab-delimited string ("1\t2\t3\tmach dat Mäh mal ei").

Setting variables

There are several ways to set variables. The most obvious ones are in the Workflow Environment Variables table in the workflow configuration sheet and using the Arg and Vars Utility. The configuration sheet is largely without magic, but in an Args and Vars Utility, you can use variable expansion macros: {query} expands (as always) to the input (which may be a user-entered query or the output from a previous element), and you can use {var:VARIABLE_NAME} macros for your own custom variables. This is described in detail in the above-mentioned Alfred help pages.

More interestingly, you can also set variables via the output of your scripts (i.e. dynamically) by emitting appropriate JSON. How you set variables depends on whether you are using a Script Filter or a Run Script action.

NOTE: You must use the appropriate mechanism, or it won't work!

From Run Script actions

Let's say your script outputs a URL, e.g. https://www.google.com. Normally you just do print('https://www.google.com') (or echo or puts) and that gets passed as the input to the next action. To also pass variables, you instead emit JSON in a very specific format:

{"alfredworkflow": {
    "arg": "https://www.google.com",
    "variables": {"browser": "Google Chrome"}}}

The root alfredworkflow object is required. If it's missing, Alfred won't parse the JSON, but will pass it as-is as input to the next action (which can also be very useful). Your output (i.e. the next Action's input/{query}) goes in arg, and any variables you wish to set go in the variables object.

From Script Filters

You can also set workflow variables via Script Filter feedback at three different levels: the root level, the item level and the modifier level. (Note: This only applies to JSON feedback; XML feedback is now deprecated and does not support the features described here.)

In each case, variables are set via a variables object at the appropriate level (feedback root, item or mod).

Root-level variables

Root-level variables are always passed to downstream elements regardless of which item is actioned. They are also passed back to the same Script Filter if you've set rerun, so you can use root-level variables to implement a progress bar.

browser is set to Safari for all items:

{"variables": {"browser": "Safari"},
 "items": [{"title": "Google",
   "arg": "https://www.google.com"}]}

Item-level variables

Item-level variables are only passed downstream when the item they're set on is actioned, and they override root-level variables. Root-level variables are also passed downstream when you action an item.

browser is set to Safari by default, but Google Chrome for Reddit:

{"variables": {"browser": "Safari"},
 "items": [
   {"title": "Google",
     "arg": "https://www.google.com"},
   {"title": "Reddit",
     "arg": "https://reddit.com",
     "variables": {"browser": "Google Chrome"}}]}

Modifier-level variables

Modifier-level variables are only passed downstream when the corresponding item is actioned with the appropriate modifier key pressed. They replace item-level variables (i.e. if a modifier sets any variables, Alfred ignores any variables set on its parent item) and override root-level variables.

As above, browser is set to Safari by default and Google Chrome for Reddit. But you can also pass browser=Google Chrome for Google by holding ⌘ when actioning it:

{"variables": {"browser": "Safari"},
 "items": [
   {"title": "Google",
     "arg": "https://www.google.com",
     "mods": {"cmd": {"variables": {"browser": "Google Chrome"}}}},
   {"title": "Reddit",
     "arg": "https://reddit.com",
     "variables": {"browser": "Google Chrome"}}]}

Using variables

So you've set a few variables, and now you want to use them. Within Alfred elements like Arg and Vars or Filter Utilities, you use the above-mentioned {var:VARIABLE_NAME} macros. Very simple.

Where it gets a little more complicated is in your own code. First and foremost, {var:VARIABLE_NAME} macro expansion does not work in Run Script actions, Script Filters or any other script boxes in Alfred.

When Alfred runs your code, it does not use {var:...} macros, but rather takes any workflow variables and sets them as environment variables for your script. Using the above example again, Alfred would pass “https://www.google.com" to my script as input (either via ARGV or {query} depending on the settings) and it would set the environment variable browser to Safari or Google Chrome. How you retrieve environment variables depends on the language you're using.

bash

The variables are already in the global namespace. Just use $browser

Python

Use the os.environ dictionary or os.getenv('VARIABLE_NAME'):

import os
browser = os.environ['browser']

# Or
browser = os.getenv('browser')

AppleScript

Use system attribute:

set theBrowser to (system attribute "browser")

JavaScript (JXA)

Use $.getenv():

ObjC.import('stdlib');
var browser = $.getenv('browser');

PHP

Use getenv():

$browser = getenv('browser');

// Or
$browser = $_ENV['browser'];

(Please see this comment by juliosecco on why you should use getenv() over $_ENV.)

Ruby

Use ENV:

browser = ENV["browser"]

Saving variables

Any variables you set in a running workflow are not saved. They exist as long as the workflow is running and then disappear. Any Workflow Environment Variables will “reset” to their values in the workflow configuration sheet on the next run.

Generally, this is what you want, but sometimes you want to save a variable's value. For example, you might have an API_KEY Workflow Environment Variable in the configuration sheet. The user can enter their API key for the service in the configuration sheet, but you'd also like to add the ability to set it from within your workflow, e.g. with a setapikey Keyword and corresponding Run Script action.

As of version 3.6, Alfred provides the set configuration and remove configuration AppleScript functions to manipulate the variables set in the Workflow Configuration Sheet.

AppleScript

The following applies to Alfred 4+. For Alfred 3, see below.

NOTE: The with exportable clause is optional. If not specified, the variable defaults to “Don't Export”.

To set variable browser to value Safari in workflow net.deanishe.demo:

tell application id "com.runningwithcrayons.Alfred" to set configuration "browser" to value "Safari" in workflow "net.deanishe.demo" with exportable

As Alfred exports the bundle ID of the running workflow to the environment variable alfred_workflow_bundleid, you can use this instead of hard-coding the bundle ID:

set bundleID to (system attribute "alfred_workflow_bundleid")

tell application id "com.runningwithcrayons.Alfred"
    set configuration "browser" to value "Safari" in workflow bundleID with exportable
end tell

The corresponding call to remove a variable is:

tell application id "com.runningwithcrayons.Alfred" to remove configuration "browser" in workflow "net.deanishe.demo"

JavaScript (JXA)

The following applies to Alfred 4+. For Alfred 3, see below.

The equivalents to the above in JXA JavaScript (again, the exportable variable is optional):

Application('com.runningwithcrayons.Alfred').setConfiguration('browser', {
    toValue: 'Safari',
    inWorkflow: 'net.deanishe.demo',
    exportable: true
});

Or using the alfred_workflow_bundleid variable:

ObjC.import('stdlib');
Application('com.runningwithcrayons.Alfred').setConfiguration('browser', {
    toValue: 'Safari',
    inWorkflow: $.getenv('alfred_workflow_bundleid'),
    exportable: true
});

And to remove a variable:

Application('com.runningwithcrayons.Alfred').removeConfiguration('browser', {
  inWorkflow: $.getenv('alfred_workflow_bundleid')
});

Alfred 3

If you're still using Alfred 3, call the application by name, not bundle ID.

AppleScript:

tell application "Alfred 3" to ...

JXA:

Application('Alfred 3')...
Fix anchors
4adcd86