Custom October CMS Plugin Development
Imagine: a ready-made plugin from the marketplace doesn't offer the flexibility you need — the form doesn't collect custom fields, filtering by related models fails, or the API integration hangs. The solution is to develop a plugin tailored to your needs. Over 5 years, we have shipped more than 50 custom solutions for October CMS, and this experience allows us to build them quickly, without surprises.
How a Custom Plugin Solves Business Tasks?
A plugin in October CMS is a full Laravel package that registers components, models, routes, and backend controllers. It can replace an entire microservice: manage reviews, integrate with 1C or CRM, output custom filters. Unlike modifications via copy‑paste in the theme, a plugin is easy to update, doesn't break the core, and is reusable across projects.
Why a Bespoke Extension Beats Modifying an Existing One?
Tweaking someone else's code means risking stability. Every time the plugin updates, your modifications get overwritten. A custom plugin is written from scratch for your architecture: we control every table, every event. A bespoke extension is 3x faster to update and 2x more reliable than modifying an existing one. Compare: modifying an existing plugin takes 2–3 days, but then another week to catch bugs. A new plugin — 5 days and done, with a warranty and documentation.
What Problems Do We Solve?
Most often, clients face missing fields in forms, suboptimal SQL queries (N+1), and the inability to flexibly configure access rights. We address these issues at the design stage: use eager loading, caching via Cache::remember, and custom validators.
Plugin Development Process
Our Development Process
- Analysis — we receive your specifications or define requirements together. We pin down models, relationships, components, access rights.
- Design — we create an ER diagram, component scheme, backend navigation.
- Implementation — we write migrations, models, components, controllers, and events. Code is covered with tests (Pest/PHPUnit, 95% coverage).
- Integration — we connect the plugin to your site, configure caching, optimize queries.
- Deployment and Documentation — we deliver an archive with composer.json, README, configs, plus training for your team.
What Is Included in the Extension
- Source code under MIT license (or yours)
- composer.json with autoloading
- Migrations and seed data
- Backend controllers and navigation menu
- Frontend components (Twig/Partial)
- Events and hooks for extensibility
- Tests (if needed)
- README with installation and usage instructions
- 30 days of support after delivery
Typical Mistakes and How to Prevent Them
Common Mistakes
- N+1 queries in components — forgetting
with()andload(). We use eager loading and caching viaCache::remember. - Hard‑coded IDs in navigation — breaks on migration to another server. We build navigation automatically via
Backend::url(). - Missing validation — users input anything. We use the
Validationtrait with rules. - Ignoring events — not subscribing to
cms.page.beforeDisplay, so customizations don't fire. We always use the event model.
Real‑world example: CRM integration in 2 days. One project required syncing orders with AmoCRM. The problem was that standard plugins didn't support custom deal fields. We developed a plugin with a component that sent data via REST API when an order was created. The whole job took 2 days, tests covered 95% of the code.
Comparison: Custom Plugin vs. Modifying Existing One
| Criteria | Custom Plugin | Modifying Existing Plugin |
|---|---|---|
| Compatibility with updates | Full — code doesn't overlap | Each update requires reapplying patches |
| Development time | 3–14 days turnkey | 2–3 days for a patch, but risk of bugs |
| Flexibility | Any logic, relationships, events | Limited by plugin's structure |
| Documentation | README, code comments, training | Often absent |
| Warranty | 30 days for fixes | Depends on the author |
Code Example
Below is a minimal plugin skeleton. A complete set of files is shown using a review model as an example.
Plugin.php — Registration of components and navigation
// Plugin.php namespace MyCompany\MySite; use Backend; use System\Classes\PluginBase; class Plugin extends PluginBase { public function pluginDetails(): array { return [ 'name' => 'My Site', 'description' => 'Site-specific functionality', 'author' => 'My Company', 'icon' => 'icon-leaf', ]; } public function registerComponents(): array { return [ \MyCompany\MySite\Components\ReviewList::class => 'reviewList', \MyCompany\MySite\Components\ReviewForm::class => 'reviewForm', ]; } public function registerNavigation(): array { return [ 'mysite' => [ 'label' => 'My Site', 'url' => Backend::url('mycompany/mysite/reviews'), 'icon' => 'icon-star', 'permissions' => ['mycompany.mysite.*'], 'order' => 500, 'sideMenu' => [ 'reviews' => [ 'label' => 'Reviews', 'icon' => 'icon-comments', 'url' => Backend::url('mycompany/mysite/reviews'), 'permissions' => ['mycompany.mysite.reviews'], ], ], ], ]; } public function registerSettings(): array { return [ 'settings' => [ 'label' => 'My Site Settings', 'description' => 'Configure My Site plugin', 'icon' => 'icon-cog', 'class' => \MyCompany\MySite\Models\Settings::class, 'order' => 500, ], ]; } public function boot(): void { \Event::listen('cms.page.beforeDisplay', function ($controller, $url, $page) { // Logic before page rendering }); } } Eloquent Model
// models/Review.php namespace MyCompany\MySite\Models; use Model; class Review extends Model { use \October\Rain\Database\Traits\Validation; use \October\Rain\Database\Traits\SoftDelete; public $table = 'mycompany_mysite_reviews'; public $rules = [ 'author_name' => 'required|string|max:255', 'email' => 'required|email', 'rating' => 'required|integer|between:1,5', 'body' => 'required|string|min:10', ]; protected $fillable = ['author_name', 'email', 'rating', 'body', 'is_approved']; protected $casts = [ 'is_approved' => 'boolean', 'rating' => 'integer', ]; public $attachOne = [ 'avatar' => \System\Models\File::class, ]; public $belongsTo = [ 'product' => [\MyCompany\MySite\Models\Product::class], ]; public function scopeApproved($query) { return $query->where('is_approved', true); } public function scopeByProduct($query, int $productId) { return $query->where('product_id', $productId); } } Component
// components/ReviewList.php namespace MyCompany\MySite\Components; use Cms\Classes\ComponentBase; use MyCompany\MySite\Models\Review; class ReviewList extends ComponentBase { public function componentDetails(): array { return [ 'name' => 'Review List', 'description' => 'Displays product reviews', ]; } public function defineProperties(): array { return [ 'productId' => ['title' => 'Product ID', 'type' => 'string'], 'limit' => ['title' => 'Limit', 'type' => 'string', 'default' => '10'], ]; } public function onRun(): void { $this->page['reviews'] = Review::approved() ->byProduct((int) $this->property('productId')) ->with('avatar') ->orderBy('created_at', 'desc') ->limit((int) $this->property('limit')) ->get(); $this->page['avgRating'] = Review::approved() ->byProduct((int) $this->property('productId')) ->avg('rating'); } public function onSubmitReview(): array { $data = post(); $review = new Review($data); $review->product_id = $this->property('productId'); if (!$review->save()) { throw new \ValidationException($review); } return ['success' => true]; } } Migration
// updates/1_0_1_create_reviews_table.php use October\Rain\Database\Schema\Blueprint; use October\Rain\Database\Updates\Migration; class CreateReviewsTable extends Migration { public function up(): void { Schema::create('mycompany_mysite_reviews', function (Blueprint $table) { $table->increments('id'); $table->integer('product_id')->unsigned()->index(); $table->string('author_name'); $table->string('email'); $table->tinyInteger('rating'); $table->text('body'); $table->boolean('is_approved')->default(false); $table->timestamps(); $table->softDeletes(); }); } public function down(): void { Schema::dropIfExists('mycompany_mysite_reviews'); } } Timeline and Stages
| Stage | Duration | Result |
|---|---|---|
| Requirements analysis | 1–2 days | Spec, ER diagram |
| Design | 1–2 days | Component scheme, navigation |
| Implementation | 3–7 days | Code, tests, documentation |
| Integration | 1–2 days | Working plugin on your site |
| Deployment and handover | 1 day | composer.json, README, configs |
What's Included in the Work
- Detailed requirements document and ER diagram
- Fully functional extension with source code (MIT license)
- composer.json and autoloading configuration
- Database migrations and seed data
- Backend controllers and navigation menu
- Frontend components (Twig/Partial)
- Events and hooks for extensibility
- Comprehensive tests (Pest/PHPUnit)
- README with installation and usage instructions
- 30 days of support and bug fixes after delivery
- Optional: team training and maintenance contract
How to Order Development?
Describe your task — we'll prepare an estimate within 1 day. Contact us, and we'll help you determine which approach best fits your project. Prices start at $500 for a simple extension, and typical projects range from $500 to $2,000.
More details about plugin development can be found in the official October CMS documentation.
5+ years of experience and 50+ delivered plugins are not just numbers. Every project starts with an architectural review: we avoid common mistakes (missing database indexes, unoptimized queries, code duplication). All plugins undergo internal code review. We provide a 30‑day warranty on code — if something goes wrong, we fix it free of charge.







