New to fastlane? Click here to open the installation & setup instructions first

1) Install the latest Xcode command line tools

xcode-select --install

2) Install fastlane

# Using RubyGems
sudo gem install fastlane -NV

# Alternatively using Homebrew
brew cask install fastlane

3) Navigate to your project and run

fastlane init

More Details

Advanced fastlane

Passing Parameters

To pass parameters from the command line to your lane, use the following syntax:

fastlane [lane] key:value key2:value2

fastlane deploy submit:false build_number:24

To access those values, change your lane declaration to also include |options|

before_all do |lane, options|
  # ...

before_each do |lane, options|
  # ...

lane :deploy do |options|
  # ...
  if options[:submit]
    # Only when submit is true
  # ...
  increment_build_number(build_number: options[:build_number])
  # ...

after_all do |lane, options|
  # ...

after_each do |lane, options|
  # ...

error do |lane, exception, options|
  if options[:debug]
    puts "Hi :)"

Switching lanes

To switch lanes while executing a lane, use the following code:

lane :deploy do |options|
  # ...
  build(release: true) # that's the important bit
  # ...

lane :staging do |options|
  # ...
  build # it also works when you don't pass parameters
  # ...

lane :build do |options|
  scheme = (options[:release] ? "Release" : "Staging")
  ipa(scheme: scheme)

fastlane takes care of all the magic for you. You can call lanes of the same platform or a general lane outside of the platform definition.

Passing parameters is optional.

Returning values

Additionally, you can retrieve the return value. In Ruby, the last line of the lane definition is the return value. Here is an example:

lane :deploy do |options|
  value = calculate(value: 3)
  puts value # => 5

lane :calculate do |options|
  # ...
  2 + options[:value] # the last line will always be the return value

Stop executing a lane early

The next keyword can be used to stop executing a lane before it reaches the end.

lane :build do |options|
  if cached_build_available?
    UI.important 'Skipping build because a cached build is available!'
    next # skip doing the rest of this lane

private_lane :cached_build_available? do |options|
  # ...

When next is used during a lane switch, control returns to the previous lane that was executing.

lane :first_lane do |options|
  puts "If you run: `fastlane first_lane`"
  puts "You'll see this!"
  puts "As well as this!"

private_lane :second_lane do |options|
  puts "This won't be shown"

When you stop executing a lane early with next, any after_each and after_all blocks you have will still trigger as usual :+1:

before_each and after_each blocks

before_each blocks are called before any lane is called. This would include being called before each lane you've switched to.

before_each do |lane, options|
  # ...

after_each blocks are called after any lane is called. This would include being called after each lane you've switched to. Just like after_all, after_each is not called if an error occurs. The error block should be used in this case.

after_each do |lane, options|
  # ...

e.g. With this scenario, before_each and after_each would be called 4 times: before the deploy lane, before the switch to archive, sign, and upload, and after each of these lanes as well.

lane :deploy do

lane :archive do
  # ...

lane :sign do
  # ...

lane :upload do
  # ...

Output environment variables

  • To hide timestamps in each row, set the FASTLANE_HIDE_TIMESTAMP environment variable to true.
  • To disable output formatting, set the FASTLANE_DISABLE_OUTPUT_FORMAT environment variable to true.

Interacting with the user

Instead of using puts, raise and gets, please use the helper class UI across all fastlane tools:

UI.message "Neutral message (usually white)"
UI.success "Successfully finished processing (usually green)"
UI.error "Wahaha, what's going on here! (usually red)"
UI.important "Make sure to use Windows (usually yellow)"

UI.header "Inputs" # a big box

name = UI.input("What's your name? ")
if UI.confirm("Are you '#{name}'?")
  UI.success "Oh yeah"
  UI.error "Wups, invalid"

UI.password("Your password please: ") # password inputs are hidden

###### A "Dropdown" for the user
project ="Select your project: ", ["Test Project", "Test Workspace"])

UI.success("Okay #{name}, you selected '#{project}'")

