Skip to content

Add Chatwoot plugin and provider#126

Closed
Philip-Evans wants to merge 1 commit intopelican-dev:mainfrom
Philip-Evans:main
Closed

Add Chatwoot plugin and provider#126
Philip-Evans wants to merge 1 commit intopelican-dev:mainfrom
Philip-Evans:main

Conversation

@Philip-Evans
Copy link
Copy Markdown

@Philip-Evans Philip-Evans commented Apr 24, 2026

Add Chatwoot plugin and provider

Summary by CodeRabbit

  • New Features
    • Introduced Chatwoot live chat widget plugin for real-time customer support integration
    • Added settings form to configure Chatwoot base URL and website token
    • Automatically injects Chatwoot SDK into all panels upon configuration
    • Settings persist securely to environment variables

Add Chatwoot plugin and provider
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete Chatwoot live chat widget plugin for Filament, including documentation, configuration files, plugin manifest, main plugin class with settings form, and a service provider that injects the Chatwoot SDK into panel rendering.

Changes

Cohort / File(s) Summary
Documentation
chatwoot/README.md
Adds plugin description and integration details for the Chatwoot widget functionality.
Configuration & Metadata
chatwoot/config/chatwoot.php, chatwoot/plugin.json
Introduces configuration file exposing base_url and website_token from environment variables, and plugin manifest registering the ChatwootPlugin class with plugin metadata.
Plugin Implementation
chatwoot/src/ChatwootPlugin.php, chatwoot/src/Providers/ChatwootPluginProvider.php
Implements the main plugin class with settings form for base_url and website_token, persists settings to environment variables, and adds service provider that conditionally injects Chatwoot SDK script into Filament panels.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin User
    participant Panel as Filament Panel
    participant Plugin as ChatwootPlugin
    participant Config as Configuration
    participant Provider as ChatwootPluginProvider
    participant Browser as Browser/Render Hook

    Admin->>Panel: Access plugin settings
    Panel->>Plugin: Load settings form
    Plugin->>Config: Read base_url & website_token
    Config-->>Plugin: Return config values
    Plugin-->>Admin: Display prefilled form

    Admin->>Plugin: Submit settings
    Plugin->>Config: Write CHATWOOT_BASE_URL & CHATWOOT_WEBSITE_TOKEN env vars
    Config-->>Plugin: Settings persisted
    Plugin-->>Admin: Show success notification

    Panel->>Provider: Boot service provider
    Provider->>Config: Retrieve base_url & website_token
    Config-->>Provider: Return values
    alt Config values present
        Provider->>Browser: Register render hook
        Browser->>Browser: Inject Chatwoot SDK script
        Browser->>Browser: Initialize window.chatwootSDK.run()
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • lajczi

Poem

🐰 A widget hops into view,
Chatwoot's SDK shines through,
Settings saved, config's true,
Live chat flows, conversations brew,
Support's window opens anew! 💬

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Chatwoot plugin and provider' clearly and concisely summarizes the main changes, which include a new Chatwoot plugin class, configuration, service provider, and documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
chatwoot/README.md (1)

1-13: Consider expanding the README with installation & configuration instructions.

The README only lists features and credits. Users benefit from a short section documenting:

  • The required env vars (CHATWOOT_BASE_URL, CHATWOOT_WEBSITE_TOKEN) or where to configure them in the settings UI.
  • Where to obtain the website_token from Chatwoot.
  • Expected base_url format (with or without trailing slash — see related comment on the provider).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chatwoot/README.md` around lines 1 - 13, Update the README to add an
Installation & Configuration section that documents required environment
variables CHATWOOT_BASE_URL and CHATWOOT_WEBSITE_TOKEN (and/or where to set them
in the plugin settings UI), explain how to obtain the website_token from the
Chatwoot dashboard, and clarify the expected base_url format (include example
with and without trailing slash and state which is accepted); reference these
exact symbols (CHATWOOT_BASE_URL, CHATWOOT_WEBSITE_TOKEN, website_token,
Chatwoot settings/UI) so users can quickly map README instructions to the
code/config.
chatwoot/src/ChatwootPlugin.php (1)

25-28: Add URL validation (and consider normalizing) the base_url input.

base_url is used as a URL prefix in the injected script (BASE_URL + "/packs/js/sdk.js"). Without validation, a typo or trailing slash will silently break SDK loading. Consider ->url() (and optionally stripping a trailing slash in saveSettings).

♻️ Proposed fix
             TextInput::make('base_url')
                 ->label('Base URL')
                 ->required()
+                ->url()
                 ->default(fn () => config('chatwoot.base_url')),

And in saveSettings:

         $this->writeToEnvironment([
-            'CHATWOOT_BASE_URL' => $data['base_url'],
+            'CHATWOOT_BASE_URL' => rtrim($data['base_url'], '/'),
             'CHATWOOT_WEBSITE_TOKEN' => $data['website_token'],
         ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chatwoot/src/ChatwootPlugin.php` around lines 25 - 28, The base_url TextInput
currently lacks URL validation and normalization; update
TextInput::make('base_url') to include URL validation (e.g., ->url()) and
client-side requirement, and in the plugin's saveSettings method normalize the
value (strip any trailing slash and optionally ensure a valid scheme like
https://) before persisting so BASE_URL used to build BASE_URL +
"/packs/js/sdk.js" will always be a well-formed prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@chatwoot/src/ChatwootPlugin.php`:
- Around line 12-46: The class and method opening braces must be moved to their
own lines to satisfy Pint rules: update the ChatwootPlugin class declaration and
every method signature (getId, register, boot, getSettingsForm, saveSettings) so
the "{" is on the next line (e.g., change "class ChatwootPlugin implements
HasPluginSettings, Plugin {" to have the "{" on a new line, and do the same for
each function declaration). Ensure all method-level braces and the class-level
brace follow this style consistently to resolve class_definition,
braces_position, and single_class_elements lint errors.

In `@chatwoot/src/Providers/ChatwootPluginProvider.php`:
- Around line 10-11: The class and method opening braces are on the same line
which violates PSR-12/Pint; update the class ChatwootPluginProvider and its
method boot() so each opening brace is placed on the following line (i.e., put
the "{" for class ChatwootPluginProvider on its own line and likewise put the
"{" for public function boot() on the next line) to satisfy the class_definition
and brace lint rules.
- Around line 18-40: The injected script currently interpolates Blade-escaped
strings directly into JS string literals causing possible JS injection; update
the Blade::render call to serialize the values with
Illuminate\Support\Js::from() (or the `@js` directive) so baseUrl and websiteToken
are safely JS-encoded before being used in the script where
window.chatwootSDK.run is called; also normalize baseUrl to remove a trailing
slash (so the computed g.src = baseUrl + "/packs/js/sdk.js" never produces a
double slash) and reference the serialized values (Js::from($baseUrl) and
Js::from($websiteToken)) instead of raw {{ $baseUrl }} / {{ $websiteToken }}
inside the Blade::render template.

---

Nitpick comments:
In `@chatwoot/README.md`:
- Around line 1-13: Update the README to add an Installation & Configuration
section that documents required environment variables CHATWOOT_BASE_URL and
CHATWOOT_WEBSITE_TOKEN (and/or where to set them in the plugin settings UI),
explain how to obtain the website_token from the Chatwoot dashboard, and clarify
the expected base_url format (include example with and without trailing slash
and state which is accepted); reference these exact symbols (CHATWOOT_BASE_URL,
CHATWOOT_WEBSITE_TOKEN, website_token, Chatwoot settings/UI) so users can
quickly map README instructions to the code/config.

In `@chatwoot/src/ChatwootPlugin.php`:
- Around line 25-28: The base_url TextInput currently lacks URL validation and
normalization; update TextInput::make('base_url') to include URL validation
(e.g., ->url()) and client-side requirement, and in the plugin's saveSettings
method normalize the value (strip any trailing slash and optionally ensure a
valid scheme like https://) before persisting so BASE_URL used to build BASE_URL
+ "/packs/js/sdk.js" will always be a well-formed prefix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6912c5a5-2051-4f00-9c99-9369e0497aa4

📥 Commits

Reviewing files that changed from the base of the PR and between 1eb679c and 3ab925a.

📒 Files selected for processing (5)
  • chatwoot/README.md
  • chatwoot/config/chatwoot.php
  • chatwoot/plugin.json
  • chatwoot/src/ChatwootPlugin.php
  • chatwoot/src/Providers/ChatwootPluginProvider.php
📜 Review details
🧰 Additional context used
🪛 GitHub Actions: Lint
chatwoot/src/Providers/ChatwootPluginProvider.php

[error] 1-1: Laravel Pint reported style issues: class_definition, brace.

chatwoot/src/ChatwootPlugin.php

[error] 1-1: Laravel Pint reported style issues: class_definition, braces_position, single_class_elements.

🪛 PHPMD (2.15.0)
chatwoot/src/ChatwootPlugin.php

[warning] 19-19: Avoid unused parameters such as '$panel'. (undefined)

(UnusedFormalParameter)


[warning] 21-21: Avoid unused parameters such as '$panel'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (3)
chatwoot/config/chatwoot.php (1)

1-6: LGTM!

Config file cleanly exposes the two env-backed values consumed by the provider and plugin class.

chatwoot/plugin.json (1)

1-15: Manifest looks fine.

Identity/metadata fields mirror the ChatwootPlugin class and match sibling plugins' conventions. See the separate comment on ChatwootPluginProvider.php regarding how the service provider gets loaded — confirm the host loader reads PHP service providers implicitly, otherwise the render hook will never register.

chatwoot/src/Providers/ChatwootPluginProvider.php (1)

1-44: Verify whether the plugin loader auto-discovers service providers.

The ChatwootPluginProvider is not explicitly registered in ChatwootPlugin.boot() or referenced in plugin.json. However, this follows the identical pattern used by TawktoWidgetPlugin and other plugins in the repository (e.g., SubdomainsPluginProvider, UserCreatableServersPluginProvider, TicketsPluginProvider), which suggests a convention-based auto-discovery mechanism may exist in the main application's plugin loader.

To confirm the widget will render, verify that the main Pelican application auto-discovers service providers from the {PluginNamespace}\Providers\{PluginId}PluginProvider convention. If no such auto-discovery exists, the render hook will not fire and the provider must be explicitly registered.

Comment on lines +12 to +46
class ChatwootPlugin implements HasPluginSettings, Plugin {
use EnvironmentWriterTrait;

public function getId(): string {
return 'chatwoot';
}

public function register(Panel $panel): void {}

public function boot(Panel $panel): void {}

public function getSettingsForm(): array {
return [
TextInput::make('base_url')
->label('Base URL')
->required()
->default(fn () => config('chatwoot.base_url')),
TextInput::make('website_token')
->label('Website Token')
->required()
->default(fn () => config('chatwoot.website_token')),
];
}

public function saveSettings(array $data): void {
$this->writeToEnvironment([
'CHATWOOT_BASE_URL' => $data['base_url'],
'CHATWOOT_WEBSITE_TOKEN' => $data['website_token'],
]);

Notification::make()
->title('Settings saved')
->success()
->send();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Pint style: opening braces on their own line for class and all methods.

The Lint pipeline is failing (class_definition, braces_position, single_class_elements). Move every { on the class/method signature lines to the next line.

🎨 Proposed fix
-class ChatwootPlugin implements HasPluginSettings, Plugin {
+class ChatwootPlugin implements HasPluginSettings, Plugin
+{
     use EnvironmentWriterTrait;

-    public function getId(): string {
+    public function getId(): string
+    {
         return 'chatwoot';
     }

     public function register(Panel $panel): void {}

     public function boot(Panel $panel): void {}

-    public function getSettingsForm(): array {
+    public function getSettingsForm(): array
+    {
         return [
             ...
         ];
     }

-    public function saveSettings(array $data): void {
+    public function saveSettings(array $data): void
+    {
         $this->writeToEnvironment([
             ...
         ]);
         ...
     }
 }
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 19-19: Avoid unused parameters such as '$panel'. (undefined)

(UnusedFormalParameter)


[warning] 21-21: Avoid unused parameters such as '$panel'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chatwoot/src/ChatwootPlugin.php` around lines 12 - 46, The class and method
opening braces must be moved to their own lines to satisfy Pint rules: update
the ChatwootPlugin class declaration and every method signature (getId,
register, boot, getSettingsForm, saveSettings) so the "{" is on the next line
(e.g., change "class ChatwootPlugin implements HasPluginSettings, Plugin {" to
have the "{" on a new line, and do the same for each function declaration).
Ensure all method-level braces and the class-level brace follow this style
consistently to resolve class_definition, braces_position, and
single_class_elements lint errors.

Comment on lines +10 to +11
class ChatwootPluginProvider extends ServiceProvider {
public function boot(): void {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Pint style: opening braces must be on their own line.

The Lint pipeline is failing (class_definition, brace). PSR-12/Pint requires class and method opening braces on the next line.

🎨 Proposed fix
-class ChatwootPluginProvider extends ServiceProvider {
-    public function boot(): void {
+class ChatwootPluginProvider extends ServiceProvider
+{
+    public function boot(): void
+    {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class ChatwootPluginProvider extends ServiceProvider {
public function boot(): void {
class ChatwootPluginProvider extends ServiceProvider
{
public function boot(): void
{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chatwoot/src/Providers/ChatwootPluginProvider.php` around lines 10 - 11, The
class and method opening braces are on the same line which violates PSR-12/Pint;
update the class ChatwootPluginProvider and its method boot() so each opening
brace is placed on the following line (i.e., put the "{" for class
ChatwootPluginProvider on its own line and likewise put the "{" for public
function boot() on the next line) to satisfy the class_definition and brace lint
rules.

Comment on lines +18 to +40
fn () => Blade::render(<<<'HTML'
<!--Start of Chatwoot Script-->
<script type="text/javascript">
(function(d,t) {
var BASE_URL="{{ $baseUrl }}";
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=BASE_URL+"/packs/js/sdk.js";
g.defer = true;
g.async = true;
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
websiteToken: '{{ $websiteToken }}',
baseUrl: BASE_URL
})
}
})(document,"script");
</script>
<!--End of Chatwoot Script-->
HTML, [
'baseUrl' => $baseUrl,
'websiteToken' => $websiteToken,
])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Laravel Blade Js::from helper for safely embedding PHP values into JavaScript

💡 Result:

The Laravel Blade Js::from helper, part of Illuminate\Support\Js, safely embeds PHP values into JavaScript by converting them to properly escaped JavaScript expressions. It handles objects and arrays by base64-encoding JSON and wrapping in JSON.parse(atob('...')), strings with single quotes and escapes, and other types via json_encode. Use it in Blade templates as {{ Js::from($value) }} or {{ \Illuminate\Support\Js::from($value) }}. Example: <script> var app = {{ Js::from($array) }}; </script> This ensures safe inclusion within HTML attributes or scripts, preventing XSS from unescaped JSON. Recent Laravel versions (e.g., 13.x) include a Js facade alias for convenience. There's also an undocumented @js($value) Blade directive that wraps Js::from. Official docs confirm: "The from method accepts the same arguments as PHP's json_encode function; however, it will ensure that the resulting JSON has been properly escaped for inclusion within HTML quotes." Only use with existing variables, not complex expressions.

Citations:


JS-escape interpolated values in the injected <script>.

{{ $websiteToken }} and {{ $baseUrl }} are HTML-escaped by Blade, but they're rendered inside JavaScript string literals. Values containing ', \, newline, or </script> can break out of the JS context and execute arbitrary code. Use Illuminate\Support\Js::from() (or the @js directive) to safely serialize these values for JavaScript.

🔒 Proposed fix using Js::from()
                 <script type="text/javascript">
                     (function(d,t) {
-                        var BASE_URL="{{ $baseUrl }}";
+                        var BASE_URL={!! \Illuminate\Support\Js::from($baseUrl) !!};
                         var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
-                        g.src=BASE_URL+"/packs/js/sdk.js";
+                        g.src=BASE_URL.replace(/\/$/, "")+"/packs/js/sdk.js";
                         g.defer = true;
                         g.async = true;
                         s.parentNode.insertBefore(g,s);
                         g.onload=function(){
                             window.chatwootSDK.run({
-                                websiteToken: '{{ $websiteToken }}',
+                                websiteToken: {!! \Illuminate\Support\Js::from($websiteToken) !!},
                                 baseUrl: BASE_URL
                             })
                         }
                     })(document,"script");
                 </script>
             HTML, [
-                    'baseUrl' => $baseUrl,
-                    'websiteToken' => $websiteToken,
+                    'baseUrlJs' => \Illuminate\Support\Js::from($baseUrl),
+                    'websiteTokenJs' => \Illuminate\Support\Js::from($websiteToken),
                 ])

(The replace(/\/$/, "") also defends against a trailing slash in the base URL yielding …//packs/js/sdk.js.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@chatwoot/src/Providers/ChatwootPluginProvider.php` around lines 18 - 40, The
injected script currently interpolates Blade-escaped strings directly into JS
string literals causing possible JS injection; update the Blade::render call to
serialize the values with Illuminate\Support\Js::from() (or the `@js` directive)
so baseUrl and websiteToken are safely JS-encoded before being used in the
script where window.chatwootSDK.run is called; also normalize baseUrl to remove
a trailing slash (so the computed g.src = baseUrl + "/packs/js/sdk.js" never
produces a double slash) and reference the serialized values (Js::from($baseUrl)
and Js::from($websiteToken)) instead of raw {{ $baseUrl }} / {{ $websiteToken }}
inside the Blade::render template.

@Boy132
Copy link
Copy Markdown
Member

Boy132 commented Apr 25, 2026

Thanks, but new plugins by the community won't be accepted.
You should upload your plugin to your own repo and then share it on the Discord and/or on the Hub once it's public.

@Boy132 Boy132 closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants