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-development
gadya-cms-development
gadya/cms
claude-code
cms
analytics
laravel
page-builder
filament
filament-plugin
inline-editing

Build and extend a site on Gadya CMS - the site document, editable templates, controllers, articles and AI, SEO, forms, analytics, redirects and the package's configuration.

Install
php artisan boost:add-skill gadya/cms

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

# Gadya CMS Development

## When to use this skill

Use it when adding or changing public templates or controllers, adding editable content, wiring forms, working on articles or the AI writer, configuring SEO, or changing `config/gadya-cms.php` in an application that uses `gadya/cms`. Full documentation lives in `vendor/gadya/cms/docs/`; read the relevant file there before implementing.

## Upgrading

To upgrade the package or switch on features a site has not taken up, use the `gadya-cms-upgrade` skill and `php artisan gadya-cms:audit`.

## Mental model

- **The site document** is one nested array: top-level keys (`announcement`, `phone`, `nav`, `theme`, `locations`, ...) plus `pages.{slug}` arrays. Pages are rows in `gadyacms_pages`; everything else is a row per key in `gadyacms_settings`. Each carries `draft` and `published` JSON.
- **The admin is for structure, the page is for content.** Filament edits types, sections, snippets, dates; the live editor edits words and photos in place.
- **Nothing is live until published.** `PublishSiteContent` copies draft → published, records a revision, flushes the forever-cached published document.
- **Configuration that is not content** (AI key, digest recipients) lives in `gadyacms_options` via `Gadya\Cms\Options\Options`, never in the document.

## Key classes

| Class | Use it for |
| --- | --- |
| `Content\SiteContentRepository` | `published()`, `draft()`, `forRequest()`, `saveDraft(array)` |
| `Content\PublicDocument` | `from($document)` strips hidden/scheduled pages from nav, cards, locations |
| `Content\PageRegistry` | `isHidden()`, `isScheduled()`, `publicPathFor($slug, $document)`, `resolveRedirect()`, `reservedSlugs()` |
| `Contracts\ResolvesPagePaths` | bind your own when pages live under a prefix (`pages.paths` config) |
| `Editor\EditContext` | `boot()`, `for($basePath)`, `isEnabled()`, `isPreviewing()`, `showsDraft()` |
| `Blog\BlogRepository` | `live()`, `findLive()`, `inTerm()`, `related()`, `termsInUse()`, `uniqueSlug()` |
| `Content\PageTypes` | `fieldsFor($type)`, `sectionTypesFor()`, `label()`, `creatable()` - fields differ per page type |
| `Content\SiteBlocks` | saved sections: `options()`, `section($key)`, `save($label, $section)` |
| `Events\EventCalendar` | `upcoming()`, `past()`, `findLive()`, `ics()`, `structuredData()` |
| `Menus\FoodMenus` / `Menus\Price` | `find($slug)`, `featured()`, `structuredData($menu)`; prices are integer cents |
| `Hours\BusinessHours` / `Hours\OpeningHours` | `current()`, `isOpenAt()`, `status()`, `rangesOn($date)`, `specification()`, `toGoogleBusinessProfile()` |
| `Privacy\Consent` | `allows($category, $request)`, `refuses()`, `choice()`; wrap third-party tags in `<x-gadya-cms::consented-script>` |
| `Search\SiteSearch` | `for($query)` over published pages and live articles |
| `Activity\Activity` | `record($event, $subject)`, `describe($event)` - the who-changed-what log |
| `Services\SchedulePublish` | `schedule($at)`, `isPending()`, `publishIfDue()` |
| `Support\Maintenance` | `isOn()`, `allows($request)`, `shareUrl()` - coming-soon mode |
| `Seo\LinkChecker` / `Models\BrokenLink` | links that lead nowhere, from visitors and from the site itself |
| `Forms\AutoReplies` | the thank-you email each form sends, written in the panel |
| `Ai\AiSettings` / `Ai\Prompter` | `isConfigured()`, `register()`; prompt any `Laravel\Ai` agent through the panel-chosen provider |
| `Seo\SeoHead` | `render($pageOrPost)` behind `@cmsSeo` |
| `Forms\FormDefinition` | the configured forms; `route('gadya-cms.forms.store', 'contact')` |
| `Models\Form` / `Forms\Builder\FormRenderer` | forms built in the panel: `Form::findLive($slug)`, `<x-gadya-cms::form form="slug" />`, `@cmsFormEmbed('slug')` |
| `Forms\Builder\FieldTypes` | register a kind of question of the site's own with `FieldType::make()` |
| `Forms\Destinations\FormDestinations` | where else a form saves each enquiry: `forms.builder.destinations` (a model and a map of questions and `@` tokens, or a `FormDestination` class); `Events\FormSubmitted` for every enquiry |
| `Content\SectionRenderer` | sections the package draws; `@cmsSection($section, $index)` first in the section loop |
| `Analytics\AnalyticsReport` | `for($days)->headline()/daily()/topPages()/referrers()/events()` |
| `Filament\GadyaCmsPlugin` | `::get()->hasBlog()`, `hasAi()`, `hasForms()`, `hasSearch()` ... |
| `Access\Abilities` | `allows($user, 'publish')`, `forRole()`; gates are `gadya-cms.{ability}` |
| `Transfer\SiteExporter` / `SiteImporter` | move a site between installs; secrets never travel |
| `Search\SearchConsole` / `Search\PageSpeed` | Google data on the dashboard; both fake with `Http::fake()` |
| `Seo\AgentReadiness` / `Seo\LlmsText` / `Seo\Markdown` | the AI-assistant side of SEO |

## Controller pattern

```php
public function show(string $slug, SiteContentRepository $repository, PublicDocument $public, EditContext $editor, PageRegistry $registry): View
{
    $editor->boot();
    $site = $public->from($repository->forRequest());
    $page = $site['pages'][$slug] ?? null;

    if (! is_array($page)) {
        $destination = $registry->resolveRedirect($slug);
        abort_if($destination === null, 404);
        return redirect()->route('pages.show', $destination, 301);
    }

    abort_if($registry->isHidden($page) && ! $editor->showsDraft(), 404);
    $editor->for("pages.{$slug}");

    return view('pages.show', compact('page', 'site', 'slug'));
}
```

## Templates

- `@editableFor($path)` once, then `@editable($field, 'text'|'multiline'|'image')` on elements; `@editableGlobal('phone')` for top-level keys.
- Array indexes go into paths (`sections.{$index}.title`); `PublicDocument` preserves keys for that reason - never `array_values()` a card list before rendering.
- Images: store filenames, render with `@siteImage($ref, $width)`, `srcset="@siteSrcset($ref)"`, `style="@siteFocus($ref)"` when cropped; `@siteThumbnail($ref)` for lists.
- Components: `<x-gadya-cms::menu menu="lunch" />`, `<x-gadya-cms::menu-specials />`, `<x-gadya-cms::opening-hours />`, `<x-gadya-cms::open-status />`, `<x-gadya-cms::todays-hours />`, `<x-gadya-cms::consent-banner />`, `<x-gadya-cms::privacy-choices-link />`, `<x-gadya-cms::consented-script>`. Menus and hours are not editable in place; for an editor they carry a link to their admin screen.
- Other directives: `@cmsSearchForm`, `@cmsNewsletterForm`, `@cmsForm('contact')`, `@cmsFormStatus('contact')`, `@cmsSeo($page)`, `@cmsToolbar`, `@gadyaBuiltBy` (the Gadya Media badge, last in the footer, in the site's ink). All take an optional array of overrides.
- Globals (announcement, phone, footer): `@editableGlobal('footer.tagline', 'multiline')`, listed in `gadya-cms.globals`.
- New page type: `php artisan gadya-cms:make:page-template pages/types/name` first, then adjust.
- Layout: `@cmsSeo($page)` in head; editor assets only when `EditContext::isEnabled()`; `@cmsToolbar` before `</body>`; `data-analytics-endpoint="{{ route('gadya-cms.events.store') }}"` on body; `data-analytics="booking_start"` on things worth counting (names must be in `analytics.events`).
- Forms: prefer a form built under Content → Forms, placed with `<x-gadya-cms::form form="slug" />` or a Form section (the section loop needs `@cmsSection($section, $index)` as its first line). Hand-written: `<form method="POST" action="{{ route('gadya-cms.forms.store', 'contact') }}">@cmsForm('contact') ...</form> @cmsFormStatus('contact')` with fields/rules/notify under `forms.forms.contact`. To convert existing forms, use the `gadya-cms-forms` skill.
- Blog templates ship as `gadya-cms::blog.index/show` extending `blog.layout`; style `cms-blog__*` / `cms-article__*` or publish the views with `--tag=gadya-cms-views`.

## Configuration rules

- Published config is merged over the package's defaults recursively, so a key the site leaves out keeps its default at any depth. Lists 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: set one and the package's entries are gone.
- New editable paths → `editable_fields`. New page URL prefixes → a `ResolvesPagePaths` implementation in `pages.paths` and the slug in `pages.reserved_slugs` + `pages.route_excluded_slugs`.
- Page edit-screen fields → `pages.content_fields` (`text`, `textarea`, `image`).
- Plugin switches: `blog`, `ai`, `forms`, `redirects`, `search` (Google cards), `events`, `foodMenus` (off by default), `newsletter`, `team`, `analytics`, `brand`, `profile`, `unsavedChangesAlerts`, `navigationGroups`.
- Never paste a third-party tracker (gtag, Meta pixel, ...) as a bare `<script>`: wrap it in `<x-gadya-cms::consented-script category="analytics|marketing">` and put `<x-gadya-cms::consent-banner />` and `<x-gadya-cms::privacy-choices-link />` in the layout. `gadya-cms:audit` flags a bare one.
- Pages are soft-deleted: `Page::query()` hides the trash, `withTrashed()` shows it, `restoreToDraft()` brings one back editable. A slug a trashed page holds is restored and overwritten rather than colliding.
- Menus: the main one is `nav` in the document and in the form; more are configured under `navigation.menus` and read with `NavigationTree::forMenu($document, 'footer')`.
- Route parameters reach a controller method **by position, not by name** - never use `->defaults()` to pass a second value to a shared action; give each route its own method.
- Roles: `users.roles.{role} = ['label' => ..., 'abilities' => [...]]`; abilities are content, articles, photos, enquiries, publish, settings, forms (implied by content). The host's `canManageContent()` must include every role. Screens check `Abilities::gate(Abilities::X)` in `canAccess()`.
- The AI-assistant checks (`seo.llms`, `seo.markdown`, `seo.ai_crawlers`, `seo.organization`) are on by default; `gadya-cms:agent-ready` scores them.

## Testing

- Seed content with `app(SiteContentRepository::class)->saveDraft($document)` then `app(PublishSiteContent::class)->handle()` and `flushPublishedCache()`.
- Fake AI: `ArticleWriter::fake([[...structured fields...]])`, `MetaWriter::fake([...])`, `ConnectionCheck::fake(['OK'])` after `app(AiSettings::class)->save([...])`.
- Filament tables: `Livewire::test(ListPosts::class)->selectTableRecords([...])->callAction(TestAction::make('x')->table()->bulk())`.
- Never mock `ImageCapabilities` without `driverName()`, `hasImagick()`, `supportsWebp()`.
- Google services: `Http::fake(['oauth2.googleapis.com/token' => ..., 'www.googleapis.com/webmasters/v3/sites/*' => ..., PageSpeed::ENDPOINT.'*' => ...])`; a service-account key for tests is any RSA PEM in `{client_email, private_key}` JSON.
- The demo host under `workbench/` is a full example application: controller, layout, template, seeder.
- Filament form/schema actions are addressed with `TestAction::make('x')->schemaComponent('sectionKey')`; table bulk actions need `->selectTableRecords([...])` first.
- `actingAs` twice with *different* users in one test logs the session out (AuthenticateSession). Reuse one user, or split the test.
- SQLite refuses `HAVING` on an ungrouped query: use `whereHas` and keep `withCount` for the ordering.

## Commands

`gadya-cms:install`, `gadya-cms:editor`, `gadya-cms:password`, `gadya-cms:doctor`, `gadya-cms:export` / `gadya-cms:import`, `gadya-cms:make:page-template`, `gadya-cms:media-variants`, `gadya-cms:import-legacy-content`, `gadya-cms:import-legacy-media`, `gadya-cms:prune-analytics`, `gadya-cms:analytics-digest`, `gadya-cms:search-console`, `gadya-cms:pagespeed`, `gadya-cms:agent-ready`. Deploys: `migrate --force`, `filament:assets`, `optimize:clear`, a queue worker.

## References

The full documentation ships inside this skill, so read the relevant file before implementing rather than guessing at an API:

- `references/installation.md` - requirements, the plugin, gates, switches, scheduling, queues
- `references/site-document.md` - how content is shaped and stored, `ResolvesPagePaths`, page fields, publishing
- `references/live-editor.md` - the layout, every directive, the allow-list, locks, preview links
- `references/articles-and-ai.md` - the writing screen, the public blog, providers, faking agents in tests
- `references/seo.md` - `@cmsSeo`, sitemap, robots, redirects
- `references/forms.md` - the form builder, Form sections and `@cmsSection`, `[form:slug]`, emails, Twilio texts, webhooks, figures, configured forms, converting them, the inbox
- `references/portal.md` - enquiries pushed to the portal, remote commands, requested changes (`ChangeWriter`), `<x-gadya-cms::call-back />`, `<x-gadya-cms::reviews />`
- `references/email.md` - sending through Gadya Media, the address, Reply-To, the footer
- `references/analytics.md` - what is counted, events, live updates, reports, `AnalyticsReport`
- `references/media-and-team.md` - the photo pipeline, folders, usage; invitations and the last-administrator rule
- `references/commands.md` - every Artisan command, config publishing, deploy steps
- `references/roles-and-globals.md` - abilities per role, the Everywhere screen
- `references/transfer.md` - moving a site, responsive photos
- `references/search-and-readiness.md` - Search Console, PageSpeed, the AI-readiness checks, the Get found page (live checks, DNS per domain, Forge robots.txt 404)
- `references/demo.md` - the demo site and the template generator
- `references/events-and-search.md` - the diary, the search box, the mailing list
- `references/local-business.md` - food menus (`<x-gadya-cms::menu>`, sold out, `Menu` JSON-LD) and opening hours (`<x-gadya-cms::opening-hours>`, `open-status`, `todays-hours`, `OpeningHours`)
- `references/privacy.md` - the consent banner, `<x-gadya-cms::consented-script>`, Global Privacy Control, what the CMS's analytics stores
- `references/operations.md` - trash, activity, scheduled publish, coming soon, broken links, replies
- `references/upgrading.md` - what each release asks of an application
- `references/multilingual.md` - more than one language: /es/ addresses, the switcher, hreflang, editing and AI translation

Gadya CMS Development

When to use this skill

Use it when adding or changing public templates or controllers, adding editable content, wiring forms, working on articles or the AI writer, configuring SEO, or changing config/gadya-cms.php in an application that uses gadya/cms. Full documentation lives in vendor/gadya/cms/docs/; read the relevant file there before implementing.

Upgrading

To upgrade the package or switch on features a site has not taken up, use the gadya-cms-upgrade skill and php artisan gadya-cms:audit.

Mental model

  • The site document is one nested array: top-level keys (announcement, phone, nav, theme, locations, ...) plus pages.{slug} arrays. Pages are rows in gadyacms_pages; everything else is a row per key in gadyacms_settings. Each carries draft and published JSON.
  • The admin is for structure, the page is for content. Filament edits types, sections, snippets, dates; the live editor edits words and photos in place.
  • Nothing is live until published. PublishSiteContent copies draft → published, records a revision, flushes the forever-cached published document.
  • Configuration that is not content (AI key, digest recipients) lives in gadyacms_options via Gadya\Cms\Options\Options, never in the document.

Key classes

Class Use it for
Content\SiteContentRepository published(), draft(), forRequest(), saveDraft(array)
Content\PublicDocument from($document) strips hidden/scheduled pages from nav, cards, locations
Content\PageRegistry isHidden(), isScheduled(), publicPathFor($slug, $document), resolveRedirect(), reservedSlugs()
Contracts\ResolvesPagePaths bind your own when pages live under a prefix (pages.paths config)
Editor\EditContext boot(), for($basePath), isEnabled(), isPreviewing(), showsDraft()
Blog\BlogRepository live(), findLive(), inTerm(), related(), termsInUse(), uniqueSlug()
Content\PageTypes fieldsFor($type), sectionTypesFor(), label(), creatable() - fields differ per page type
Content\SiteBlocks saved sections: options(), section($key), save($label, $section)
Events\EventCalendar upcoming(), past(), findLive(), ics(), structuredData()
Menus\FoodMenus / Menus\Price find($slug), featured(), structuredData($menu); prices are integer cents
Hours\BusinessHours / Hours\OpeningHours current(), isOpenAt(), status(), rangesOn($date), specification(), toGoogleBusinessProfile()
Privacy\Consent allows($category, $request), refuses(), choice(); wrap third-party tags in <x-gadya-cms::consented-script>
Search\SiteSearch for($query) over published pages and live articles
Activity\Activity record($event, $subject), describe($event) - the who-changed-what log
Services\SchedulePublish schedule($at), isPending(), publishIfDue()
Support\Maintenance isOn(), allows($request), shareUrl() - coming-soon mode
Seo\LinkChecker / Models\BrokenLink links that lead nowhere, from visitors and from the site itself
Forms\AutoReplies the thank-you email each form sends, written in the panel
Ai\AiSettings / Ai\Prompter isConfigured(), register(); prompt any Laravel\Ai agent through the panel-chosen provider
Seo\SeoHead render($pageOrPost) behind @cmsSeo
Forms\FormDefinition the configured forms; route('gadya-cms.forms.store', 'contact')
Models\Form / Forms\Builder\FormRenderer forms built in the panel: Form::findLive($slug), <x-gadya-cms::form form="slug" />, @cmsFormEmbed('slug')
Forms\Builder\FieldTypes register a kind of question of the site's own with FieldType::make()
Forms\Destinations\FormDestinations where else a form saves each enquiry: forms.builder.destinations (a model and a map of questions and @ tokens, or a FormDestination class); Events\FormSubmitted for every enquiry
Content\SectionRenderer sections the package draws; @cmsSection($section, $index) first in the section loop
Analytics\AnalyticsReport for($days)->headline()/daily()/topPages()/referrers()/events()
Filament\GadyaCmsPlugin ::get()->hasBlog(), hasAi(), hasForms(), hasSearch() ...
Access\Abilities allows($user, 'publish'), forRole(); gates are gadya-cms.{ability}
Transfer\SiteExporter / SiteImporter move a site between installs; secrets never travel
Search\SearchConsole / Search\PageSpeed Google data on the dashboard; both fake with Http::fake()
Seo\AgentReadiness / Seo\LlmsText / Seo\Markdown the AI-assistant side of SEO

Controller pattern

public function show(string $slug, SiteContentRepository $repository, PublicDocument $public, EditContext $editor, PageRegistry $registry): View
{
    $editor->boot();
    $site = $public->from($repository->forRequest());
    $page = $site['pages'][$slug] ?? null;

    if (! is_array($page)) {
        $destination = $registry->resolveRedirect($slug);
        abort_if($destination === null, 404);
        return redirect()->route('pages.show', $destination, 301);
    }

    abort_if($registry->isHidden($page) && ! $editor->showsDraft(), 404);
    $editor->for("pages.{$slug}");

    return view('pages.show', compact('page', 'site', 'slug'));
}

Templates

  • @editableFor($path) once, then @editable($field, 'text'|'multiline'|'image') on elements; @editableGlobal('phone') for top-level keys.
  • Array indexes go into paths (sections.{$index}.title); PublicDocument preserves keys for that reason - never array_values() a card list before rendering.
  • Images: store filenames, render with @siteImage($ref, $width), srcset="@siteSrcset($ref)", style="@siteFocus($ref)" when cropped; @siteThumbnail($ref) for lists.
  • Components: <x-gadya-cms::menu menu="lunch" />, <x-gadya-cms::menu-specials />, <x-gadya-cms::opening-hours />, <x-gadya-cms::open-status />, <x-gadya-cms::todays-hours />, <x-gadya-cms::consent-banner />, <x-gadya-cms::privacy-choices-link />, <x-gadya-cms::consented-script>. Menus and hours are not editable in place; for an editor they carry a link to their admin screen.
  • Other directives: @cmsSearchForm, @cmsNewsletterForm, @cmsForm('contact'), @cmsFormStatus('contact'), @cmsSeo($page), @cmsToolbar, @gadyaBuiltBy (the Gadya Media badge, last in the footer, in the site's ink). All take an optional array of overrides.
  • Globals (announcement, phone, footer): @editableGlobal('footer.tagline', 'multiline'), listed in gadya-cms.globals.
  • New page type: php artisan gadya-cms:make:page-template pages/types/name first, then adjust.
  • Layout: @cmsSeo($page) in head; editor assets only when EditContext::isEnabled(); @cmsToolbar before </body>; data-analytics-endpoint="{{ route('gadya-cms.events.store') }}" on body; data-analytics="booking_start" on things worth counting (names must be in analytics.events).
  • Forms: prefer a form built under Content → Forms, placed with <x-gadya-cms::form form="slug" /> or a Form section (the section loop needs @cmsSection($section, $index) as its first line). Hand-written: <form method="POST" action="{{ route('gadya-cms.forms.store', 'contact') }}">@cmsForm('contact') ...</form> @cmsFormStatus('contact') with fields/rules/notify under forms.forms.contact. To convert existing forms, use the gadya-cms-forms skill.
  • Blog templates ship as gadya-cms::blog.index/show extending blog.layout; style cms-blog__* / cms-article__* or publish the views with --tag=gadya-cms-views.

Configuration rules

  • Published config is merged over the package's defaults recursively, so a key the site leaves out keeps its default at any depth. Lists 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: set one and the package's entries are gone.
  • New editable paths → editable_fields. New page URL prefixes → a ResolvesPagePaths implementation in pages.paths and the slug in pages.reserved_slugs + pages.route_excluded_slugs.
  • Page edit-screen fields → pages.content_fields (text, textarea, image).
  • Plugin switches: blog, ai, forms, redirects, search (Google cards), events, foodMenus (off by default), newsletter, team, analytics, brand, profile, unsavedChangesAlerts, navigationGroups.
  • Never paste a third-party tracker (gtag, Meta pixel, ...) as a bare <script>: wrap it in <x-gadya-cms::consented-script category="analytics|marketing"> and put <x-gadya-cms::consent-banner /> and <x-gadya-cms::privacy-choices-link /> in the layout. gadya-cms:audit flags a bare one.
  • Pages are soft-deleted: Page::query() hides the trash, withTrashed() shows it, restoreToDraft() brings one back editable. A slug a trashed page holds is restored and overwritten rather than colliding.
  • Menus: the main one is nav in the document and in the form; more are configured under navigation.menus and read with NavigationTree::forMenu($document, 'footer').
  • Route parameters reach a controller method by position, not by name - never use ->defaults() to pass a second value to a shared action; give each route its own method.
  • Roles: users.roles.{role} = ['label' => ..., 'abilities' => [...]]; abilities are content, articles, photos, enquiries, publish, settings, forms (implied by content). The host's canManageContent() must include every role. Screens check Abilities::gate(Abilities::X) in canAccess().
  • The AI-assistant checks (seo.llms, seo.markdown, seo.ai_crawlers, seo.organization) are on by default; gadya-cms:agent-ready scores them.

Testing

  • Seed content with app(SiteContentRepository::class)->saveDraft($document) then app(PublishSiteContent::class)->handle() and flushPublishedCache().
  • Fake AI: ArticleWriter::fake([[...structured fields...]]), MetaWriter::fake([...]), ConnectionCheck::fake(['OK']) after app(AiSettings::class)->save([...]).
  • Filament tables: Livewire::test(ListPosts::class)->selectTableRecords([...])->callAction(TestAction::make('x')->table()->bulk()).
  • Never mock ImageCapabilities without driverName(), hasImagick(), supportsWebp().
  • Google services: Http::fake(['oauth2.googleapis.com/token' => ..., 'www.googleapis.com/webmasters/v3/sites/*' => ..., PageSpeed::ENDPOINT.'*' => ...]); a service-account key for tests is any RSA PEM in {client_email, private_key} JSON.
  • The demo host under workbench/ is a full example application: controller, layout, template, seeder.
  • Filament form/schema actions are addressed with TestAction::make('x')->schemaComponent('sectionKey'); table bulk actions need ->selectTableRecords([...]) first.
  • actingAs twice with different users in one test logs the session out (AuthenticateSession). Reuse one user, or split the test.
  • SQLite refuses HAVING on an ungrouped query: use whereHas and keep withCount for the ordering.

Commands

gadya-cms:install, gadya-cms:editor, gadya-cms:password, gadya-cms:doctor, gadya-cms:export / gadya-cms:import, gadya-cms:make:page-template, gadya-cms:media-variants, gadya-cms:import-legacy-content, gadya-cms:import-legacy-media, gadya-cms:prune-analytics, gadya-cms:analytics-digest, gadya-cms:search-console, gadya-cms:pagespeed, gadya-cms:agent-ready. Deploys: migrate --force, filament:assets, optimize:clear, a queue worker.

References

The full documentation ships inside this skill, so read the relevant file before implementing rather than guessing at an API:

  • references/installation.md - requirements, the plugin, gates, switches, scheduling, queues
  • references/site-document.md - how content is shaped and stored, ResolvesPagePaths, page fields, publishing
  • references/live-editor.md - the layout, every directive, the allow-list, locks, preview links
  • references/articles-and-ai.md - the writing screen, the public blog, providers, faking agents in tests
  • references/seo.md - @cmsSeo, sitemap, robots, redirects
  • references/forms.md - the form builder, Form sections and @cmsSection, [form:slug], emails, Twilio texts, webhooks, figures, configured forms, converting them, the inbox
  • references/portal.md - enquiries pushed to the portal, remote commands, requested changes (ChangeWriter), <x-gadya-cms::call-back />, <x-gadya-cms::reviews />
  • references/email.md - sending through Gadya Media, the address, Reply-To, the footer
  • references/analytics.md - what is counted, events, live updates, reports, AnalyticsReport
  • references/media-and-team.md - the photo pipeline, folders, usage; invitations and the last-administrator rule
  • references/commands.md - every Artisan command, config publishing, deploy steps
  • references/roles-and-globals.md - abilities per role, the Everywhere screen
  • references/transfer.md - moving a site, responsive photos
  • references/search-and-readiness.md - Search Console, PageSpeed, the AI-readiness checks, the Get found page (live checks, DNS per domain, Forge robots.txt 404)
  • references/demo.md - the demo site and the template generator
  • references/events-and-search.md - the diary, the search box, the mailing list
  • references/local-business.md - food menus (<x-gadya-cms::menu>, sold out, Menu JSON-LD) and opening hours (<x-gadya-cms::opening-hours>, open-status, todays-hours, OpeningHours)
  • references/privacy.md - the consent banner, <x-gadya-cms::consented-script>, Global Privacy Control, what the CMS's analytics stores
  • references/operations.md - trash, activity, scheduled publish, coming soon, broken links, replies
  • references/upgrading.md - what each release asks of an application
  • references/multilingual.md - more than one language: /es/ addresses, the switcher, hreflang, editing and AI translation
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package