This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Strapi Webtools is a plugin for Strapi CMS v5 that provides URL management, routing, and an extensible addon system. It's a monorepo managed with Yarn workspaces and Turborepo, containing:
- packages/core: Main plugin (
strapi-plugin-webtools) - packages/cli: Installation CLI tool (
webtools-cli) - packages/addons/sitemap: Sitemap addon example
- packages/docs: Documentation site
- playground: Development Strapi instance for testing
# Install root dependencies
yarn install
# Install playground dependencies (runs automatically via postinstall)
yarn playground:installDevelopment requires running two terminal sessions:
Terminal 1 - Build the plugin in watch mode:
yarn developThis runs TypeScript compilation in watch mode across all packages and uses yalc to link them.
Terminal 2 - Run the playground Strapi instance:
yarn playground:developThis starts Strapi at http://localhost:1337 with the plugin pre-installed.
Changes to the plugin code will rebuild automatically (Terminal 1) and hot-reload in the playground (Terminal 2).
yarn buildBuilds all packages using Turborepo's caching and dependency graph.
# Run all unit tests
yarn test:unit
# Run unit tests in watch mode (add to jest command in package.json)
ENV_PATH=./playground/.env jest --watchUnit tests are located in __tests__ directories:
packages/core/server/middlewares/__tests__/packages/core/server/hooks/__tests__/packages/core/server/controllers/__tests__/packages/addons/sitemap/server/utils/__tests__/
Uses Jest with ts-jest preset. Test files: *.test.ts or *.test.js
yarn test:integrationRuns healthcheck integration test against the playground instance.
yarn test:e2eOpens Cypress test runner. E2E tests have .cy.ts or .cy.tsx extensions.
# Check all packages
yarn eslint
# Auto-fix issues
yarn eslint:fix
# Type checking (no emit)
yarn tscheckUses @uncinc/eslint-config with special overrides for Cypress and Jest files.
The plugin follows Strapi 5's plugin architecture with two entry points:
Server-side (packages/core/server/index.ts):
register(): Registers services, controllers, routes, content typesbootstrap(): Registers document middlewares and hookscontentTypes: Definesurl-aliasandurl-patterninternal content typesroutes: Admin API and public Content API endpointsservices: Business logic (url-alias, url-pattern, info)middlewares: Document lifecycle hooks for automatic URL generationcontrollers: Request handlers for API endpoints
Admin-side (packages/core/admin/index.ts):
register(): Registers the plugin with Strapi Adminbootstrap(): Injects UI into Content Manager and Content-Type Builderpermissions: RBAC permission definitions
URL Pattern (plugin::webtools.url-pattern):
- Template that defines how URLs are generated for a content type
- Uses bracket syntax:
[fieldName],[relation.field],[pluralName],[documentId] - Example:
/blog/[category.slug]/[title]→/blog/news/hello-world - Stored in database, managed via Admin UI
URL Alias (plugin::webtools.url-alias):
- The actual generated URL path for an entry
- Localized (supports i18n)
- Tracks whether it was generated or manually set (
generatedfield) - Automatically created/updated by middlewares
Three middlewares hook into the Strapi 5 document lifecycle:
-
generate-url-alias.ts: Runs on create/update/clone
- Fetches URL patterns for the content type
- Resolves pattern templates using entry data
- Creates/updates URL alias records
- Respects manual URLs (
generated: false)
-
prevent-duplicate-urls.ts: Ensures URL uniqueness
- Appends numeric suffixes when conflicts exist (-1, -2, etc.)
-
delete-url-alias.ts: Cleans up on entry deletion
These run BEFORE/AFTER document operations using Strapi's document middleware API (not legacy entity service).
Content types opt into Webtools via pluginOptions:
{
"pluginOptions": {
"webtools": { "enabled": true }
}
}When enabled:
- A
url_aliasrelation field is injected at bootstrap - Middlewares activate for that content type
- Admin UI shows the Webtools side panel in the content editor
Addons are Strapi plugins with a special flag in their package.json:
{
"strapi": {
"webtoolsAddon": true
}
}Discovery: Core scans enabledPlugins at runtime for this flag
Integration: Addons inject components via named zones:
webtoolsRouter: Adds routes to main navigationwebtoolsSidePanel: Adds components to content editor sidebar
Implementation: Addons call:
app.getPlugin('webtools').injectComponent(zone, type, { Component, ... })Example: The Sitemap addon extends enabled content types with a sitemap_exclude field and adds UI to manage sitemaps.
Key services accessible via getPluginService():
url-alias:
findByPath(path, locale?): Find entries by URL pathfindRelatedEntity(path, locale?): Resolve URL to entitymakeUniquePath(uid, path, locale?, excludeId?): Ensure uniqueness
url-pattern:
resolvePattern(uid, entity, locale?): Convert pattern to pathvalidatePattern(uid, pattern): Check pattern syntaxgetAllowedFields(uid): List fields available in patternsgetFieldsFromPattern(pattern): Extract field referencesgetRelationsFromPattern(uid, pattern): Get relations to populate
The /api/webtools/router endpoint enables frontend routing:
GET /api/webtools/router?path=/blog/hello-world
Returns:
- The content entity
- Content type UID
- Applies full permission checks
- Optionally executes Strapi controllers (if
router_use_controllers: true)
Main Routes:
/- Overview page/urls- List/edit all URL aliases/patterns- Manage URL patterns
Injection Zones:
- Addons can add navigation items (webtoolsRouter)
- Addons can add sidebar components (webtoolsSidePanel)
Content Manager Integration:
WebtoolsPanelappears in Content Manager's edit view for enabled types- Shows current URL alias, edit form, and injected addon components
Plugin options in config/plugins.js:
module.exports = {
webtools: {
config: {
default_pattern: '/[pluralName]/[documentId]',
unique_per_locale: true,
router_use_controllers: false,
slugify: (text) => text.toLowerCase().replace(/\s+/g, '-'),
website_url: 'https://example.com',
},
},
};- Add method to service interface in
server/services/[service-name].ts - Export service in
server/services/index.ts - Access via
getPluginService('[service-name]')with full type safety
- Add file to
server/middlewares/ - Export from
server/middlewares/index.ts - Register in
bootstrap()usingstrapi.documents.use(middleware)
- Create screen component in
admin/screens/[ScreenName]/ - Add route to
containers/App/index.tsx - Add permission check if needed
- Add navigation link in sidebar configuration
See packages/addons/sitemap as reference:
- Create Strapi plugin with
strapi.webtoolsAddon: truein package.json - Add
strapi-plugin-webtoolsas peerDependency - In
admin/index.jsbootstrap, call injection APIs:const webtoolsPlugin = app.getPlugin('webtools'); webtoolsPlugin.injectComponent('webtoolsRouter', 'route', { path: '/sitemap', label: 'Sitemap', Component: SitemapPage, });
- Optionally extend content types in server
bootstrap()
This project uses Changesets for version management:
# After making changes, create a changeset
npx changeset
# Prepare release (bump versions, update CHANGELOGs)
yarn release:prepare
# Publish to npm
yarn release:publish- Ensure
yarn developis running (builds plugin in watch mode) - Check that yalc linked correctly:
cd playground && yalc check - Rebuild admin:
cd playground && yarn build - Clear Strapi cache:
rm -rf playground/node_modules/.strapi/
Run yarn tscheck to see actual type errors. The playground has a separate tsconfig that may cause confusion.
Ensure ENV_PATH=./playground/.env is set when running tests. The tests need access to the playground's Strapi configuration.
- Main docs: https://docs.pluginpal.io/webtools
- Contributing guide: CONTRIBUTING.md
- Strapi plugin development: https://strapi.io/documentation/developer-docs/latest/plugin-development/