Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
gadya-cms-upgrade
gadya-cms-upgrade
gadya/cms
claude-code
cms
analytics
laravel
page-builder
filament
filament-plugin
inline-editing

Audit a site's gadya/cms install, upgrade it to the latest release, and switch on and wire up every feature it has not taken up yet - config, migrations, plugin switches, schedule, templates - then hand over the steps only a person can do.

Install
php artisan boost:add-skill gadya/cms

Save this content to: .claude/skills/gadya-cms-upgrade/SKILL.md

# Gadya CMS Upgrade

## When to use this skill

Use it when asked to update or upgrade gadya/cms, to audit a site's install, to "turn on everything", or to bring a client site up to the latest version. It works the same on every site: `gadya-cms:audit` says what is missing, and you work down its list until nothing is left to do.

Documentation for the installed version is in `vendor/gadya/cms/docs/`. Read the file named in each step before you change anything it covers. After the update, `vendor/gadya/cms/docs/` is the new version's documentation.

## Rules

- **Code and config only, never content.** Pages, menus, photos and articles live in the database and belong to the client. Never run `migrate:fresh`, `db:seed`, `gadya-cms:import*` or `gadya-cms:install --force`, and never write to `gadyacms_*` tables. When a feature needs content (a menu link to `/events`, a first event), list it for the person to do in the panel.
- **Branch first, never push.** Start from a clean tree (`git status --short` is empty). Pushing often deploys; leave that to the person.
- **Never move or delete a git tag**, in the app or the package.
- **Business facts come from the site, not the package.** The package config's examples (Springfield, 555 numbers, example.com) are placeholders. Take names, phones, domains and addresses from `config/site.php`, the existing layout, `.env` and the published document. Ask only when none of those has the answer.
- **Some things stay off unless the person asks:**
  - comments (`blog.comments.enabled`), because someone must moderate them
  - coming-soon mode
  - `->brand(false)` or `->analytics(false)` on a site that chose them deliberately. The audit marks them; ask.
- **API keys are the client's.** AI and Search Console keys are entered in the panel and stored encrypted, so never put them in config or `.env`.
- Follow the application's own rules in `.ai/rules` and its CLAUDE.md or AGENTS.md. Run Pint on PHP you touch.

## 1. Audit where the site is

```bash
git status --short                       # must be empty
git switch -c chore/gadya-cms-upgrade
composer show gadya/cms | grep versions  # installed
composer show gadya/cms --latest --format=json | php -r 'echo json_decode(stream_get_contents(STDIN))->latest, PHP_EOL;'
grep '"gadya/cms"' composer.json         # the constraint
php artisan gadya-cms:audit              # from 0.4.5; skip on older installs
php artisan test --compact               # the baseline: note anything already failing
```

Write down the installed version, the latest version, the constraint and every failing test before you start.

## 2. Update the package

On 0.x a minor release may break things, so `^0.4` never reaches 0.5:

- **Same minor (0.4.3 → 0.4.9):** `composer update gadya/cms -W`
- **New minor (0.4 → 0.5):** `composer require gadya/cms:^0.5 -W`

If `gadya/connect` is older than 0.6 (`php artisan help gadya:upgrade` fails), update it too: `composer update gadya/connect -W`.

Then let the packages do the mechanical part. Each step checks whether it still has anything to do, so this is right after every release:

```bash
php artisan gadya:upgrade --phase=code   # the release's changes to the repository, then boost:update --discover
php artisan gadya:upgrade                # migrate, the release's server steps, optimize:clear, filament:assets
```

Read what each prints. A `failed` step must be fixed before you go on; `cms.workflow-template` only reports - never edit `.github/workflows/gadya-update.yml` from its message unless the person asks; the portal refreshes it. On a connect without `gadya:upgrade`, run `php artisan migrate`, `php artisan filament:assets` and `php artisan optimize:clear` instead.

## 3. Read what changed

Read `vendor/gadya/cms/CHANGELOG.md` from the old version up to the new one. Also read every section of `vendor/gadya/cms/docs/upgrading.md` between them. These are the breaking changes and the notes on what a person must do; apply each one. The published `config/gadya-cms.php` is merged over the package's defaults at every depth, so a key a release adds works without being copied. Lists (`locales.enabled`) and the site-owned maps (`editable_fields`, `globals`, `users.roles`, `forms.forms`, `pages.types`, `pages.content_fields`, `navigation.menus`, `fonts.*`, `menus.dietary`, `seo.content_signals`) are the site's whole answer, though: when a release adds an entry the site should have (a new editable path, a new role ability), add it there by hand.

## 4. Refresh the skills

`gadya:upgrade --phase=code` ran `php artisan boost:update --discover` where Boost is set up (run it yourself on an older connect). `--discover` picks up skills a release added, including this one on a site that never had it. Check that `boost.json` then lists `gadya-cms-content`, `gadya-cms-development`, `gadya-cms-forms` and `gadya-cms-upgrade` under `skills`.

## 5. Work down the audit

```bash
php artisan gadya-cms:audit --json
```

Each check has a `group`, `label`, `status` (`ok`, `todo`, `optional`) and a `fix`. Clear every `todo` and decide every `optional`, group by group. Features a site has switched off, and a static robots.txt or sitemap.xml, show as `optional`: turn them on when asked to "turn everything on", otherwise leave them and list them.

### Config

- The "Every key the package reads" finding is optional: those keys already take the package's defaults. Copy one from `vendor/gadya/cms/config/gadya-cms.php` into `config/gadya-cms.php` (same place, the package's comment above it) only when this site needs a different value, or when asked to "turn everything on" for a switch that is off by default.
- Fill `seo.site_name`, `seo.organization` (telephone, email, address, area, `same_as` social links) and `seo.domains` (every domain the business owns, the live one first; `dig NS domain` tells you if one is parked elsewhere). Also fill `seo.content_signals`.
- Add every new editable path to `editable_fields`. The live editor refuses anything not listed.

### Database

- `gadya:upgrade` ran the migrations. If the audit still lists pending ones, run `php artisan migrate` and find out why.

### Features

Each feature switch, and what else turning it on needs:

| Switch | Turn on | Also build | Read |
| --- | --- | --- | --- |
| `->blog()` / `blog.routes` | remove `->blog(false)`; `blog.routes => true` | `blog.layout` is the site's public layout; `blog.prefix`; add `blog` to `pages.reserved_slugs` and `pages.route_excluded_slugs` | `articles-and-ai.md` |
| `->events()` / `events.routes` | `events.routes => true` | `events.heading` and `events.description` in the site's voice; `events` in both slug lists | `events-and-search.md` |
| `->search()` / `site_search.routes` | `site_search.routes => true` | `@cmsSearchForm` in the header or footer, styled like the site; `search` in both slug lists; `site_search` in `analytics.events` | `events-and-search.md` |
| `->newsletter()` / `newsletter.enabled` | `newsletter.enabled => true` | `@cmsNewsletterForm` in the footer, wording in `newsletter.*` | `events-and-search.md` |
| `->forms()` | on by default | every enquiry form posts through `@cmsForm('key')` plus `@cmsFormStatus('key')`, with the form defined under `forms.forms` | `forms.md` |
| `->analytics()` / `analytics.enabled` | on | `data-analytics="…"` on the site's key buttons (booking, directions, CTA), with each name listed in `analytics.events` | `analytics.md` |
| `->ai()` | on | nothing in code: the client enters the key under Settings → AI | `articles-and-ai.md` |
| `->foodMenus()` | only on a restaurant, café or bakery | `<x-gadya-cms::menu menu="…" />` on the menu page in place of a hand-written menu (list the menus for the client to enter in the panel); `<x-gadya-cms::menu-specials />` where specials belong | `local-business.md` |
| Opening hours | always on | `<x-gadya-cms::opening-hours />`, `<x-gadya-cms::open-status />`, `<x-gadya-cms::todays-hours />` where the site shows hours, replacing hand-written ones; check `hours.timezone`; list "enter the hours under Appearance → Opening hours and publish" for the client | `local-business.md` |
| Privacy choices / `privacy.banner_enabled` | only when the audit finds third-party trackers, or the client asks | wrap each tracker in `<x-gadya-cms::consented-script category="analytics|marketing">`; `<x-gadya-cms::consent-banner />` before `</body>`; `<x-gadya-cms::privacy-choices-link />` in the footer; list "switch on under Settings → Privacy choices and publish" for the person | `privacy.md` |
| `->redirects()`, `->team()`, `->profile()`, `->unsavedChangesAlerts()`, `->brand()` | on | nothing | `roles-and-globals.md`, `media-and-team.md` |
| `seo.sitemap`, `seo.robots`, `seo.llms`, `seo.markdown`, `seo.link_headers` | `true` | delete any static `public/robots.txt` or `public/sitemap.xml` | `seo.md`, `search-and-readiness.md` |
| `blog.comments.enabled` | only if asked | `blog.comments.notify` addresses | `articles-and-ai.md` |

A site with its own catch-all page route (`/{slug}`) must list every new top-level prefix in `pages.route_excluded_slugs`, or the page route swallows it. Check with `php artisan route:list`.

### Schedule

Add each missing line from the audit to `routes/console.php`. They are idempotent, so schedule all of them even before Search Console is connected. The server needs its cron running `php artisan schedule:run` every minute; put that in the hand-over.

### Templates

- The public layout has `@cmsSeo($page)` in `<head>`, replacing any hand-written title, description, canonical or Open Graph tags. It also has `@cmsToolbar` just before `</body>`.
- `@gadyaBuiltBy` is the last thing in the footer. Replace any hand-pasted `<gadya-built-by>` script and tag with it. If the footer's background is dark, set `built_by.color` to the footer's text colour.
- Search and newsletter forms go where a visitor looks for them. Match the site's own markup and CSS rather than leaving them unstyled.
- Hard-coded text in templates that the client should be able to change becomes `@editable(...)` against the document, with the path in `editable_fields`. Do this only where the template already reads the document for that element; do not invent new content.

### Install

- Run `php artisan filament:assets` whenever the stylesheet check fails (the server phase of `gadya:upgrade` does it too).
- Missing gates: define `manage-content` and `manage-users` as `docs/installation.md` shows.

### Forms

Clients can now build their own forms under **Content → Forms** and place them on any page. Make sure the section loop has `@cmsSection($section, $index)` (the `cms.form-sections` code step adds it to an ordinary loop; the audit names any it could not). Converting the site's existing forms is a separate job with its own skill, `gadya-cms-forms`: offer it, do not do it as part of an upgrade unless asked.

## 6. Verify

```bash
php artisan gadya-cms:audit                     # no "!" left; every "○" decided
vendor/bin/pint --dirty --format agent
php artisan test --compact                      # nothing that passed before may fail now
php artisan gadya-cms:agent-ready               # note the score for the hand-over
php artisan route:list --path=events            # and blog, search, sitemap.xml, robots.txt, llms.txt
```

Request each public address the upgrade touched through the app (`/`, `/sitemap.xml`, `/robots.txt`, `/llms.txt`, `/blog`, `/events`, `/search?q=a`, `/events.ics`) with `get-absolute-url` and `curl -s -o /dev/null -w '%{http_code}'`. Each must answer 200. If `/robots.txt` answers 404 locally with the right text, the web server swallowed it (see *Get found* in `search-and-readiness.md`); that is a server fix for the hand-over, not a code change.

When a test fails because of a change the release intended (a renamed heading, a new default), update the test to match. Never delete a test to make the suite pass. Anything else that fails is a regression: fix it or report it.

## 7. Commit and hand over

Commit on the branch: `chore: gadya/cms <old> → <new> - <what was switched on>`. Do not push.

Then tell the person, in this order:

1. **Versions:** the old and new versions, and the changelog highlights that matter to this client.
2. **Done:** what you switched on and wired up, and the audit before and after (to-do counts).
3. **Their decisions:** any optional item you left off, and why.
4. **After deploying,** things only a person can do:
   - After deploying, run `php artisan gadya:upgrade` on the server (on a site connected to the portal, a rollout sends `upgrade.finish`, which does the same). Otherwise make sure the deploy script runs `php artisan migrate --force` and `php artisan filament:assets`.
   - Make sure the cron runs `schedule:run` every minute.
   - **Pair the site with the Gadya portal** if the audit says it is not connected. Use the portal MCP's `connect-site-tool`, or ask the person for a code from **Sites → Connect a site**. Then run `php artisan gadya:connect <code>` on the live server after deploying.
   - Laravel Forge: delete the `location = /robots.txt` line in the site's nginx config if Settings → Get found says robots.txt answers 404.
   - DNS and Search Console: follow **Settings → Get found** for each domain. It lists the exact records, and the sitemap address to submit.
   - Keys: AI under **Settings → AI**; Google under **Settings → Search & speed**.
   - Content: add new sections to the menu (**Appearance → Menu**) and publish, e.g. What's on or the blog. Write missing page descriptions (the readiness score lists them).

Gadya CMS Upgrade

When to use this skill

Use it when asked to update or upgrade gadya/cms, to audit a site's install, to "turn on everything", or to bring a client site up to the latest version. It works the same on every site: gadya-cms:audit says what is missing, and you work down its list until nothing is left to do.

Documentation for the installed version is in vendor/gadya/cms/docs/. Read the file named in each step before you change anything it covers. After the update, vendor/gadya/cms/docs/ is the new version's documentation.

Rules

  • Code and config only, never content. Pages, menus, photos and articles live in the database and belong to the client. Never run migrate:fresh, db:seed, gadya-cms:import* or gadya-cms:install --force, and never write to gadyacms_* tables. When a feature needs content (a menu link to /events, a first event), list it for the person to do in the panel.
  • Branch first, never push. Start from a clean tree (git status --short is empty). Pushing often deploys; leave that to the person.
  • Never move or delete a git tag, in the app or the package.
  • Business facts come from the site, not the package. The package config's examples (Springfield, 555 numbers, example.com) are placeholders. Take names, phones, domains and addresses from config/site.php, the existing layout, .env and the published document. Ask only when none of those has the answer.
  • Some things stay off unless the person asks:
    • comments (blog.comments.enabled), because someone must moderate them
    • coming-soon mode
    • ->brand(false) or ->analytics(false) on a site that chose them deliberately. The audit marks them; ask.
  • API keys are the client's. AI and Search Console keys are entered in the panel and stored encrypted, so never put them in config or .env.
  • Follow the application's own rules in .ai/rules and its CLAUDE.md or AGENTS.md. Run Pint on PHP you touch.

1. Audit where the site is

git status --short                       # must be empty
git switch -c chore/gadya-cms-upgrade
composer show gadya/cms | grep versions  # installed
composer show gadya/cms --latest --format=json | php -r 'echo json_decode(stream_get_contents(STDIN))->latest, PHP_EOL;'
grep '"gadya/cms"' composer.json         # the constraint
php artisan gadya-cms:audit              # from 0.4.5; skip on older installs
php artisan test --compact               # the baseline: note anything already failing

Write down the installed version, the latest version, the constraint and every failing test before you start.

2. Update the package

On 0.x a minor release may break things, so ^0.4 never reaches 0.5:

  • Same minor (0.4.3 → 0.4.9): composer update gadya/cms -W
  • New minor (0.4 → 0.5): composer require gadya/cms:^0.5 -W

If gadya/connect is older than 0.6 (php artisan help gadya:upgrade fails), update it too: composer update gadya/connect -W.

Then let the packages do the mechanical part. Each step checks whether it still has anything to do, so this is right after every release:

php artisan gadya:upgrade --phase=code   # the release's changes to the repository, then boost:update --discover
php artisan gadya:upgrade                # migrate, the release's server steps, optimize:clear, filament:assets

Read what each prints. A failed step must be fixed before you go on; cms.workflow-template only reports - never edit .github/workflows/gadya-update.yml from its message unless the person asks; the portal refreshes it. On a connect without gadya:upgrade, run php artisan migrate, php artisan filament:assets and php artisan optimize:clear instead.

3. Read what changed

Read vendor/gadya/cms/CHANGELOG.md from the old version up to the new one. Also read every section of vendor/gadya/cms/docs/upgrading.md between them. These are the breaking changes and the notes on what a person must do; apply each one. The published config/gadya-cms.php is merged over the package's defaults at every depth, so a key a release adds works without being copied. Lists (locales.enabled) and the site-owned maps (editable_fields, globals, users.roles, forms.forms, pages.types, pages.content_fields, navigation.menus, fonts.*, menus.dietary, seo.content_signals) are the site's whole answer, though: when a release adds an entry the site should have (a new editable path, a new role ability), add it there by hand.

4. Refresh the skills

gadya:upgrade --phase=code ran php artisan boost:update --discover where Boost is set up (run it yourself on an older connect). --discover picks up skills a release added, including this one on a site that never had it. Check that boost.json then lists gadya-cms-content, gadya-cms-development, gadya-cms-forms and gadya-cms-upgrade under skills.

5. Work down the audit

php artisan gadya-cms:audit --json

Each check has a group, label, status (ok, todo, optional) and a fix. Clear every todo and decide every optional, group by group. Features a site has switched off, and a static robots.txt or sitemap.xml, show as optional: turn them on when asked to "turn everything on", otherwise leave them and list them.

Config

  • The "Every key the package reads" finding is optional: those keys already take the package's defaults. Copy one from vendor/gadya/cms/config/gadya-cms.php into config/gadya-cms.php (same place, the package's comment above it) only when this site needs a different value, or when asked to "turn everything on" for a switch that is off by default.
  • Fill seo.site_name, seo.organization (telephone, email, address, area, same_as social links) and seo.domains (every domain the business owns, the live one first; dig NS domain tells you if one is parked elsewhere). Also fill seo.content_signals.
  • Add every new editable path to editable_fields. The live editor refuses anything not listed.

Database

  • gadya:upgrade ran the migrations. If the audit still lists pending ones, run php artisan migrate and find out why.

Features

Each feature switch, and what else turning it on needs:

Switch Turn on Also build Read
->blog() / blog.routes remove ->blog(false); blog.routes => true blog.layout is the site's public layout; blog.prefix; add blog to pages.reserved_slugs and pages.route_excluded_slugs articles-and-ai.md
->events() / events.routes events.routes => true events.heading and events.description in the site's voice; events in both slug lists events-and-search.md
->search() / site_search.routes site_search.routes => true @cmsSearchForm in the header or footer, styled like the site; search in both slug lists; site_search in analytics.events events-and-search.md
->newsletter() / newsletter.enabled newsletter.enabled => true @cmsNewsletterForm in the footer, wording in newsletter.* events-and-search.md
->forms() on by default every enquiry form posts through @cmsForm('key') plus @cmsFormStatus('key'), with the form defined under forms.forms forms.md
->analytics() / analytics.enabled on data-analytics="…" on the site's key buttons (booking, directions, CTA), with each name listed in analytics.events analytics.md
->ai() on nothing in code: the client enters the key under Settings → AI articles-and-ai.md
->foodMenus() only on a restaurant, café or bakery <x-gadya-cms::menu menu="…" /> on the menu page in place of a hand-written menu (list the menus for the client to enter in the panel); <x-gadya-cms::menu-specials /> where specials belong local-business.md
Opening hours always on <x-gadya-cms::opening-hours />, <x-gadya-cms::open-status />, <x-gadya-cms::todays-hours /> where the site shows hours, replacing hand-written ones; check hours.timezone; list "enter the hours under Appearance → Opening hours and publish" for the client local-business.md
Privacy choices / privacy.banner_enabled only when the audit finds third-party trackers, or the client asks wrap each tracker in `<x-gadya-cms::consented-script category="analytics marketing">; <x-gadya-cms::consent-banner />before; <x-gadya-cms::privacy-choices-link />` in the footer; list "switch on under Settings → Privacy choices and publish" for the person
->redirects(), ->team(), ->profile(), ->unsavedChangesAlerts(), ->brand() on nothing roles-and-globals.md, media-and-team.md
seo.sitemap, seo.robots, seo.llms, seo.markdown, seo.link_headers true delete any static public/robots.txt or public/sitemap.xml seo.md, search-and-readiness.md
blog.comments.enabled only if asked blog.comments.notify addresses articles-and-ai.md