###### To run a command use
FastlaneCore::CommandExecutor.execute(command: "ls",
                                    print_all: true,
                                        error: proc do |error_output|
                                          # handle error here

###### or if you just want to receive a simple value use this only if the command doesn't take long
diff = Helper.backticks("git diff")

###### fastlane "crash" because of a user error everything that is caused by the user and is not unexpected
UI.user_error!("You don't have a project in the current directory")

###### an actual crash when something unexpected happened
UI.crash!("Network timeout")

###### a deprecation message
UI.deprecated("The '--key' parameter is deprecated")

Run actions directly

If you just want to try an action without adding them to your Fastfile yet, you can use

fastlane run notification message:"My Text" title:"The Title"

To get the available options for any action run fastlane action [action_name]. You might not be able to set some kind of parameters using this method.

Shell values

You can get value from shell commands:

output = sh("pod update")

Priorities of parameters and options

The order in which fastlane tools take their values from

  1. CLI parameter (e.g. gym --scheme Example) or Fastfile (e.g. gym(scheme: 'Example'))
  2. Environment variable (e.g. GYM_SCHEME)
  3. Tool specific config file (e.g. Gymfile containing scheme 'Example')
  4. Default value (which might be taken from the Appfile, e.g. app_identifier from the Appfile)
  5. If this value is required, you'll be asked for it (e.g. you have multiple schemes, you'll be asked for it)

Importing another Fastfile

Within your Fastfile you can import another Fastfile using 2 methods:


Import a Fastfile from a local path

import "../GeneralFastfile"

override_lane :from_general do
  # ...


Import from another git repository, which you can use to have one git repo with a default Fastfile for all your project

import_from_git(url: '')
# or
import_from_git(url: '',
               path: 'fastlane/Fastfile')

lane :new_main_lane do
  # ...

This will also automatically import all the local actions from this repo.


You should import the other Fastfile on the top above your lane declarations. When defining a new lane fastlane will make sure to not run into any name conflicts. If you want to overwrite an existing lane (from the imported one), use the override_lane keyword.

Using fastlane_require

If you're using a third party gem, it is recommended to use fastlane_require in your Fastfile instead of require. fastlane_require will:

  • Verify the gem is installed
  • Show installation instructions if not installed
  • Require the gem (like require does)


fastlane_require 'hike'

lane :release do
  # Do stuff with hike

Environment Variables

You can define environment variables in a .env or .env.default file in the same directory as your Fastfile. Environment variables are loaded using dotenv. Here's an example:


fastlane also has a --env option that allows loading of environment specific dotenv files. .env and .env.default will be loaded before environment specific dotenv files are loaded. The naming convention for environment specific dotenv files is .env.<environment>

For example, fastlane <lane-name> --env development will load .env, .env.default, and .env.development

Alternatively, as environment variables are not a fastlane specific thing, you can also use standard methods to set them:

DELIVER_USER="" fastlane test


export DELIVER_USER="";
fastlane test

Although it kind of defeats the purpose of using them in the first place (not to have their content in any files), you can also set them in your Fastfile:


Lane Properties

It can be useful to dynamically access properties of the current lane. These are available in lane_context:

lane_context[SharedValues::PLATFORM_NAME]        # Platform name, e.g. `ios`, `android` or empty (for root level lanes)
lane_context[SharedValues::LANE_NAME]            # The name of the current lane preceded by the platform name (stays the same when switching lanes)
lane_context[SharedValues::DEFAULT_PLATFORM]     # Default platform

and environment variables:


Lane Context

The different actions can communicate with each other using a shared hash. You can access this in your lanes with the following code:


Here are some examples:

lane_context[SharedValues::BUILD_NUMBER]                # Generated by `increment_build_number`
lane_context[SharedValues::VERSION_NUMBER]              # Generated by `increment_version_number`
lane_context[SharedValues::SNAPSHOT_SCREENSHOTS_PATH]   # Generated by _snapshot_
lane_context[SharedValues::PRODUCE_APPLE_ID]            # The Apple ID of the newly created app
lane_context[SharedValues::IPA_OUTPUT_PATH]             # Generated by _gym_
lane_context[SharedValues::DSYM_OUTPUT_PATH]            # Generated by _gym_
lane_context[SharedValues::SIGH_PROFILE_PATH]           # Generated by _sigh_
lane_context[SharedValues::SIGH_UDID]                   # The UDID of the generated provisioning profile
lane_context[SharedValues::HOCKEY_DOWNLOAD_LINK]        # Generated by `hockey`
lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]      # Generated by `gradle`
lane_context[SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS] # Generated by `gradle`
lane_context[SharedValues::GRADLE_FLAVOR]               # Generated by `gradle`
lane_context[SharedValues::GRADLE_BUILD_TYPE]           # Generated by `gradle`

To get information about available lane variables, run fastlane action [action_name].

Private lanes

Sometimes you might have a lane that is used from different lanes, for example:

lane :production do
  # ...
  build(release: true)
  appstore # Deploy to the AppStore
  # ...

lane :beta do
  # ...
  build(release: false)
  crashlytics # Distribute to testers
  # ...

lane :build do |options|
  # ...
  # ...

It probably doesn't make sense to execute the build lane directly using fastlane build. You can hide this lane using

private_lane :build do |options|
  # ...

This will hide the lane from:

  • fastlane lanes
  • fastlane list
  • fastlane docs

And also, you can't call the private lane using fastlane build.

The resulting private lane can only be called from another lane using the lane switching technology.

Load own actions from external folder

Add this to the top of your Fastfile.

actions_path '../custom_actions_folder/'

Skip update check when launching fastlane

You can set the environment variable FASTLANE_SKIP_UPDATE_CHECK to skip the update check.

Hide changelog information at the end of running fastlane

You can set the environment variable FASTLANE_HIDE_CHANGELOG to hide the detailed changelog information when new fastlane versions are available.

Adding Credentials

You can add credentials for use by fastlane to your keychain using the CredentialsManager command line interface. This is useful for situations like CI environments.

Adding a Credential

fastlane fastlane-credentials add --username
Password: *********
Credential********* added to keychain.

Removing a Credential

fastlane fastlane-credentials remove --username
password has been deleted.

Control configuration by lane and by platform

In general, configuration files take only the first value given for a particular configuration item. That means that for an Appfile like the following:

app_identifier ""
app_identifier ""

the app_identfier will be "" and the second value will be ignored. The for_lane and for_platform configuration blocks provide a limited exception to this rule.

All configuration files (Appfile, Matchfile, Screengrabfile, etc.) can use for_lane and for_platform blocks to control (and override) configuration values for those circumstances.

for_lane blocks will be called when the name of lane invoked on the command line matches the one specified by the block. So, given a Screengrabfile like:

locales ['en-US', 'fr-FR', 'ja-JP']

for_lane :screenshots_english_only do
  locales ['en-US']

for_lane :screenshots_french_only do
  locales ['fr-FR']

locales will have the values ['en-US', 'fr-FR', 'ja-JP'] by default, but will only have one value when running the fastlane screenshots_english_only or fastlane screenshots_french_only.

for_platform gives you similar control based on the platform for which you have invoked fastlane. So, for an Appfile configured like:

app_identifier ""

for_lane :enterprise do
  app_identifier "com.forlane.enterprise"

for_platform :mac do
  app_identifier "com.forplatform.mac"

  for_lane :release do
    app_identifier "com.forplatform.mac.forlane.release"

you can expect the app_identifier to equal "com.forplatform.mac.forlane.release" when invoking fastlane mac release.

Manually Manage the fastlane match Repo

Most users can benefit from match's automatic management of the repo that stores certificates and provisioning profiles. From time to time, it may be necessary to manually change the files in this repo.

For example, fastlane requires admin access to the Apple Developer account to generate the appropriate files. If you are provided with an updated certificate or profile but do not have admin access, you can manually edit the repo.

Warning: Manually editing your match repo can introduce unexpected behavior and is not recommended. Proceed with caution.




  1. fastlane encrypts the repo, and
  2. fastlane doesn't support manual edits to the repo

it's necessary to manually decrypt, then modify, then encrypt, the repo to make any changes.

These instructions presuppose you already have fastlane match configured correctly.

🔓 Decryption Instructions

The easiest way to decrypt the repo is to use the fastlane match GitHelper class. You can do this from an interactive Ruby console:

$ bundle console

Then, require match and set the appropriate parameters:

irb(main):001:0> require 'match'
irb(main):002:0> git_url = ''
=> ""
irb(main):003:0> shallow_clone = false
=> false
irb(main):004:0> manual_password = 'example-password'
=> "example-password"

Now call GitHelper.clone, which will clone and decrypt the repo for you. Assign the return value to workspace, which we'll need later when we re-encrypt:

irb(main):005:0> workspace = Match::GitHelper.clone(git_url, shallow_clone, manual_password: manual_password)
[14:49:30]: Cloning remote git repo...
[14:49:31]: 🔓  Successfully decrypted certificates repo
=> "/var/folders/0j/29ytx6wx0fg86sznfb4mqdph0000gn/T/d20170314-14350-11hmdro"

The above example checks out the master branch by default. A common match pattern is to create a separate branch per each developer team (the name of the branch being the team identifier). You can optionally pass in the branch name as a parameter to the clone method:

irb(main):005:0> workspace = Match::GitHelper.clone(git_url, shallow_clone, manual_password: manual_password, branch: 'ABCDE12345')

The directory beginning with /var/folders contains the decrypted git repo. Modify it as needed.

If you are updating a .p12 file, ensure it's exported from the keychain without a password, since match doesn't support importing private keys with a password.

Warning: Do not commit your changes. Allow fastlane to do that for you.

Once your changes are made, we'll need to encrypt the repo and push it.

🔒 Encryption Instructions

In the Ruby console, call GitHelper.commit_changes, passing in the commit message you want. For example:

irb(main):006:0> Match::GitHelper.commit_changes(workspace, "remove password from p12 file", git_url)

Again, pass in the branch name if your changes are not on master:

irb(main):006:0> Match::GitHelper.commit_changes(workspace, "remove password from p12 file", git_url, 'ABCDE12345')

Your changes will be encrypted, committed, and pushed.

Note: If your keychain doesn't include the encryption passcode, you may be prompted for it. If so, just enter the same password you used to decrypt it.

Directory behavior

fastlane was designed in a way that you can run fastlane from both the root directory of the project, and from the ./fastlane sub-folder.

Take this example Fastfile on the path fastlane/Fastfile

sh "pwd" # => "[root]/fastlane"
puts Dir.pwd # => "[root]/fastlane"

lane :something do
  sh "pwd" # => "[root]/fastlane"
  puts Dir.pwd # => "[root]/fastlane"


The implementation of my_action looks like this:

def run(params)
  puts Dir.pwd # => "[root]"

Notice how every action and every plugin's code runs in the root of the project, while all user code from the Fastfile runs inside the ./fastlane directory. This is important to consider when migrating existing code from your Fastfile into your own action or plugin. To change the directory manually you can use standard Ruby blocks:

Dir.chdir("..") do
  # code here runs in the parent directory

This behavior isn't great, and has been like this since the very early days of fastlane. As much as we'd like to change it, there is no good way to do so, without breaking thousands of production setups, so we decided to keep it as is for now.


The Appfile stores useful information that are used across all fastlane tools like your Apple ID or the application Bundle Identifier, to deploy your lanes faster and tailored on your project needs.

The Appfile has to be inside your ./fastlane directory.

By default an Appfile looks like:

app_identifier "net.sunapps.1" # The bundle identifier of your app
apple_id ""  # Your Apple email address

# You can uncomment the lines below and add your own
# team selection in case you're in multiple teams
# team_name "Felix Krause"
# team_id "Q2CBPJ58CA"

# To select a team for App Store Connect use
# itc_team_name "Company Name"
# itc_team_id "18742801"

If you have different credentials for App Store Connect and the Apple Developer Portal use the following code:

app_identifier ""       # The bundle identifier of your app

apple_dev_portal_id ""  # Apple Developer Account
itunes_connect_id ""     # App Store Connect Account

team_id "Q2CBPJ58CA" # Developer Portal Team ID
itc_team_id "18742801" # App Store Connect Team ID

