Advanced Setup

Build Servers

We support Jenkins and Xcode Bots right out of the box. Onboard your app using Fabric, push to your server and we’ll receive your dSYMs automatically.

Pro tip: Make sure that is closed.

Move the Framework

If you want to move Fabric.Framework to another directory, you can; just be sure to update the Run Script Build Phase to point to the new directory.

Example: The default Crashlytics run script build phase is:

./Fabric.framework/run <your_api_key_here>

If you move the Crashlytics framework one level down, to a directory called “Vendor”, you’ll need to modify the run script build phase to be:

./Vendor/Fabric.framework/run <your_api_key_here>

You can use our Xcode Build Phase Cheat Sheet to help.

Conditional dSYM Upload

The default set up process ensures that we get your app’s dSYMs on every build (assuming a code change). This is the preferred configuration, so that if a crash comes while you develop and do internal testing, we can show you the exact line of code that caused the crash.

However, if you would like to run Crashlytics only on certain builds, you can add logic to the run script build phase to only run when desired. Try something similar to:


if [ "$releaseConfig" = "${CONFIGURATION}" ]; then
    echo "Running Crashlytics"
    ./Fabric.framework/run <your_api_key_here>

Bitcode Enabled Apps


We’ve updated our documentation related to missing dSYMs and Bitcode enabled apps. Visit our Missing dSYMs page for more information.

Multiple Targets

To run Crashlytics with multiple targets, add a Crashlytics Run Script to each target’s Build Phase. The default Crashlytics run script build phase is:

./Fabric.framework/run <your_api_key_here>

Apps with Multiple Environments

Let’s say you have an app called MyAwesomeApp and you want to keep the bundle id the same, even though you have a staging, dev and prod version of your app.

The easiest thing to do is create a separate organization for each version, onboard each version of MyAwesomeApp to the corresponding org (just make sure you grab the new API key for each org) and you’re all set.

Why do it this way?

  • Keeps your crashes separated by org
  • Add different team members to each org to control who has access to prod vs staging, etc.

Multiple exception handlers

Using multiple uncaught exception handlers is not supported by Crashlytics.

Static or Dynamic Frameworks

If you link in other static or dynamic frameworks, you could see crash reports with missing line numbers or file information. This information comes from your dSYM files, so ensure that your dSYM files for the frameworks are placed in the same directory as the app’s dSYM and that they are built before your .app.

The recommended setup for Xcode is the following for static libraries:

"Strip Debug Symbols During Copy" -> NO
"Strip Linked Product" -> NO
"Use Separate Strip" -> NO

This will not impact your app’s size as long as you strip the symbols from your .app.

Control Submission Behavior

There are many reasons why a developer may want to control submission behavior. One example is to request permission to submit crash reports out of privacy concerns. You also may want to build a preference into your app instead of asking on every request.Additionally, you may want to add post-crash data.

First, you must set the Crashlytics delegate. When using the delegate, you must initialize Crashlytics in this order:

// correct usage
CrashlyticsKit.delegate = self;
[Fabric with:@[[Crashlytics class]]];

// incorrect usage
[Fabric with:@[Crashlytics class]]];
CrashlyticsKit.delegate = self; // Important delegate calls may have already been missed
// Override point for customization after application launch.
Crashlytics.sharedInstance().delegate = self

// Note: You will need to add CrashlyticsDelegate to your AppDelegate
// @UIApplicationMain
// class AppDelegate: UIResponder, UIApplicationDelegate, CrashlyticsDelegate {


Implementing this delegate method disables synchronous report submission, which could be important, if you experience a crash on launch. If your app crashes on launch, on the subsequent launch we will try to send a crash report synchronously blocking the main thread for a short period of time. If you call this delegate, we will not block the main thread. This could result in another crash preventing the original crash from being submitted successfully.

Ensure that the Crashlytics’ delegate is set to your object and then implement the following method:

- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;

Your delegate will be called back synchronously on the main thread informing you of the previous report, if any existed. You can then use the CLSReport object to inspect the previous report and append information. However, you must call the completion handler, otherwise this report will not be submitted.


The completion handler must be called or the report will not be sent.

It is safe, but not required, to call the completion handler from a background thread. Here’s the complete implementation including the most important part which is calling the completion handler.

- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler {
    // Use this opportunity to take synchronous action on a crash. See Crashlytics.h for
    // details and implications.

    // Maybe consult NSUserDefaults or show a UI prompt.

    // But, make ABSOLUTELY SURE you invoke completionHandler, as the SDK
    // will not submit the report until you do. You can do this from any
    // thread, but that's optional. If you want, you can just call the
    // completionHandler and return.
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
func crashlyticsDidDetectReport(forLastExecution report: CLSReport, completionHandler: @escaping (Bool) -> Void)
    // You should take some set of actions here based on knowing that a crash happened, but then make sure to call the completion handler
     // If you don't call the completion handler, the SDK will not submit the crash report.

By default the SDK uses background sessions. On iOS 6 or lower, we will wait up to 3 seconds to send a crash report before unblocking the main thread. On iOS 7 and above, the main thread is blocked for an insignificant amount of time.

If you don’t want Crashlytics to use background sessions, set this value to false. Note: background submissions cannot be used for extensions.

- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics;
func crashlyticsCanUseBackgroundSessions(_ crashlytics: Crashlytics) -> Bool {
    return true

Directories Used

Crashlytics writes files required to provide our services to a few locations in your application’s sandbox. In order to allow Crashlytics to properly function, do not remove files that your application did not create from the following directories:

NSTemporaryDirectory() // <app-sandbox>/tmp
NSCachesDirectory // <app-sandbox>/Library/Caches


Other system services, including the networking stack, use NSTemporaryDirectory() too. Deleting files owned by system services can cause unexpected behavior in your application and is not recommended.

Crashlytics Frames Appearing in Crashes

In order to show stack traces for your threads, Crashlytics needs to run some code post-crash. Because this code is executing on one of your app’s threads, Crashlytics always captures information about its own execution as part of this process. You will always see a thread executing the “CLSProcessRecordAllThreads” function. In fact, you’ll see it more than once, due to a compiler optimization called inlining.


Exceptions add an extra bit of complexity. When a Objective-C or C++ exception goes uncaught, Crashlytics records some information about it before the app is allowed to terminate. When this happens, the CLSProcessRecordAllThreads function must be run on the thread that threw the exception. This means that in the case of an exception, the “crashing” thread will always look like it is running Crashlytics code. This is normal, and is just an artifact of how we capture and present the stack traces at the time of exception.

Enable opt-in reporting

For various reasons, you may need to ensure that you’re users have given consent that information such as crash reports or analytics can be collected. There are many ways to do this and you should use the method that is most applicable to your app. Please note, you should talk with your own legal counsel in order to confirm your app’s specific needs. An example would be to have a setting that is configurable by your app’s end user and then check that setting before initializing Fabric.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    var optIn = checkOptIn()

    if (optIn == true)
    return true

func checkOptIn() -> Bool {
    //var optedIn = true
     var optedIn = false // Use if you want users to grant permission before Fabric runs
    //Practically you should be checking an app settings for this but the specific implementation details would vary by app.
    return optedIn

Opt-out after opt-in

If a user changes their data collection preference within your app, in the above example, optIn, you should advise users to completely quit the app in order for the change to take effect. Backgrounding the app will not pull in the updated setting, a new launch of the app is required.