Semantic GraphQL schema design policy enforcement for CI pipelines
Semantic GraphQL schema design policy enforcement for CI pipelines. Goes beyond syntax linting to enforce design rules like "every mutation must return a result type" and "no String IDs."
GraphQL linters check syntax and formatting, but they don't enforce design policies — the conventions your team agrees on to keep schemas consistent, safe, and evolvable. This library fills that gap.
npm install graphql-schema-policies graphqlimport { buildSchema } from 'graphql';
import {
createPolicyEngine,
noStringIds,
mutationsHaveResultType,
listFieldsHavePagination,
deprecatedFieldsHaveReason,
noNullableIdFields,
} from 'graphql-schema-policies';
const schema = buildSchema(`
type Query {
users: [User]
}
type User {
id: String
name: String
}
`);
const engine = createPolicyEngine({
rules: [
noStringIds(),
mutationsHaveResultType(),
listFieldsHavePagination(),
deprecatedFieldsHaveReason(),
noNullableIdFields(),
],
});
const result = engine.check(schema);
console.log(result.passed); // false
console.log(result.errorCount); // 2
console.log(result.violations); // [{ rule: 'no-string-ids', ... }, ...]| Rule | Default Severity | Description |
|---|---|---|
noStringIds() |
error | Fields named id must use the ID scalar, not String |
mutationsHaveResultType() |
error | Mutation fields must return a named object type, not a scalar |
listFieldsHavePagination() |
warn | List fields should have first/after or limit/offset args |
deprecatedFieldsHaveReason() |
warn | @deprecated fields must include a non-empty reason |
noNullableIdFields() |
error | Fields named id must be non-nullable (ID!) |
All rules accept an optional { severity: 'error' | 'warn' } override:
noStringIds({ severity: 'warn' })import { isObjectType } from 'graphql';
import type { GraphQLSchema } from 'graphql';
import type { PolicyRule, Violation } from 'graphql-schema-policies';
function noFieldNamedData(): PolicyRule {
return {
name: 'no-field-named-data',
description: 'Fields should not be named "data"',
severity: 'warn',
check(schema: GraphQLSchema): Violation[] {
const violations: Violation[] = [];
const typeMap = schema.getTypeMap();
for (const [typeName, type] of Object.entries(typeMap)) {
if (typeName.startsWith('__') || !isObjectType(type)) continue;
const fields = type.getFields();
if (fields['data']) {
violations.push({
rule: 'no-field-named-data',
typeName,
fieldName: 'data',
message: `${typeName}.data — avoid generic field name "data"`,
severity: 'warn',
});
}
}
return violations;
},
};
}npx graphql-policies schema.graphqlExits with code 1 if any error-severity violations are found.
# GitHub Actions
- name: Check schema policies
run: npx graphql-policies schema.graphql# GitLab CI
schema-policies:
script:
- npx graphql-policies schema.graphqlCreates a new PolicyEngine instance.
opts.rules— Array ofPolicyRuleobjects to enforce
Runs all rules against the provided GraphQLSchema and returns a PolicyResult:
passed—trueif no error-severity violationsviolations— Array ofViolationobjectserrorCount— Number of error-severity violationswarnCount— Number of warn-severity violations
MIT