If your project has different bundle identifiers per environment (i.e. beta, app store), you can define that by using for_platform and/or for_lane block declaration.

app_identifier "net.sunapps.1"
apple_id ""
team_id "Q2CBPJ58CC"

for_platform :ios do
  team_id '123' # for all iOS related things
  for_lane :test do
    app_identifier ''

You only have to use for_platform if you're using platform [platform_name] do in your Fastfile.

fastlane will always use the lane specific value if given, otherwise fall back to the value on the top of the file. Therefore, while driving the :test lane, this configuration is loaded:

app_identifier ""
apple_id ""
team_id "123"

Accessing from fastlane

If you want to access those values from within your Fastfile use

identifier = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)

Building Actions

Using FastlaneCore::Configuration

Most actions accept one or more parameters to customize their behavior. Actions define their parameters in an available_options method. This method returns an array of FastlaneCore::ConfigItem objects to describe supported options. Each option is declared by creating a new ConfigItem, e.g.:
  key: :file,
  env_name: "MY_NEW_ACTION_FILE",
  description: "A file to operate on",
  type: String,
  optional: false

This declares a file option for use with the action in a Fastfile, e.g.:

my_new_action(file: "file.txt")

If the optional env_name is present, an environment variable with the specified name may also be used in place of an option in the Fastfile:

MY_NEW_ACTION_FILE=file.txt fastlane run my_new_action

The type argument to the FastlaneCore::ConfigItem initializer specifies the name of a Ruby class representing a standard data type. Supplied arguments will be coerced to the specified type. Some standard types support default conversions.

Boolean parameters

Ruby does not have a single class to represent a Boolean type. When specifying Boolean parameters, use is_string: false, without specifying a type, e.g.:
  key: :commit,
  env_name: "MY_NEW_ACTION_COMMIT",
  description: "Commit the results if true",
  optional: true,
  default_value: false,
  is_string: false

When passing a string value, e.g. from an environment variable, certain set string values are recognized:


These values may also be passed in all caps, e.g. MY_NEW_ACTION_COMMIT=YES.

Array parameters

If a parameter is declared with type: Array and a String argument is passed, an array will be produced by splitting the string using the comma character as a delimiter:
  key: :files,
  env_name: "MY_NEW_ACTION_FILES",
  description: "One or more files to operate on",
  type: Array,
  optional: false
my_new_action(files: "file1.txt,file2.txt")

This is received by the action as ["file1.txt", "file2.txt"].

This also means a parameter that accepts an array may take a single string as an argument:

my_new_action(files: "file.txt")

This is received by the action as ["file.txt"].

Comma-separated lists are particularly useful when using environment variables:

export MY_NEW_ACTION_FILES=file1.txt,file2.txt

Polymorphic parameters

To allow for different types to be passed to a parameter (beyond what is provided above), specify is_string: false without a type field. Use an optional verify_block argument (see below) or verify the argument within your action. If the block does not raise, the option is considered verified. The UI.user_error! method is a convenient way to handle verification failure.
  key: :polymorphic_option,
  is_string: false,
  verify_block: ->(value) { verify_option(value) }

def verify_option(value)
  case value
  when String
    @polymorphic_option = value
  when Array
    @polymorphic_option = value.join(" ")
  when Hash
    @polymorphic_option = value.to_s
    UI.user_error! "Invalid option: #{value.inspect}"

Callback parameters

If your action needs to provide a callback, specify Proc for the type field.
  key: :callback,
  description: "Optional callback argument",
  optional: true,
  type: Proc

To invoke the callback in your action, use the Proc#call method and pass any arguments:

params[:callback].call(result) if params[:callback]

To notify the user of success or failure, it's usually best just to return a value such as true or false from your action. Use a callback for contextual error handling. For example, the built-in sh action passes the entire command output to an optional error_callback:

callback = lambda do |result|
  handle_missing_file if result =~ /file not found/i
  handle_auth_failure if result =~ /login failed/i

sh "some_cmd", error_callback: callback

Note on Procs

