Skip to content



Raw SKILL.md · MIT · sha256:723aa16fbca041d94ae40db8eddcf3de1a4a0c1549f04ae6ca5e19be12aa8df9

Sanity GROQ conventions

Conventions for keeping GROQ readable and consistent once a project grows past its first few queries. The recurring theme: queries are composed from named fragments, named predictably, and project only what the page needs.

File organisation

  • Import defineQuery (and groq if needed) from next-sanity at the top of query files
  • Export queries as constants through defineQuery so tooling and typegen can find them
  • Organise queries by content type (blogs, pages, products) and group related queries together
  • Define common projection fragments at the top of the file, before the queries that use them

Naming conventions

  • camelCase for all query names
  • Prefix with an action verb followed by the content type: getAllBlogPosts, getPageBySlug
  • Suffix every query with Query: getAllBlogIndexTranslationsQuery
  • Prefix reusable fragments with an underscore: _richText, _buttons, _icon

Fragment reuse

Create a named fragment for every repeated projection and interpolate it:

const _buttons = /* groq */ `
  buttons[]{
    _key,
    label,
    variant,
    "href": url.href
  }
`;

export const getPageBySlugQuery = defineQuery(`
  *[_type == "page" && slug.current == $slug][0]{
    title,
    ${_buttons}
  }
`);

Keep fragments composable and focused on one concern each. A fragment that projects half the document defeats the purpose.

Images

When a query touches an image, do not expand it (image{...} with asset dereferencing) unless explicitly required. Pass the image reference to the frontend image builder instead; expanding assets in GROQ bloats every response that includes the fragment.

Parameters

  • Use $ parameters for every dynamic value: $slug, $locale, $id. Never interpolate user input into the query string.
  • Handle localisation with a consistent pattern shared across queries (a ${localeMatch} fragment beats five subtly different filters).
  • Use select() for conditional logic inside queries and coalesce() for defaults.
  • Add pagination parameters ([$start...$end]) to every list query before the list gets long, never after.

Response types

  • Export TypeScript types for query responses when typegen does not cover the case
  • Make the type names match the query: export type GetAllMainPageTranslationsQueryResponse = string[];

Query practices

  • Filter explicitly on type: _type == "blog" beats implicit shape matching
  • Project the fields the page needs; returning whole documents couples every consumer to the full schema
  • Sort with order() explicitly instead of relying on document order
  • Check defined(field) before filtering or sorting on optional fields
  • Use conditional projections for fields that only exist on some variants

Code style

  • Template literals for query strings, with nested structures indented for readability
  • Keep related query parts together and maintain consistent whitespace
  • Comment the intent of complex filters; the GROQ itself rarely explains why

About this skill

Maintained by Roboto Studio, a UK agency that has written GROQ for production Sanity projects since the early days of the platform. It distills the query conventions we hold every project to. If you would rather have it done for you: robotostudio.com/services/sanity.

Licensed MIT. Wow, I can't believe people are actually using these. Tell me if it worked: yo@robotostudio.com