A Drush-powered TypeScript and GraphQL scaffold generator for Next.js frontends driven by graphql_compose.
When you add or remove a content type or field in Drupal, this module tells you what needs updating in your Next.js project and generates the boilerplate so you can focus on the actual component logic.
Built by Jeremy Michael Cerda (jmcerda@wilkesliberty.com). Maintained by Wilkes & Liberty, LLC.
-
drush gqcc:inspect— list every node (and paragraph) bundle and its "extra" fields (fields not already in your shared base type). -
drush gqcc:generate— write four scaffold artefacts:types.generated.d.ts— TypeScript type additions fortypes/index.d.tsfragments.generated.ts— GraphQL inline fragments forlib/queries/node-by-path.tsnode-renderer-cases.generated.tsx— switch-case stubs forNodeRenderer.tsxcomponents/{Name}.generated.tsx— one bare React component stub per bundle
Paragraph bundles get the same four artefact types under
paragraphs/:paragraphs/types.generated.d.ts— types joining theDrupalParagraphunionparagraphs/fragments.generated.ts— thePARAGRAPH_FRAGMENTSblockparagraphs/paragraph-renderer-cases.generated.tsx— switch-case stubs forParagraphRenderer.tsxparagraphs/components/{Name}Paragraph.generated.tsx— one component stub per bundle
-
drush gqcc:diff— compare the current schema against the last generation snapshot. -
drush gqcc:validate— verify scaffold files on disk are in sync with the live schema (useful in CI / pre-commit). -
Schema-change hooks — logs a Drupal notice at
/admin/reports/dblogwhenever a node or paragraph bundle / field is created or deleted, with the exactdrush gqcc:generatecommand to run. -
Pluggable field-type mappers — other modules can register their own Drupal-type → TypeScript-type mappings via
#[FieldTypeMapper]attributes.
- Drupal 10.6, or 11.3 and up
graphql_compose2.1 or higher- Drush 12 or 13
- PHP 8.1+
This module's entire user surface is Drush commands. The drush/drush package
is in require (not suggest) because without it the module does nothing
useful. If you cannot ship Drush in your project's require block, do not
install this module.
composer require drupal/graphql_compose_codegen
drush en graphql_compose_codegenConfigure via drush config:set or by editing
graphql_compose_codegen.settings.yml in your config sync directory:
Or visit Configuration → Development → GraphQL Compose Codegen
(/admin/config/development/graphql-compose-codegen) to edit the same
settings via the admin UI. Both methods write to the same
graphql_compose_codegen.settings config object.
# config/sync/graphql_compose_codegen.settings.yml
base_type_fields:
- title
- path
- body
- field_summary
# ... add any project-specific common fields
# (field_hero_image, field_primary_cta, field_industries, …)
base_ts_type: NodeCommonFields
output_dir: '/path/to/nextjs' # PathGuard-legal absolute path (no '..' segments)| Key | Default | Description |
|---|---|---|
base_type_fields |
[title, path, body] |
Fields in your shared base TypeScript type. Excluded from per-bundle output. Fresh Article/Page sites ship only these plus NodeCommonFields. |
base_ts_type |
NodeCommonFields |
Name of the shared base TS type. |
output_dir |
(empty) | Default output directory. Can be overridden with --output-dir. |
drush gqcc:inspect
drush gqcc:inspect --bundles=platform,service# Print all scaffold artefacts to stdout (review before writing):
drush gqcc:generate
# Write scaffold to /path/to/nextjs/generated/:
drush gqcc:generate --output-dir=/path/to/nextjs
# Scaffold only new bundles:
drush gqcc:generate --bundles=newsletter,event --output-dir=/path/to/nextjs
# Regenerate even if scaffold files already exist:
drush gqcc:generate --output-dir=/path/to/nextjs --overwrite
# Skip fields that are being handled elsewhere:
drush gqcc:generate --skip-fields=field_components,field_paragraphs
# Preview without writing:
drush gqcc:generate --output-dir=/path/to/nextjs --dry-runAfter running drush gqcc:generate --output-dir=/path/to/nextjs, the
generated files land in /path/to/nextjs/generated/. Integrate them
manually:
types.generated.d.ts— copy each newexport type Drupal*block intotypes/index.d.ts, and add the new type name to theDrupalNodeunion.fragments.generated.ts— copy each... on NodeFoo { }block into theNODE_BY_PATH_QUERYtemplate literal inlib/queries/node-by-path.ts.node-renderer-cases.generated.tsx— add the import line and switch case for each bundle intocomponents/drupal/NodeRenderer.tsx.components/{Name}.generated.tsx— rename to{Name}.tsx, move tocomponents/drupal/, and implement the actual component layout.paragraphs/*— same four steps for paragraph bundles: types join theDrupalParagraphunion, fragments form thePARAGRAPH_FRAGMENTStemplate literal, renderer cases go intocomponents/drupal/paragraphs/ParagraphRenderer.tsx, and component stubs move tocomponents/drupal/paragraphs/.
The scaffold files in generated/ are intentionally suffixed .generated
and not referenced anywhere. Delete them once you have integrated the code
you need.
- Only paragraph bundles enabled in graphql_compose (
enabled+query_load_enabledinentity_config) are inspected and generated, and only their graphql_compose-enabled fields. Sites without any graphql_compose config fall back to listing everything. - Paragraph references between paragraphs (
entity_reference_revisions) emit nested inline fragments for the allowed target bundles, one level deep. Bundles that only appear nested (e.g. FAQ items inside an FAQ group) get no top-level fragment or renderer case of their own. - When two bundles select the same field name with incompatible shapes
(required vs optional sub-fields), GraphQL refuses to merge the shared
response key across the union, so the generator aliases the later bundle's
field — e.g.
tabItems: items— and names the TypeScript property after the alias.
# Exits non-zero if scaffold files on disk are out of sync with the live schema.
drush gqcc:validate --output-dir=/path/to/nextjsWhen a content editor or developer creates a new node or paragraph bundle, or
adds/removes a field via the Drupal admin UI, the module logs a structured
notice at /admin/reports/dblog with the exact command to run.
| Module | What it does | How graphql_compose_codegen differs |
|---|---|---|
| typescript_generator | TS types for Drupal entity internals (FieldItemList<IntegerItem> style). |
This module emits the client-facing shape that graphql_compose actually returns over the wire. |
| nextgen | Next.js component/page scaffolding from JSON:API via Drush Code Generator. | This module is graphql_compose-aware (not JSON:API) and emits TypeScript types + fragments alongside components. |
| graphql_compose_fragments | GraphQL fragments exposed inside the schema's _info query (runtime, server-side). |
This module emits fragments to disk for direct merge into hand-written queries, plus TypeScript types and React stubs. |
| graphql_export | Exports raw .graphqls / introspection JSON to disk for npm graphql-codegen. |
Complementary, not a duplicate. This module is opinionated for the graphql_compose + Next.js stack and emits ready-to-merge code, not raw schema. |
This module is intentionally narrow: graphql_compose + Next.js + React. If you're on a different stack, the modules above may serve you better.
graphql_compose_codegen/
├── graphql_compose_codegen.module # LegacyHook bridges: bundle_create/delete, field_config_*, help
├── graphql_compose_codegen.install # hook_requirements (D10) + hook_uninstall
├── graphql_compose_codegen.services.yml # service + plugin manager registrations
├── graphql_compose_codegen.routing.yml # settings form route
├── graphql_compose_codegen.links.menu.yml # menu entry under Config → Development
├── graphql_compose_codegen.permissions.yml
├── graphql_compose_codegen.api.php # hook documentation
├── config/
│ ├── install/graphql_compose_codegen.settings.yml
│ └── schema/graphql_compose_codegen.schema.yml
├── modules/
│ └── graphql_compose_codegen_mcp/ # optional governed Tool API schema tools
└── src/
├── Attribute/FieldTypeMapper.php # plugin attribute
├── Drush/Commands/CodegenCommands.php # Drush 12+ attribute commands
├── Form/SettingsForm.php
├── Hook/GraphqlComposeCodegenHooks.php # attribute hooks + runtime requirements
├── Plugin/FieldTypeMapper/ # default field-type mapper plugins
├── PluginManager/FieldTypeMapperManager.php
└── Service/
├── SchemaInspector.php
├── TypeScriptGenerator.php
├── SchemaPreview.php
├── ArtefactSnapshot.php
└── PathGuard.php
The optional Codegen MCP submodule
provides schema inspection, snapshot comparison, and scaffold previews through
Tool API (graphql_compose_codegen_inspect, graphql_compose_codegen_diff,
graphql_compose_codegen_preview). MCP Sentinel governs access with
mcp_config_read. Previews never write files or update the saved baseline; the
base module has no new runtime dependencies and remains usable when the
integration is disabled.
Other modules can register custom mappers via the attribute-based plugin
system. See graphql_compose_codegen.api.php for hook_graphql_compose_codegen_pre_generate
/ hook_graphql_compose_codegen_post_generate documentation and the
#[FieldTypeMapper] attribute.
These were considered for v1.0 and deferred:
- Runtime validation output (Zod / Valibot / Yup
.schema.tsfiles) gqcc:watchdaemon mode- Storybook / Vitest stub generation per bundle
- Vue / Svelte / Astro / Solid component variants (would require a template plugin system)
- Schema.org Blueprints integration
Issues and merge requests at drupal.org/project/graphql_compose_codegen.
GPL-2.0-or-later, as per Drupal community standards.