When passing a block as a parameter to an action or ConfigItem, use a Proc object. There are three ways to create an instance of Proc in Ruby.

Using the lambda operator:

verify_block = lambda do |value|


verify_block = do |value|

Using the Proc literal notation:

verify_block = ->(value) { ... }

Note that you cannot pass a block literal as a Proc.

Verify blocks

Use a verify_block argument with your ConfigItem to provide special argument verification:

verify_block = lambda do |value|
  # Has to be a String to get this far
  uri = URI(value)
  UI.error "Invalid scheme #{uri.scheme}" unless uri.scheme == "http" || uri.scheme == "https"
  key: :url,
  type: String,
  verify_block: verify_block

The verify_block requires a Proc argument (see above).

Conflicting options

If your action includes multiple conflicting options, use conflicting_options in the ConfigItem for each. Make sure conflicting options are optional.
  key: :text,
  type: String,
  optional: true,
  conflicting_options: [:text_file]
  key: :text_file,
  type: String,
  optional: true,
  conflicting_options: [:text]

You can also pass a conflict_block (a Proc, see above) if you want to implement special handling of conflicting options:

conflict_block = do |other|
  UI.user_error! "Unexpected conflict with option #{other}" unless [:text, :text_file].include?(other)
  UI.message "Ignoring :text_file in favor of :text"
  key: :text,
  type: String,
  optional: true,
  conflicting_options: [:text_file],
  conflict_block: conflict_block
  key: :text_file,
  type: String,
  optional: true,
  conflicting_options: [:text],
  conflict_block: conflict_block

Optional parameters

Parameters with optional: true will be nil unless a default_value field is present. Make sure the default_value is reasonable unless it's acceptable for the key to be absent.
  key: :build_configuration,
  description: "Which build configuration to use",
  type: String,
  optional: true,
  default_value: "Release"
  key: :offset,
  description: "Offset to start from",
  type: Integer,
  optional: true,
  default_value: 0
  key: :workspace,
  description: "Optional workspace path",
  type: String,
  optional: true
  # Not every project has a workspace, so nil is a good default value here.

Within the action params[:build_configuration] will never be nil. Specifying the default_value is preferable to something in code like:

config = params[:build_configuration] || "Release"

Default values are included in the documentation for action parameters.

Configuration files

Many built-in actions such as deliver, gym and scan support configuration files (Deliverfile, Gymfile, Scanfile). This is useful for actions with many options. To add support for a configuration file to a custom action, call load_configuration_file early, usually as the first line of run:

  # ...

This will load any parameters specified in MyNewActionfile. This method looks for the specified file in ./fastlane, ./.fastlane and ., in that order. The file is evaluated by the Ruby interpreter. You may specify they key from any FastlaneCore::ConfigItem as a method call in the configuration file:

command "ls -la"
files %w{file1.txt file2.txt}

Resolution order

Parameters are resolved from different sources in the following order:

  1. A parameter directly passed to an action using the key, usually from the Fastfile.
  2. An environment variable, if the env_name is set.
  3. A configuration file used in load_configuration_file.
  4. The default_value of the ConfigItem. If not explicitly set, this will be nil.

User input and output

The FastlaneCore::UI utility may be used to display output to the user and also request input from an action. UI includes a number of methods to customize the output for different purposes:

UI.message "Hello from my_new_action."
UI.important "Warning: This is a new action."
UI.error "Something unexpected happened in my_new_action. Attempting to continue."
method description
error Displays an error message in red
important Displays a warning or other important message in yellow
success Displays a success message in green
message Displays a message in the default output color
deprecated Displays a deprecation message in bold blue
command Displays a command being executed in cyan
command_output Displays command output in magenta
verbose Displays verbose output in the default output color
header Displays a message in a box for emphasis

Methods ending in an exclamation point (!) terminate execution of the current lane and report an error:

UI.user_error! "Could not open file #{file_path}"
method description
crash! Report a catastrophic error
user_error! Rescue an exception in your action and report a nice message to the user
shell_error! Report failure of a shell command
build_failure! Report a build failure
test_failure! Report a test failure
abort_with_message! Report a failure condition that prevents continuing

