117

I am looking for the most efficient way of identifying the type of org (Production or Sandbox) via Apex code. URL scraping is not useful to me, since I'd like to get this information inside a trigger where URLs don't make sense.

Thanks!

4
  • 1
    If you are checking you'd better be sure your code is going to fully portable, ie it will work in any environment. I'm sure you've considered this already but thought it worth pointing out.
    – user684
    Commented Oct 7, 2012 at 21:42
  • You may want to change the accepted answer to use Daniel Hoechst's solution as it seems more current and useful.
    – Adrian Larson
    Commented May 5, 2015 at 17:48
  • 1
    Yep, makes sense!
    – Anup
    Commented May 5, 2015 at 17:49
  • 2
    There is a new answer that doesn't use a query. I'd recommend that as the new accepted answer. salesforce.stackexchange.com/a/386611/87 Commented Jan 24, 2023 at 2:44

14 Answers 14

149

Update: There is a new method that doesn't use a query. Check out this answer

In Summer '14, (version 31.0), there is a new field available on the Organization object.

SELECT IsSandbox FROM Organization LIMIT 1

From the release notes under New and Change Objects:

The Organization object has the following new read-only fields.

  • InstanceName
  • IsSandbox

You can, for example, create a lazy loader that other classes can use like this:

public Boolean runningInASandbox {
    get {
        if (runningInASandbox == null) {
            runningInASandbox = [SELECT IsSandbox FROM Organization LIMIT 1].IsSandbox;
        }
        return runningInASandbox;
    }
    set;
}

You can then call that as a Boolean value:

if (!runningInASandbox()) {
  Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
  msg.setSubject('An email');
  msg.setToAddresses(new List<String>{siteAdmin.Email});
  msg.setPlainTextBody('A message');
  Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{msg});
}
7
  • 4
    Is this still the best answer? it's such a waste of a SOQL query to have to do it this way. Commented Sep 12, 2019 at 11:59
  • AFAIK, yes. I haven't found any classes that provide this information. The closest I can find is ConnectApi.OrganizationSettings, but it doesn't look like it has any info about sandbox. Commented Sep 13, 2019 at 17:28
  • 1
    Problem with custom metadata is remembering to flip it once a sandbox refreshes. Commented Sep 13, 2019 at 20:58
  • 1
    Agreed, if you could have a script run on sandbox creation that did the query and populated the metadata that would work. I suppose you could create a class that did that, check for the metadata if it doesn't exist, run the query save the metadata that way the query would only run once for every org instance. Commented Sep 16, 2019 at 15:12
  • 1
    @GuyClairbois thanks for adding that answer! I've put a link to it from my answer so others will hopefully see it. Commented Jan 24, 2023 at 2:41
28

It's not perfect but the static method

System.URL.getSalesforceBaseUrl()

Will allow you to scrape the instance name, will that be sufficient to distinguish between production and sandbox?

2
  • 1
    Worth mentioning here that some upcoming critical updates are going to remove Sandbox instance numbers from URLs if My Domain is enabled.
    – Charles T
    Commented Apr 21, 2018 at 16:26
  • 2
    Url.getSalesforceBaseURL() will no longer be usable in Apex code with a version of 59.0 or above starting in Winter '24. Commented Sep 29, 2023 at 21:54
23

Michael Farrington recently posted an Apex method for this on his blog: http://www.michaelforce.org/recipeView?id=a0Ga000000Ekp65EAB

This method uses URL.getSalesforceBaseUrl().getHost() which should work in a trigger.

3
  • 5
    This is an exact duplicate of my answer Commented Aug 3, 2012 at 1:29
  • 3
    I tend to prefer using the orgID by principle since ID's are less prone to be modified for various reasons.
    – ebt
    Commented Aug 6, 2012 at 13:55
  • 1
    Worth mentioning here that some upcoming critical updates are going to remove Sandbox instance numbers from URLs if My Domain is enabled.
    – Charles T
    Commented Apr 21, 2018 at 16:26
9

I do think there's a method that allows this without a SOQL query and without running the risk that URLs change because of domain name changes.

(NOTE: this solution only works for distinguishing between Production and Sandbox. It does not distinguish between Production and Scratch orgs.)

Salesforce has a System.Domain class that has a method getSandboxName() that should keep working for any url change, because it is a documented Salesforce method. It either returns the sandbox name, or null if you are in a production org.

https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_System_Domain.htm#apex_System_Domain_methods

The utility method would look like this:

public static Boolean runningInASandbox {
    get {
        if (runningInASandbox == null) {
            System.Domain domain = System.DomainParser.parse(URL.getOrgDomainUrl());
            runningInASandbox = domain.getSandboxName()==null?false:true;
        }
        return runningInASandbox;
    }
    set;
} 
4
  • It is not functioning in a scratch org as expected. The method domain.getSandboxName() is returning a null value while the SOQL query is returning "IsSandbox" as true.
    – Jair
    Commented Apr 26, 2023 at 9:21
  • 1
    @jair Thanks for your comment. That's interesting. So basically a scratch org is seen as a sandbox without a name. I don't see an immediate solution for this, except for reverting back to the query mentioned in Daniel Hoechst's answer. Commented Apr 26, 2023 at 14:18
  • Also what about developer orgs, i.e. would they be identified as production as well?
    – wesaw
    Commented Sep 9, 2023 at 7:17
  • Developer orgs would be identified as non-sandbox as well, since sandboxname will be empty. Commented Sep 13, 2023 at 12:11
5

Another method worth mentioning is to change the name of the org in the sandbox after it is created and then you can query for the org name.

For instance, in production your org name ( Setup > Administration > Company Profile > Company Information > Organization Name ) is Acme Inc.

In the sandbox named DEV, if you change the Organization Name to Acme Inc. (DEV) you could then query for the org name and use it as you need within your code.

Organization org = [SELECT Name FROM Organization];

Update: It is also possible to get the org name from the UserInfo class eliminating the need for a query: UserInfo.getOrganizationName();

This name change has an additional benefit, the email messages which are sent out from the org when an unhandled script exception occurs include this org name value in the subject line. This makes it easy for an exception email to be quickly identified as having come from production or a sandbox without resorting to having to look up the OrgId.

Sandbox: Developer script exception from Acme Inc. (DEV) : HelperClassName : Attempt to de-reference a null object

2

Check this salesforce Idea link Determine Sandbox or production from apex

There is workaround for this by hard-coding the OrgId in the apex class

1
  • Hard-coding the id doesn't work like you'd expect, SFDC modifies your metadata and replaces instances of the production org id with the sandbox. I'd use this exact technique previously until I realized it kept changing my production org id constant. Commented Nov 7, 2014 at 19:02
2

In the solutions in search of a problem category, you could use the pod identifier from the OrgId to determine if you are dealing with a sandbox.

string podId = UserInfo.getOrganizationId().substring(3,4);
boolean isSandbox = 'TSRQPONMLKJZVWcefg'.contains(podId);
System.debug('IsSandbox: ' + isSandbox);

Caveat Confector: The big weakness here is that you will need to update the list of know sandbox pods as and when Salesforce brings new ones online (so it might be safer sticking with the other solutions).

2

This works for me.

String baseUrl = URL.getOrgDomainURL().toExternalForm();
String boxName = DomainParser.parse(baseUrl).getSandboxName();
Boolean isProduction = boxName == null;
System.debug(isProduction);
1

I personally always consider a new table/object containing configuration information. This allows you to build an N tier rollout environment without having to fully understand the vagaries of SF. If you are planning, or have, a DEV>TEST>SYSTEST>PRODUCTION environment then I think my suggestion has merit.

After all, the alternative is to create such a table, but in code. I would propose that the code should be the same in all environments and the configuration information be changed.

1

What I was looking for when I came to this question today was how to allow my LWC to know the difference. I came up with this easy check, hopefully can help others that may be looking for the LWC JavaScript solution:

get isProduction() {
    return !(window.location.hostname.includes('sandbox'));
}

Then wherever you need the check in the rest of your JS class, use this.isProduction.

1

Posting for reference if someone is looking to the same with Flows. $Organization.IsSandbox does not exist there to this day,

so the workaround by @gorav on his blog post is

  • Before your Decision "Add a Get Record" to your Flow, (name it "getOrganization") pinging the Organization Object
  • Then in your Decision have {!getOrganization.IsSandbox} Equals False
1

There's an easy way where you don't have to query by using the URL.getOrgDomainUrl()

Boolean isRunningInASandbox = String.valueOf(URL.getOrgDomainUrl()).contains('sandbox');

Works perfectly and neatly for me.

0

The accepted answer requires a DML statement. We can do without DML statements by using the code from Daniel Blackhall's answer like this:

Boolean isSandbox = URL.getSalesforceBaseUrl().getHost().left(2).equalsignorecase('cs');

or with My Domain enabled (where the cs part is not necessarily on the left)

Boolean isSandbox = URL.getSalesforceBaseUrl().getHost().containsignorecase('cs');
5
  • 3
    That approach doesn't work for any org which has MyDomain enabled.
    – Adrian Larson
    Commented Apr 21, 2018 at 15:44
  • @AdrianLarson Interesting. That appears not to be true in my tests. The domain will look something like 'subdomain.cs88.my.salesforce.com'. Is that not the case in your situation? Commented Apr 21, 2018 at 17:12
  • And .left(2) gets you 'cs'?
    – Adrian Larson
    Commented Apr 21, 2018 at 17:19
  • Good catch :-D I've added a .containsignorecase() variant. Thanks. Commented Apr 23, 2018 at 7:37
  • 1
    in my case it is 'CompanyName--SandBoxName.my.salesforce.com' - so this method doesn't work. Better to check if getHost() is not eq production host name
    – John
    Commented Jan 6, 2021 at 7:48
-3

For some reason this didn't work for me. This worked in its place

public static Boolean isSandbox() {
    
   Boolean org = ([SELECT IsSandbox FROM Organization LIMIT 1].IsSandbox);
    
        if(org){
            return true;
        } else{
            return false;
        }
 }

To use the code in another class or trigger:

trigger fancyname on sObjname (after insert, after update){

if(MiscFunctions.isSandbox()){...} else{...}

}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .