Encode and decode GitHub's GraphQL global node IDs — all 354 of them, including the ones that carry more than a database ID.
decodeNodeId('I_kwDOAJy2Ks4A47jA');
// { typeName: 'Issue', scope: 'ri', fields: { repo_id: 10270250, issue_id: 14923968 }, id: 14923968, ... }pnpm add parse-github-gql-global-idA modern ("next") node ID is a type prefix, a _, and a base64url-encoded
MessagePack array:
I_kwDOAJy2Ks4A47jA -> "I" + "_" + msgpack([0, 10270250, 14923968])
The prefix names the GraphQL type. The array's first element is a template index, and the rest are that template's values. Each type declares its templates, so the payload is self-describing once you know the prefix:
| Type | Prefix | Template |
|---|---|---|
User |
U |
[id] |
Repository |
R |
[id] |
Issue |
I |
[repo_id, issue_id] |
Commit |
C |
[repo_id, oid] |
MentionedEvent |
MEE |
[repo_id, issue_id, id] |
Reaction |
REA |
7 templates, one per reactable type |
Values are usually database IDs, but they can also be strings — a commit oid,
a qualified_name like refs/heads/main, a topic name, a license key.
Older IDs (MDQ6VXNlcjE=) are base64 of 0<len>:<Type><id>; those are handled
too.
import {
decodeNodeId,
encodeNodeId,
encodeNodeIdWithFields,
extractDatabaseId,
extractRepositoryId,
parseNodeId,
} from 'parse-github-gql-global-id';
// Decode
decodeNodeId('C_kwDOACN7MtoAKDUwMmQ0NTc3NGFmMDlmMWM2ODFjNzU0YzRiN2NkZmI1ZDdmNzJmZDk');
// {
// format: 'next',
// prefix: 'C_',
// typeName: 'Commit',
// templateIndex: 0,
// scope: 'rc',
// fields: { repo_id: 2325298, oid: '502d45774af09f1c681c754c4b7cdfb5d7f72fd9' },
// values: [2325298, '502d45774af09f1c681c754c4b7cdfb5d7f72fd9'],
// id: '502d45774af09f1c681c754c4b7cdfb5d7f72fd9',
// }
// Encode by position (uses the type's first template)
encodeNodeId('User', 1); // "U_kgAB"
encodeNodeId('Issue', 10270250, 14923968); // "I_kwDOAJy2Ks4A47jA"
// Encode by field name — order-independent, and picks the matching template
encodeNodeIdWithFields('Commit', { repo_id: 2325298, oid: '502d4577…' });
encodeNodeIdWithFields('App', { org_id: 9919, app_id: 15368 }); // template `oa`
// Shortcuts
extractDatabaseId('I_kwDOAJy2Ks4A47jA'); // 14923968
extractRepositoryId('I_kwDOAJy2Ks4A47jA'); // 10270250
// Either format
parseNodeId('MDQ6VXNlcjE='); // { format: 'legacy', typeName: 'User', id: '1' }When a type has several templates with the same field names — ProjectV2 has
one for org owners and one for user owners — pass the scope:
encodeNodeIdWithFields('ProjectV2', { owner_id: 1, id: 2 }, { scope: 'upvt' });| Export | Description |
|---|---|
decodeNodeId(id) |
Decode a next-format ID into type, template, and named fields |
decodeLegacyNodeId(id) |
Decode a legacy (Relay) ID |
parseNodeId(id) |
Decode either format |
encodeNodeId(type, ...values) |
Encode from positional values |
encodeNodeIdWithFields(type, fields, options?) |
Encode from named fields |
encodeLegacyNodeId(type, id) |
Encode a legacy ID |
encodeUserNodeId / encodeBotNodeId / encodeOrganizationNodeId / encodeRepoNodeId |
Top-level owner shortcuts |
extractDatabaseId(id) / extractId(id) / extractRepositoryId(id) |
Pull single values out |
isNextNodeId(id) / isLegacyNodeId(id) |
Format detection |
getTypeMetadata(typeOrPrefix) |
Prefix and templates for a type |
GLOBAL_ID_METADATA / TYPE_NAME_BY_PREFIX |
The full registry |
Type names and prefixes are both accepted wherever a type is expected, and
GlobalIdTypeName is a union of all 354 names, so editors autocomplete them.
decodeNodeIdreturnsvaluesinstead ofids, plustypeName,scope, andfields.prefixis unchanged.encodeNodeIdnow knows each type's shape and throws aNodeIdErroron the wrong number of values.encodeNodeId('Issue', issueId)was producing an invalid ID; it needsencodeNodeId('Issue', repoId, issueId).
- IDs larger than
Number.MAX_SAFE_INTEGERdecode to abigint; smaller ones staynumber. Encoding accepts either. - The GraphQL API only returns next-format IDs when you send
X-Github-Next-Global-ID: 1; the REST API returns them fornode_idunder the same header. pnpm test-onlinechecks the library against the live API — crafting IDs for known users, repositories, and organizations, and round-tripping every ID returned by a broad sweep of a few large repositories.