Note that these methods raise exceptions that are rescued in the runner context for the lane. This terminates further lane execution, so it is not necessary to return.

# No need for "and return" here
UI.user_error!("Could not open file #{file_path}") and return if file.nil?

The following methods may be used to prompt the user for input.

if UI.interactive?
  name = UI.input "Please enter your name: "
  is_correct = UI.confirm "Is this correct? "
  choice = "Please choose from the following list:", %w{alpha beta gamma}
  password = UI.password "Please enter your password: "
method description
interactive? Indicates whether interactive input is possible
input Prompt the user for string input
confirm Ask the user a binary question
select Prompt the user to select from a list of options
password Prompt the user for a password (masks output)

Invoking shell commands

If your action needs to run a shell command, there are several methods. You can easily determine the exit status of the command and capture all terminal output from the command.

Using Kernel#system

Use the Ruby system method call to invoke a command string. This does not redirect stdin, stdout or stderr, so output formatting will be unaffected. It executes the command in a subshell.

system "cat fastlane/Fastfile"

Upon command completion, the method returns true or false to indicate completion status. The $? global variable will also indicate the exit status of the command.

system "cat fastlane/Fastfile"
UI.user_error! "Could not execute command" unless $?.exitstatus == 0

If the command to be executed is not found, system will return nil, and $?.exitstatus will be nonzero.

Using backticks

To capture the output of a command, enclose the command in backticks:

pod_cmd = `which pod`
UI.important "'pod' command not found" if pod_cmd.empty?

Because you are capturing stdout, the command output will not appear at the terminal unless you log it using UI. Formatting may be lost when capturing command output. The entire output to stdout will be captured after the command returns. Output to stderr is not captured or redirected. The $? global variable will indicate the completion status of the command.

If the command to be executed is not found, Errno::ENOENT is raised.

Using the sh method

You can also use the built-in sh method:

sh "pwd"

This is called the same way in an action as in a Fastfile. This provides consistent logging of command output. All output to stdout and stderr is logged via UI.

The sh method can accept a block, which will receive the Process::Status returned by the command, the complete output of the command, and an equivalent shell command upon completion of the command.

sh "ls", "-la" do |status, result, command|
  unless status.success?
    UI.error "Command #{command} (pid #{}) failed with status #{status.exitstatus}"
  UI.message "Output is #{result.inspect}"

To be notified only when an error occurs, use the error_callback parameter (a Proc):

success = true
sh("pwd", error_callback: ->(result) { success = false })
UI.user_error "Command failed" unless success

The result argument to the error_callback is the entire string output of the command.

If the command to be executed is not found, Errno::ENOENT is raised without calling the block or error_callback.

If an error_callback or block is not provided, and the command executes and returns an error, an exception is raised, and lane execution is terminated unless the exception is rescued. The exit status of the command will be available in $?. It is also available as the first argument to a block.

The return value of the method is the output of the command, unless a block is given. Then the output is available within the block, and the return value of sh is the return value of the block. This enables usage like:

if sh command { |s| s.success? }
  UI.success "Command succeeded"
  UI.error "Command failed"

Anywhere other than an action or a Fastfile (e.g. in helper code), you can invoke this method as

Escaping in shell commands

Use shellwords to escape arguments to shell commands.

`git commit -aqm #{Shellwords.escape commit_message}`
system "cat #{path.shellescape}"

When using system or sh, pass a list of arguments instead of shell-escaping individual arguments.

sh "git", "commit", "-aqm", commit_message
system "cat", path

Calling other actions

Some built-in utility actions, such as sh, may be used in custom actions (e.g., in plugins). It's not generally a good idea to call a complex action from another action. In particular:

  • If you're calling one plugin action from another plugin action, you should probably refactor your plugin helper to be more easily called from all actions in the plugin.
  • Avoid wrapping complex built-in actions like deliver and gym.
  • There can be issues with one plugin depending on another plugin.
  • Certain simple built-in utility actions may be used with other_action in your action, such as: other_action.git_add, other_action.git_commit.
  • Think twice before calling an action from another action. There is often a better solution.