Webiny-js webiny-project-structure
git clone https://github.com/webiny/webiny-js
T=$(mktemp -d) && git clone --depth=1 https://github.com/webiny/webiny-js "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/user-skills/project-structure" ~/.claude/skills/webiny-webiny-js-webiny-project-structure && rm -rf "$T"
skills/user-skills/project-structure/SKILL.mdWebiny Project Structure
TL;DR
A Webiny project has a flat structure centered around
webiny.config.tsx -- the single configuration file where all extensions are registered. Custom code lives in the extensions/ folder. Extensions are registered as React components (<Api.Extension>, <Admin.Extension>, <Infra.*>, <Cli.Command>) and can be conditionally loaded per environment.
Project Layout
my-webiny-project/ ├── extensions/ # All custom code -- API, Admin, Infra, CLI extensions │ └── README.md ├── public/ # Static assets for the Admin app │ ├── favicon.ico │ ├── global.css │ ├── index.html │ └── robots.txt ├── eslint.config.js # ESLint configuration ├── package.json # Single package.json for the whole project ├── tsconfig.json # Single TypeScript config ├── webiny.config.tsx # Main configuration -- all extensions registered here ├── webiny-env.d.ts # TypeScript environment types └── yarn.lock
Key points:
- Single
-- no monorepo, no workspaces needed.package.json - Single
-- straightforward TypeScript setup.tsconfig.json
-- the entry point for everything. All extensions, infrastructure options, and project settings are declared here.webiny.config.tsx
-- where all your custom code lives. Organize with subfolders as needed (e.g.,extensions/
,extensions/contactSubmission/
).extensions/AdminBranding/
The webiny.config.tsx
File
webiny.config.tsxThis file exports a single React component called
Extensions. It uses JSX to declaratively register all configuration:
// webiny.config.tsx import React from "react"; import { Admin, Api, Cli, Infra, Project, Security } from "webiny/extensions"; import { Cognito } from "@webiny/cognito"; export const Extensions = () => { return ( <> {/* Infrastructure configuration */} <Infra.Aws.DefaultRegion name={"us-east-1"} /> <Infra.OpenSearch enabled={true} /> <Infra.Aws.Tags tags={{ OWNER: "me", PROJECT: "my-project" }} /> {/* Identity provider */} <Cognito /> {/* API extensions (backend) */} <Api.Extension src={"/extensions/ProductCategoryModel.ts"} /> <Api.Extension src={"/extensions/ProductModel.ts"} /> <Api.Extension src={"/extensions/contactSubmission/ContactSubmissionHook.ts"} /> {/* Security hooks */} <Api.Extension src={"/extensions/MyApiKey.ts"} /> <Security.ApiKey.AfterUpdate src={"/extensions/MyApiKeyAfterUpdate.ts"} /> {/* Admin extensions (frontend) */} <Admin.Extension src={"/extensions/AdminTheme/AdminTheme.tsx"} /> <Admin.Extension src={"/extensions/AdminTitleLogo/AdminTitleLogo.tsx"} /> <Admin.Extension src={"/extensions/contactSubmission/EmailEntryListColumn.tsx"} /> {/* Infrastructure / Pulumi extensions */} <Infra.Core.Pulumi src={"/extensions/MyCorePulumiHandler.ts"} /> {/* CLI extensions */} <Cli.Command src={"/extensions/MyCustomCommand.ts"} /> {/* Project settings */} <Project.Telemetry enabled={false} /> </> ); };
Extension Types
| JSX Element | What It Does | File Type |
|---|---|---|
| Registers a backend extension (GraphQL schemas, content models, lifecycle hooks) | |
| Registers a frontend Admin UI extension (themes, branding, custom columns, custom forms) | |
| Registers a Pulumi infrastructure handler | |
| Registers a custom CLI command | |
| Registers a security lifecycle hook | |
YOU MUST include the full file path with the
or .ts
extension in every .tsx
prop. For example, use src
src={"/extensions/MyModel.ts"}, NOT src={"/extensions/MyModel"}. Omitting the file extension will cause a build failure.
YOU MUST use
for the export default
call when the file is targeted directly by an Extension createImplementation()
src prop. Using a named export (export const Foo = SomeFactory.createImplementation(...)) will cause a build failure. Named exports are only valid inside files registered via createFeature.
Infrastructure Components
Declarative components for configuring AWS infrastructure:
| Component | Purpose |
|---|---|
| Set the AWS region |
| Apply tags to all AWS resources |
| Enable/disable OpenSearch |
| Enable/disable VPC deployment |
| Prefix all Pulumi resource names |
| Define which envs use production infra |
| Enable/disable telemetry |
Environment-Conditional Configuration
Use
<Infra.Env.Is> to load extensions or config only in specific environments:
<Infra.Env.Is env="prod"> <Infra.Aws.Tags tags={{ ENV: "production" }} /> <Infra.OpenSearch enabled={true} /> </Infra.Env.Is> <Infra.Env.Is env={["dev", "staging"]}> <Infra.Aws.Tags tags={{ ENV: "non-production" }} /> <Infra.OpenSearch enabled={false} /> </Infra.Env.Is>
Build Parameters
Build parameters pass values from config to extensions at build time. They are accessed in backend code via the
BuildParams DI service (see dependency-injection skill).
Rule:
and <Api.BuildParam>
MUST live inside the extension's <Admin.BuildParam>
, NOT directly in Extension.tsx
. Required parameters are exposed as React props on the extension component. The consumer in webiny.config.tsx
webiny.config.tsx decides where the value comes from (env var, hardcoded, config, etc.).
// extensions/myExtension/Extension.tsx import React from "react"; import { Api } from "webiny/extensions"; interface MyExtensionProps { apiKey: string; } export const MyExtension = ({ apiKey }: MyExtensionProps) => { return ( <> <Api.BuildParam paramName="MY_API_KEY" value={apiKey} /> <Api.Extension src={"@/extensions/myExtension/features/myService/feature.ts"} /> </> ); };
// webiny.config.tsx — consumer decides where the value comes from <MyExtension apiKey={process.env.MY_API_KEY || ""} />
This keeps extensions self-contained — all required configuration is declared via typed props in one place.
Installing Pre-Built Extensions
Webiny provides official extensions you can install with:
yarn webiny extension <extension-name>
This downloads the extension code into
extensions/, updates webiny.config.tsx to register it, and gives you full access to modify the code.
Related Skills
-- How extensions use DI to access serviceswebiny-dependency-injection
-- How to deploy and develop locallywebiny-local-development
-- Full-stack extension skeleton, entry points, and shared domain layerwebiny-full-stack-architect