A site with its own catch-all page route (/{slug}) must list every new top-level prefix in pages.route_excluded_slugs, or the page route swallows it. Check with php artisan route:list.

Schedule

Add each missing line from the audit to routes/console.php. They are idempotent, so schedule all of them even before Search Console is connected. The server needs its cron running php artisan schedule:run every minute; put that in the hand-over.

Templates

  • The public layout has @cmsSeo($page) in <head>, replacing any hand-written title, description, canonical or Open Graph tags. It also has @cmsToolbar just before </body>.
  • @gadyaBuiltBy is the last thing in the footer. Replace any hand-pasted <gadya-built-by> script and tag with it. If the footer's background is dark, set built_by.color to the footer's text colour.
  • Search and newsletter forms go where a visitor looks for them. Match the site's own markup and CSS rather than leaving them unstyled.
  • Hard-coded text in templates that the client should be able to change becomes @editable(...) against the document, with the path in editable_fields. Do this only where the template already reads the document for that element; do not invent new content.

Install

  • Run php artisan filament:assets whenever the stylesheet check fails (the server phase of gadya:upgrade does it too).
  • Missing gates: define manage-content and manage-users as docs/installation.md shows.

Forms

Clients can now build their own forms under Content → Forms and place them on any page. Make sure the section loop has @cmsSection($section, $index) (the cms.form-sections code step adds it to an ordinary loop; the audit names any it could not). Converting the site's existing forms is a separate job with its own skill, gadya-cms-forms: offer it, do not do it as part of an upgrade unless asked.

6. Verify

php artisan gadya-cms:audit                     # no "!" left; every "○" decided
vendor/bin/pint --dirty --format agent
php artisan test --compact                      # nothing that passed before may fail now
php artisan gadya-cms:agent-ready               # note the score for the hand-over
php artisan route:list --path=events            # and blog, search, sitemap.xml, robots.txt, llms.txt

Request each public address the upgrade touched through the app (/, /sitemap.xml, /robots.txt, /llms.txt, /blog, /events, /search?q=a, /events.ics) with get-absolute-url and curl -s -o /dev/null -w '%{http_code}'. Each must answer 200. If /robots.txt answers 404 locally with the right text, the web server swallowed it (see Get found in search-and-readiness.md); that is a server fix for the hand-over, not a code change.

When a test fails because of a change the release intended (a renamed heading, a new default), update the test to match. Never delete a test to make the suite pass. Anything else that fails is a regression: fix it or report it.

7. Commit and hand over

Commit on the branch: chore: gadya/cms <old> → <new> - <what was switched on>. Do not push.

Then tell the person, in this order:

  1. Versions: the old and new versions, and the changelog highlights that matter to this client.
  2. Done: what you switched on and wired up, and the audit before and after (to-do counts).
  3. Their decisions: any optional item you left off, and why.
  4. After deploying, things only a person can do:
    • After deploying, run php artisan gadya:upgrade on the server (on a site connected to the portal, a rollout sends upgrade.finish, which does the same). Otherwise make sure the deploy script runs php artisan migrate --force and php artisan filament:assets.
    • Make sure the cron runs schedule:run every minute.
    • Pair the site with the Gadya portal if the audit says it is not connected. Use the portal MCP's connect-site-tool, or ask the person for a code from Sites → Connect a site. Then run php artisan gadya:connect <code> on the live server after deploying.
    • Laravel Forge: delete the location = /robots.txt line in the site's nginx config if Settings → Get found says robots.txt answers 404.
    • DNS and Search Console: follow Settings → Get found for each domain. It lists the exact records, and the sitemap address to submit.
    • Keys: AI under Settings → AI; Google under Settings → Search & speed.
    • Content: add new sections to the menu (Appearance → Menu) and publish, e.g. What's on or the blog. Write missing page descriptions (the readiness score lists them).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource