.ai/principles/distilled/graphql.md
required: false) to required (required: true) without deprecation.null: true) to non-nullable (null: false) without deprecation.deprecated: property with reason: and milestone: keys; DO NOT remove them directly.description: on deprecated items; append the reason via the reason: key, not by editing the description.Use \otherFieldName`` as the deprecation reason when a field is replaced by another.Deprecations issue template with ~GraphQL and ~deprecation labelsTypes::DeprecatedMutations and test them in Types::MutationType unit tests.mount_aliased_mutation to alias a mutation when renaming, to preserve the old name during the deprecation period.Deprecation entry to Gitlab::GlobalId::Deprecations::DEPRECATIONS instead of making a breaking change.experiment: { milestone: '...' }.experiment: property when the feature flag is removed to make the item public.@gl_introduced directive on fields for Self-Managed/Dedicated to strip future nodes from queries hitting older backend versions.@gl_introduced on arguments, fragments, or single future fields that are the only selection in a query or object.@gl_introduced as still requiring null-checks on the frontend.description: value ending with a period (.).The or A.{x} of the {y} phrasing for field descriptions where possible.Types::TimeType for all Time/DateTime fields and include the word timestamp in the description.Indicates the issue is confidential.).Values for sorting {x}.description:.copy_field_description(Types::SomeType, :field_name) to keep descriptions in sync between a type field and a mutation argument.see: property for external documentation references instead of embedding URLs in descriptions.Premium and Ultimate only.) for fields restricted to higher tiers.null: true) over non-nullable ones; reserve non-nullable for fields that are required, unlikely to become optional, and cheap to compute (for example, id).Types::GlobalIDType[Model] (not plain ID or database primary key integers) for all Global ID inputs and outputs.id-named fields that expose Global IDs manually using Gitlab::GlobalId.build or #to_global_id.GraphQL::Types::ID only for full paths; DO NOT use it for database IDs or IIDs.Types::TimeType for all Ruby Time and DateTime fields and arguments.markdown_field helper for all fields that return rendered Markdown.calls_gitaly: true; annotate resolvers that call Gitaly with calls_gitaly!.complexity: 0 for trivially cheap fields (for example, id, title); set higher complexity for expensive fields.extension(::Gitlab::Graphql::Limit::FieldCallCount, limit: N) and update the field description: when limiting field call count to prevent N+1 problems of last resort.CountableConnectionType when exact counts matter; use LimitedCountableConnectionType when the collection can be very large and exact counts are not critical.GraphQL::Types::JSON unless the data is truly unstructured; use typed objects or unions instead.latest_pipeline); use pagination arguments instead (for example, pipelines(last: 1)).Enum, graphql_name does not contain Enum, and all values are uppercase.each_key to keep them in sync.graphql_name as the first line of a mutation class.{Resource}{Action} or {Resource}{Action}{Attribute}; use Create, Update/Set/Add/Toggle, and Delete/Remove verbs.expose_permissions with a type inheriting BasePermissionType.loads: option in argument definitions; accept the Global ID and load the object manually with authorized_find!.required: :nullable when an argument must be provided but its value can be null.validates: { allow_null: false } for optional arguments where null is not a valid value.validates mutually_exclusive: or validates exactly_one_of: for mutually exclusive or exactly-one-of argument groups.{PROPERTY}_{DIRECTION} format.project_path, group_path, or namespace_path; use iid with a parent path for IID-identified objects; use Types::GlobalIDType[Model] for all other object identifiers.authorized_find! in resolvers to load and authorize objects; DO NOT raise errors for unauthorized resources — return null instead.#ready? for set-up or early-return logic; use validators for argument validation instead of #ready?.BaseResolver.single derived resolvers have more restrictive arguments than the collection resolver via a when_single block.LooksAhead concern and implement preloads / unconditional_includes to avoid N+1 queries via lookahead.before_connection_authorization to preload data for type authorization checks and avoid N+1s from permission checks.BatchModelLoader for ID-based record lookups; DO NOT implement custom ID batch loaders.batch.sync or Lazy.force in resolver code; use Lazy.with_value instead.GraphqlTriggers to trigger subscriptions; DO NOT call GitlabSchema.subscriptions.trigger directly in application code.#authorized? in subscription classes and call unauthorized! when authorization fails.errors as an empty array on success and populate it with user-relevant error messages on failure; raise raise_resource_not_available_error! for authorization/not-found errors.null: true.#reset if needed).Gitlab::Graphql::Errors types; DO NOT let StandardError propagate uncaught (it becomes Internal server error).for(data) call.authorize :ability on types, resolvers, or fields using DeclarativePolicy abilities.authorizes_object! when a resolver should authorize against the parent object.authorize: for scalar fields with different access levels or to avoid expensive per-object checks.skip_type_authorization on a field only when the resolver already authorizes the resolved objects and the permission checks are equivalent, to avoid redundant N+1 authorization calls.development.log, the performance bar, or request specs with QueryRecorder.BatchLoader::GraphQL for batching queries in resolvers; pass all needed data through for(data) and DO NOT close over instance state in batch blocks.#sync.QueryRecorder tests to avoid false positives from authentication queries.includes(); build at the class level to avoid Arel::Nodes::LeadingJoin errors.spec/requests/api/graphql as the primary test vehicle; DO NOT rely on resolver unit specs for behavior testing.authorize declarations.post_graphql / post_graphql_mutation helpers and GraphqlHelpers methods in integration specs.graphql_mutation, post_graphql_mutation, and graphql_mutation_response helpers for mutation specs.a_graphql_entity_for, graphql_data_at, and graphql_dig_at helpers to access and match result fields.empty_schema instead of manually constructing a schema in unit tests.get_graphql_query_as_string to test frontend .graphql query files.app/graphql/types in spec/requests/api/graphql.batch_sync or Gitlab::Graphql::Lazy.force only in tests when lazy values must be forced; prefer Schema.execute in request specs to avoid manual lifecycle management.For the full picture, see: