GitHub Org Tools uses a GraphQL-first approach for all data reads. Instead of calling the GitHub REST API directly, you send GraphQL queries through a server-side proxy that validates, authenticates, and executes them using the user’s OAuth token.
This doc covers usage from the frontend perspective. For the route handler internals, see GraphQL Route Internals.
Endpoint
POST /api/github/graphqlRequires an authenticated session. The server validates the session, then uses your OAuth token to call GitHub.
Request contract
{
"query": "query ViewerProfile { viewer { login } }",
"variables": {},
"operationName": "ViewerProfile"
}| Field | Type | Required | Description |
|---|---|---|---|
query | string | yes | The GraphQL query document |
operationName | string | yes | Must match the server allowlist |
variables | object | no | Query variables |
Response contract
Success (200):
{ "data": { ... } }Error responses:
| Status | Reason |
|---|---|
| 400 | Missing or invalid query |
| 401 | No session or expired session |
| 403 | Operation not in allowlist |
| 500 | GitHub GraphQL execution failure |
Operation allowlist
Only explicitly allowlisted operations can be executed. This is intentional — it prevents arbitrary GraphQL queries and makes backend behavior predictable.
Current operations
| Operation | Description | Variables |
|---|---|---|
ViewerProfile | Authenticated user’s profile and org memberships | None |
OrganizationSummary | Org metadata, member/team/repo counts | login: String! |
OrganizationMembers | Paginated list of org members | login: String!, after: String |
OrganizationTeams | Paginated list of org teams | login: String!, after: String |
TeamMembers | Members of a specific team | login: String!, teamSlug: String!, after: String |
RepositoryContributors | Contributors to a specific repository | owner: String!, name: String!, after: String |
OrgRepositories | Paginated repos for an org | login: String! |
RepoScoringData | Commits, PRs, issues for scoring | owner: String!, name: String! |
OrgTeamsData | Teams + members for an org | login: String! |
OrgScoringData | Org-level scoring data | login: String! |
OrganizationRepositories | Repos with metadata | login: String! |
Example queries
ViewerProfile
query ViewerProfile {
viewer {
login
avatarUrl
organizations(first: 25) {
nodes {
login
viewerCanAdminister
}
}
}
}OrganizationSummary
query OrganizationSummary($login: String!) {
organization(login: $login) {
login
membersWithRole {
totalCount
}
teams {
totalCount
}
repositories {
totalCount
}
}
}OrganizationMembers (paginated)
query OrganizationMembers($login: String!, $after: String) {
organization(login: $login) {
membersWithRole(first: 50, after: $after) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
node {
login
name
avatarUrl
}
role
}
}
}
}RepositoryContributors
query RepositoryContributors($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
defaultBranchRef {
target {
... on Commit {
history(first: 100, after: $after) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
node {
author {
user {
login
avatarUrl
}
}
committedDate
}
}
}
}
}
}
}
}Backend implementation
Allowlist configuration
The allowlist lives in lib/github/graphqlProxy.ts. To add a new operation:
// Extend this array
export const USER_GITHUB_GRAPHQL_OPERATIONS = [
'ViewerProfile',
'OrganizationSummary',
'SearchUsers',
// Add yours here
] as const;Adding a new allowed operation
- Add the operation name to
USER_GITHUB_GRAPHQL_OPERATIONSingraphqlProxy.ts - Optionally add a fallback query in the route’s
OPERATION_QUERY_FALLBACKS - Create a frontend query hook in
hooks/queries/using TanStack Query +fetch - Use the hook in the relevant component
Query hook pattern
// hooks/queries/useOrganizationSummary.ts
import { useQuery } from '@tanstack/react-query';
export function useOrganizationSummary(login: string) {
return useQuery({
queryKey: ['organization-summary', login],
queryFn: async () => {
const res = await fetch('/api/github/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query OrganizationSummary($login: String!) {
organization(login: $login) { login membersWithRole { totalCount } }
}`,
variables: { login },
operationName: 'OrganizationSummary',
}),
});
const json = await res.json();
if (!res.ok) throw new Error(json.error ?? 'Failed to fetch');
return json.data;
},
enabled: !!login,
});
}Best practices
- Keep operations focused — one operation per data concern
- Use query variables — never interpolate user input into query strings
- Keep operation names stable — React Query caching keys depend on them
- Always use the allowlist — never execute client-supplied queries directly
- Auth at the boundary — session checks happen in the route handler, not in individual queries
When to use REST
Some operations don’t have a GraphQL equivalent in GitHub’s API:
POST /api/auth/*— OAuth token exchange and session managementPOST /api/install/*— GitHub App installation flowPOST /orgs/{org}/invitations— Inviting users (no GraphQL mutation exists)
For everything else, prefer the GraphQL endpoint.
Related
- GraphQL Route Internals — how the proxy works, query fallbacks
- API Overview — all endpoints
- Auth Routes — session authentication