Content types
strapi-client generate writes the interfaces and the registry augmentation. The installed binary is strapi-client, so npx @fbritoferreira/strapi generate and strapi-client generate run the same command.
It takes exactly one source, or a config file for all of them: --dir or --url for content types (this page), --openapi for route types, or --graphql for GraphQL schema types. Each writes its own file. They are meant to be used side by side.
# From a Strapi project checked out next to your app
npx @fbritoferreira/strapi generate --dir ../my-strapi -o src/strapi-types.ts
# From a running instance (admin user credentials, not an API token)
STRAPI_ADMIN_EMAIL=me@example.com STRAPI_ADMIN_PASSWORD=... \
npx @fbritoferreira/strapi generate --url https://cms.example.com -o src/strapi-types.ts
# In CI: fail when the committed file is stale
npx @fbritoferreira/strapi generate --dir ../my-strapi -o src/strapi-types.ts --checkImport the generated file once (import "./strapi-types") and strapi.collection("articles") returns CollectionClient<Article>.
The first line of the generated file records its source, with local paths relative to the output file's directory:
// Generated by @fbritoferreira/strapi generate from dir ../../my-strapi. Do not edit.There is no timestamp, so regenerating an unchanged schema leaves the file as it was, and the same checkout produces the same file on every machine. --check ignores that line.
What is generated
- One
interfaceperapi::content type, extendingStrapiDocument. Localized types get a requiredlocale. - One
interfaceper component, withid: number. - Relations, media, components and dynamic zones are optional (they appear only when populated).
mediaisStrapiMedia | nullorStrapiMedia[]. Relations toplugin::users-permissions.userareStrapiUser. - Dynamic zones are
Array<(BlocksHero & { __component: "blocks.hero" }) | ...>. enumerationbecomes a union of string literals.jsonisunknown.bigintegerisstring.blocksisStrapiBlock[].- A
__relationsmarker listing the fields written by reference: relations and media, not components or dynamic zones. - A
__populatablemarker listing the fieldspopulateaccepts. It exists only in the type system. It is what letsfields,sortandpopulatebe told apart, and what lets results be narrowed. privateattributes are skipped. Plugin content types are skipped unless--include-pluginsis passed.--include-pluginsregisters plugin content types under theirpluralNameeven when the plugin does not expose a matching/api/<pluralName>route.
The output augments StrapiContentTypes and StrapiSingleTypes. Once that file is imported, a uid it does not declare is a compile error, which catches strapi.collection("aritcles"). An explicit type argument still overrides the registry and accepts any uid.
Admin credentials
--url calls POST /admin/login and the Content-Type Builder routes. That needs an admin user with plugin::content-type-builder.read. Strapi does not accept API tokens on admin routes. --email and --password override STRAPI_ADMIN_EMAIL and STRAPI_ADMIN_PASSWORD.
Add --watch to regenerate as schemas change. See Watch mode.