167

I remember seeing somewhere that IDs are composed of a few pieces. I always have a hard time trying to find that information when I'm looking for it. What I mean by the above is that the various places in the ID represent different things - for example the first few characters represent what type of sObject it is.

1
  • 4
    Awesome question - especially the valuable data it uncovered by virtue of the replies below. ;)
    – AMM
    Dec 27, 2013 at 18:53

4 Answers 4

216

The Id Field Type is a base-62 encoded string.

Each character can be one of 62 possible values:

  • a lowercase letter (a-z) - 26 values
  • an uppercase letter (A-Z) - 26 values
  • a numeric digit (0-9) - 10 values

As there is a combination of lower and upper case letters the casing of the 15 character Id has significance. E.g. 50130000000014c is a different ID from 50130000000014C.

Within a 15 character Id the breakdown is:

  • First 3 characters - Key Prefix As per Jon's answer, the first 3 characters are the key prefix that identify the object type. There are a few exceptions to this where multiple objects all share the same key prefix! There are a number of fixed key prefixes that are common across all of Salesforce. Custom objects get a unique key prefix per Org. I'd need to confirm this, but I'm fairly certain that Custom objects in managed packages can have a different keyprefix in each installed org.
  • The 4th and 5th characters - Reserved. Currently used for the instance id (a.k.a. pod identifier) (As per comment from @ca_peterson). Starting with the 4th character and overflowing to the 5th if required. Indicates which pod/instance the record was created on. Note that data may be migrated to other pods over time. Updated based on a separate question that indicated the pod identifier is actually two characters rather than one as initially thought.
  • 6th and 7th characters - Reserved. Will be 0 until such time that Salesforce has a need for it. Source - Steven Tamm
  • Remaining 8 characters - basically a really big number. Like 62^8 big.

To this you can add an optional 3 character suffix that will make the Id unique case-insensitive. This is useful when working with programs that can't maintain the case of the ID (E.g. Excel VLookup).

Notes about the suffix:

  • this is not intended as a check sum to verify the other 15 characters haven't been corrupted.
  • you can't just lower/upper case the entire ID. While it helps other case insensitive applications handle the IDs Salesforce is still case sensitive and won't auto correct the casing based on the suffix. E.g. the casing on a KeyPrefix is important with 00t being OpportunityShare and 00T being Task.

The algorithm to convert from a 15 character Id to an 18 character Id is: (Source - I'm sure there used to be official documentation on how do this.)

  1. Divide the 15 char into 3 chunks of 5 chars each.

  2. For each character give that position a value of 1 if uppercase, 0 otherwise (lowercase or number).

  3. Combine the bits from each chunk into a 5 bit integer where the rightmost bit is the most significant bit. This will yield a number between 0 and 31 for each chunk.

  4. Construct an array that contains the sequence of capital letters A-Z and 0-5 (26 + 6 = 32 possible values).

  5. Use the integer from each chunk to choose a character from the array.

  6. Append the resulting 3 characters, in chunk order, to the end of the 15 char id.

In a formula there is the CASESAFEID function that will perform this algorithm.

You can apply this algorithm to some sample IDs to see how it doesn't really function as a checksum or checkdigit. For example, if you exclude the alpha characters, every ID between 001100000000001 and 001999999999999 will have the suffix AAA. Infact, you get the same suffix if you include any lowercase alpha characters as well. The suffix will only change in the presence of uppercase characters. It is basically encoding which of the 5 characters that each suffix character represents are uppercase.

Sample code to restore the casing from an 18 character ID is in Creating a link using an 18 character ID


If you are working with Data Exports you can also come across the special empty key with the 000 keyprefix.

One area I'm not sure of is the order in which Salesforce increments through the base 62 encoding. E.g. Does it go 0 to 9, then a to z, then A to Z? At this stage I think the sequence looks like '0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ' '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

17
  • 5
    The first character after the prefix is a unique number based on the pod it was created on (e.g. NA1, CS5). This doesn't correspond to it's name (CS 5 isn't 5), but instead the order of all pods which it was brought online. Dec 19, 2012 at 4:46
  • 2
    Rumor has it they're at the max number of pods for a single base-62 encoded digit, so this might spill over into a 2nd character soon. Dec 20, 2012 at 1:28
  • 3
    Fun fact: you can get a mapping of the pod identifier in the 4th char of the orgId to the pod's name from github.com/ryanbrainard/forceworkbench/blob/… Jul 20, 2013 at 0:22
  • 2
    WOW. I love this. I did not know about the Pod reference.
    – AMM
    Nov 23, 2016 at 20:03
  • 3
    @CharlesKoppelman You would need someone internal to Salesforce to compile a complete list. I've started a new question so the community can build up a more complete list salesforce.stackexchange.com/q/164554/102 Mar 15, 2017 at 19:43
37

The first 3 digits are a prefix that specifies the type of sObject, a big list can be found here:

http://www.fishofprey.com/2011/09/obscure-salesforce-object-key-prefixes.html

I believe the rest of the Id is reference to the record itself. I should also add that the ID's are 15 digits long but can be 18 digits long with the last 3 digits for error correction making the the Id case-insensitive.

So:

3 Digits (Object) / 12 Digits (Record) / (Optional) 3 Digits (Error Correction)

4
  • 2
    Good to note that you can also get the object prefix via describe (s.getDescribe().getKeyPrefix()))
    – joshbirk
    Sep 25, 2012 at 17:56
  • I think the 12 digits for record still can be devided into 2 or more groups of different meaning. I remeber once seeing a blog about it, but can't find it. Sep 25, 2012 at 19:04
  • 7
    Answer is slightly inaccurate. The last 3 characters are not for error checking, but for making the ID case insensitive. ID generation logic is described here in the documentation: salesforce.com/us/developer/docs/api/Content/… Sep 25, 2012 at 19:57
  • @sorenkrabbe You are correct, ill update my answer :)
    – Jon Hazan
    Sep 26, 2012 at 8:52
15

While it is true that the last 3 characters add case insensitivity, I believe the algorithm used to generate them is the check digit. Check digits originated in legacy data transmission to alleviate the introduction of errors in the data. So a bit of both I reckon.

2
3

Not detracting from anything above, and not being part of the SF community, here follows a PHP class for converting between type 18 and type 15 ids. Note that many people suggest merely cutting the last three characters off the type 18 id, in order to get the type 15. Since type 18 is 'case-safe' and type 15 is case-sensitive, such a stratagem not a good idea.

Although, strictly, the 18-character id is not case-safe, in that case-insensitive sorting will differ for 18-character due to the digits coming after the alphabet, and the encoding bit being set for upper case. To be correctly case-safe, the encoding characters should have been in ansi-order, and the encoding bit should have been reset for uppercase). Consider the following id15s..

  1. AAAAbAAAAbAAAAb

  2. aaAABaaAABaaAAB

  3. aaaAbaaaAbaaaAb

  4. aaaabaaaabaaaab

These will sort (case-sensitive) to 1,2,3,4 - because 'A' < 'a' and 'B' < 'b'.

However, their id18 equivalents,

  1. AAAAbAAAAbAAAAbPPP

  2. aaAABaaAABaaAAB222

  3. aaaAbaaaAbaaaAbIII

  4. aaaabaaaabaaaabAAA

sorts (using case-INsensitive, as they are 'case-safe') to 2,4,3,1 - because 2 < A < I < P

Anyway, that's only a side issue here.

<?php

class SalesForceID {
    private $id15;  //
    private $id18;  //
    private $valid  =false; //is it a valid sf id
    private $length =0; //is it a valid sf id
    private $errs   = [];

    public function __construct(string $idIn = null) {
        $this->valid = $this->validate($idIn);
        if($this->valid) {
            $this->length = strlen($idIn);
            if($this->length == 18) {
                $this->id18 = $idIn;
            } else {
                $this->id15 = $idIn;
            }
        }
    }

    // Helper function. Use this to default to 
    // The id that you wish to use.
    public function id() : ?string {
        return $this->id15();
    }

    public function id15() : ?string {
        if(is_null($this->id15) && $this->valid) {
            $this->id15 = $this->sf18to15($this->id18);
        }
        return $this->id15;
    }

    public function id18() : ?string {
        if(is_null($this->id18)  && $this->valid ) {
            $this->id15 = $this->sf15to18($this->id15);
        }
        return $this->id15;
    }

    private function validate(string $id) : bool {
        if(!is_null($id)) {
            if(preg_match('/^[a-zA-Z0-9]+$/',$id)) {
                $length = strlen($id);
                if($length !== 18 && $length !== 15) {
                    $this->errs[] = "Salesforce Key Id has an incorrect length";
                    return false;
                }
                if(($id === "000000000000000") || ($id === "000000000000000AAA")) {
                    $this->errs[] = "SalesForce Empty Key Id is not acceptable";
                    return false;
                }
            } else {
                $this->errs[] = "SalesForce Key Id is not valid";
                return false;
            }
        } else {
            $this->errs[] = "SalesForce Key is null";
            return false;
        }
        return true;
    }

    private function sf15to18($str): string {
        $lib = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
        $retval = $str;
        foreach (str_split($str, 5) as $segment) {
            $idx = 0; $base = 1;
            $chars = str_split($segment);
            foreach($chars as $char) {
                $idx += ctype_upper($char) ? $base : 0;
                $base <<=1;
            }
            $retval .= $lib[$idx];
        }
        return $retval;
    }

    private function sf18to15($str): string {
        $retval = "";
        $idx = str_split(substr($str, -3)); //This marks upper case.
        $val = str_split(substr($str, 0, -3),5);
        for($i=0; $i < 3; $i++) {
            $base = 1;
            $value = strpos("ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",$idx[$i]);
            $chars = str_split($val[$i]);
            foreach($chars as $char) {
                $retval .= ($value & $base) === 0 ? strtolower($char) : strtoupper($char);
                $base <<=1;
            }
        }
        return $retval;
    }

    public function isValid() :bool {
        return $this->valid;
    }

    public function errors() : array {
        return $this->errs;
    }
}

function doTest() {
    $salesForceId =
        [
            '0032400000QZbmt'=>'0032400000QZbmtAAD',
            '0032400000QZbnG'=>'0032400000QZbnGAAT',
            '0032400000QZbnZ'=>'0032400000QZbnZAAT',
            '0032400000eGqdZ'=>'0032400000eGqdZAAS'
        ];


   foreach($salesForceId as $key => $base ) {
        $x8 = new SalesForceID($key);
        $xx = $x8->id18();
        $tt = $xx === $base ? 'Y' : 'N';
            print("$key should $xx = $base $tt\n");
        }

    foreach($salesForceId as $key => $base ) {
        $x8 = new SalesForceID($key);
        $xx = $x8->id15();
        $tt = $xx === $key ? 'Y' : 'N';
        print("$base should $xx = $key $tt\n");
    }

}

doTest();
5
  • 2
    This is not correct. Both the 15 character and 18 character IDs are case-sensitive. The 18 character ID is case-safe, which means that two different Ids never differ from one another only in case due to the addition of a three-character checksum. Truncation is the correct way to convert from 18 to 15 character Ids, but that should really never be necessary.
    – David Reed
    Oct 31, 2018 at 13:10
  • Once an 18 character ID has been exported to a third party system, there's no guarantee that case will be preserved. As the final 3 characters encode the case, there is also no necessity for case sensitivity to be preserved. Yes, the =intent= was to allow excel to be able to use lookups. However, SalesForce is used in many other environments, and the preservation of case in an 18 character ID is certainly not guaranteed.
    – Konchog
    Oct 31, 2018 at 13:46
  • 1
    That is incorrect. You can easily demonstrate this by loading a record in Salesforce, viewing its 18-character id, and then attempting to load the Id with the case of one character changed. You will get an error because the id is still case sensitive. The final three characters do not encode the case, for Salesforce's purposes, and Salesforce will not "reconstruct" case from them; they differentiate between Ids that differ only in case.
    – David Reed
    Oct 31, 2018 at 13:48
  • David, you seem to be missing the point. Third party coding uses Salesforce IDs. When you change case, as you suggest, you are invalidating the ID for Salesforce, but the valid ID remains recoverable. This is the purpose of using a function for such stuff. The idea that 18character ids are always going to preserve case is terribly presumptive.
    – Konchog
    Oct 31, 2018 at 13:51
  • 3
    Regardless, this question is not actually about conversion in either direction, and hence your answer doesn't especially belong here. There are numerous questions where it would address what is asked, though. I'll try to link to some.
    – Adrian Larson
    Oct 31, 2018 at 15:52

You must log in to answer this question.

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