FilaRank Pro by Usama Muneer
CommunitySEO toolkit for Filament with live scoring, readability analysis, SERP preview, head tag rendering, redirect management, site-wide health reports and many more.
filament/
namespace. Review the source and install at your own risk. Found
malware or an unresolved security issue the author won't
address?
Report it
.
Author:
Usama Muneer
Documentation
- Documentation Index
- Features
- Requirements
- Installation
- Usage
- AI features
- Sitemap
- Technical audit
- Keyword research & rank tracking
- Backlinks & competitors
- Configuration
- Security notes
- Writing your own check
- Cross-record reports
- Styling
- Testing
- Roadmap ideas
- License
- Support
#Documentation Index
Fetch the complete documentation index at: https://docs.filarank.com/llms.txt Use this file to discover all available pages before exploring further.
SEO toolkit for Filament v5: live scoring, GEO/AEO, AI fixes, sitemap indexes, robots.txt, IndexNow, and head tags. Single-file overview for the Filament plugin directory.
SEO toolkit for Filament v5: live content scoring, readability and GEO/AEO analysis, SERP preview, head-tag rendering, sitemap indexes, robots.txt / llms.txt, IndexNow, redirect management, AI fix suggestions, keyword research, rank tracking, and site-wide health reports.
- Live demo: filarank.com
- Full docs: docs.filarank.com
- Checkout: Anystack
#Features
- 26 analysis checks across three score groups:
- SEO (13), title length, keyword in title / meta description / slug / first paragraph / subheadings, keyword density with stuffing detection, content length, cornerstone depth, internal links, outbound links, image alt text.
- Readability (7), Flesch Reading Ease, sentence length distribution, paragraph length, passive voice, transition words, subheading distribution, repeated consecutive sentence openings.
- GEO / AEO (6), direct-answer blocks, FAQ structure, entity clarity, citation signals, snippet-length meta, structured-data readiness.
- Live analysis in the form, results re-render as the editor types (debounced), with traffic-light indicators and an overall 0–100 score.
- AI Fix, one-click title / meta / slug suggestions with a before/after diff. Bring your own OpenAI, Gemini, Anthropic, Mistral, or DeepSeek key.
- Google SERP snippet preview plus Facebook and X social previews, right where the editor needs it.
SeoScoreColumn, a sortable red/amber/green badge for any Filament table.- Persisted scores, recalculated whenever the parent model is saved, so table badges never need a live re-analysis.
<x-filarank::tags />, one component that renders<title>, meta description, robots, canonical, Open Graph, Twitter Cards, hreflang, and multi-type JSON-LD (@graph) with site-wide fallbacks.<x-filarank::breadcrumbs />, a visible trail that matches BreadcrumbList JSON-LD.- XML sitemap index at
/sitemap.xml(per-model changefreq/priority, image sitemap, HTML sitemap)./filarank-sitemap.xml301s to the new path. - Crawl files, a panel editor for
robots.txt(with revisions and AI-bot presets),llms.txt/llms-full.txt, and IndexNow (key file + submit on save). - Admin dashboards, SEO Overview, SEO Health, Keyword Research, Rank Tracker, Backlinks, Competitors, Redirects, SEO Files, Instant Indexing, Bulk SEO, Setup wizard, and Settings. Every page is individually toggleable.
- Framework-free analysis engine,
src/Analysisandsrc/Supporthave zero Laravel dependencies, so the engine is trivially unit-testable and reusable outside Filament. - Interactive installer,
php artisan filarank:installpublishes assets, migrates, and wires the trait, form section, and score column into the models and resources you pick, with a--dry-runmode. - Fully translatable, publishable views, configurable checks.
New in v3.0: crawl files, sitemap indexes, title templates, social overrides, an extensible schema registry, bulk SEO, IndexNow, and FR/DE/ES readability packs. See Upgrade to v3 if you already have FilaRank installed.
#Requirements
- PHP 8.3+
- Laravel 11 / 12 / 13
- Filament v5
Optional: laravel/head (Laravel 13.17+) if you would rather render tags through Laravel's first-party @head directive. See Using Laravel Head.
#Installation
composer require usamamuneerchaudhary/filarank
php artisan filarank:install
The interactive installer publishes the config and migrations, offers to run migrate, then shows a checklist of your app/Models classes. For each model you pick it will:
- add the
HasSeotrait (plus import) to the model, - find the matching Filament resource anywhere under
app/Filament, and, with your confirmation, appendSeoFields::make()to the form andSeoScoreColumn::make()to the table, with imports.
Patching is deliberately conservative: files are only modified when the expected structure is found, every edit is idempotent (safe to re-run), and anything that can't be patched automatically falls back to printed manual instructions. Use --dry-run to preview without writing:
php artisan filarank:install --dry-run
Prefer to wire things up by hand? The manual steps below are exactly what the installer automates:
php artisan vendor:publish --tag=filarank-migrations
php artisan migrate
Optionally publish the config, views, or translations:
php artisan vendor:publish --tag=filarank-config
php artisan vendor:publish --tag=filarank-views
php artisan vendor:publish --tag=filarank-translations
#Register the panel plugin
The admin pages live behind a plugin so you only get the ones you want:
use Usamamuneerchaudhary\FilaRank\FilaRankPlugin;
public function panel(Panel $panel): Panel
{
return $panel->plugin(FilaRankPlugin::make());
}
Every page defaults to on and can be switched off individually:
FilaRankPlugin::make()
->overview() // SEO Overview dashboard
->healthDashboard() // 404s, cannibalization, orphans, technical audit
->keywordResearch()
->rankTracker()
->backlinks(false) // hide the pages you don't need
->competitors(false)
->settings()
->redirects()
->files() // robots.txt + llms.txt
->indexing() // IndexNow
->bulkEditor()
->setupWizard()
#Usage
#1. Prepare your model
use Usamamuneerchaudhary\FilaRank\Concerns\HasSeo;
class Post extends Model
{
use HasSeo;
}
By default the analyzer reads the model's content and slug attributes and falls back to title for the page title. Override if your columns differ:
public function getSeoContent(): ?string
{
return $this->body;
}
public function getSeoSlug(): ?string
{
return $this->permalink;
}
/** Public URL, used by the sitemap, link graph, and technical audit. */
public function getSeoUrl(): ?string
{
return route('posts.show', $this, absolute: false);
}
#2. Add the SEO section to your Filament form
use Usamamuneerchaudhary\FilaRank\Forms\SeoFields;
public static function form(Schema $schema): Schema
{
return $schema->components([
TextInput::make('title'),
TextInput::make('slug'),
RichEditor::make('content'),
SeoFields::make(), // assumes `content` + `slug` fields on this form
]);
}
If your body field has a different name, or lives somewhere unusual in the schema tree:
SeoFields::make(contentField: 'body', slugField: 'permalink');
// or take full control of where values are read from and written to:
SeoFields::make(
getContentUsing: fn (Get $get) => $get('../body'),
getSlugUsing: fn (Get $get) => $get('../permalink'),
setSlugUsing: fn (string $value, Set $set) => $set('../permalink', $value),
);
SeoFields::make()binds to theseorelationship, so it must be used on a form with a record model that usesHasSeo.
#3. Show the score in your table
use Usamamuneerchaudhary\FilaRank\Tables\SeoScoreColumn;
public static function table(Table $table): Table
{
return $table->columns([
TextColumn::make('title'),
SeoScoreColumn::make(),
]);
}
Scores are recalculated and stored whenever the model is saved (disable with 'persist_score' => false).
#4. Render the tags on your frontend
In your layout's <head>:
<x-filarank::tags :model="$post" />
<x-filarank::breadcrumbs :model="$post" />
Or for static pages without a model:
<x-filarank::tags title="Contact us" description="Get in touch with our team." />
#Using Laravel Head
Laravel 13.17 shipped laravel/head, a first-party way to manage the document <head>. FilaRank works with it, so you don't have to choose between the panel you're editing SEO in and the head management the rest of your app uses.
It stays optional. FilaRank supports Laravel 11 and 12, laravel/head requires 13.17+, so it is a suggest rather than a require. Nothing changes if you don't install it.
composer require laravel/head
Then push the record's stored meta before the view renders, instead of placing the component:
use Usamamuneerchaudhary\FilaRank\SeoHead;
public function show(Post $post): View
{
SeoHead::for($post);
return view('posts.show', ['post' => $post]);
}
Static pages take the same overrides as the component:
SeoHead::for(null, title: 'Contact us', description: 'Get in touch with our team.');
Keep Laravel Head's @head directive in your layout and drop <x-filarank::tags />. Render one or the other, never both, or every page gets two titles and two JSON-LD blocks.
Both renderers read the same resolution chain (explicit argument, then the stored SEO row, then your model's fallbacks, then site config) and emit the same title, description, canonical, robots, Open Graph, Twitter card, and JSON-LD graph. Two deliberate differences:
- Laravel Head normalizes canonical URLs, forcing
httpsand giving a root path its trailing slash. The component emits whatever you stored, verbatim. - FilaRank's JSON-LD is scrubbed recursively before handover, because Laravel Head rejects null and empty-string values at any depth. This mostly matters if your model's
getProductSchema()returns partially-filled nested nodes.
SeoHead::for() throws if laravel/head isn't installed. Guard with SeoHead::isAvailable() if you ship code that has to run either way.
#5. Analyze anything programmatically
use Usamamuneerchaudhary\FilaRank\SeoAnalyzer;
$report = SeoAnalyzer::analyze([
'title' => 'Coffee Beans Guide',
'description' => '…',
'focus_keyword' => 'coffee beans',
'slug' => 'coffee-beans',
'content' => $html,
]);
$report->score(); // 0–100 or null
$report->seoScore();
$report->readabilityScore();
$report->geoScore();
$report->rating(); // Status::Good | Ok | Bad
$report->results; // list of CheckResult
Or against a model directly: SeoAnalyzer::analyzeModel($post).
#AI features
FilaRank is bring-your-own-key. Add a key under FilaRank SEO → SEO Settings (stored encrypted via Crypt), or fall back to env: OPENAI_API_KEY, GEMINI_API_KEY, ANTHROPIC_API_KEY.
| Feature | Where | What it does |
|---|---|---|
| Fix with AI | SEO section header, per record | Suggests title, meta description, and slug, shown as a before/after diff before anything is written |
| Audit fix plan | SEO Health → Technical audit | Turns an audit issue into a numbered remediation plan |
| Keyword enrichment | Keyword Research | Adds intent, difficulty, and volume estimates to a seed keyword |
| Competitor gaps | Competitors | Suggests keywords a competitor likely targets that you don't |
| Backlink toxicity | Backlinks | Scores a link 0–100 for spamminess |
Every call is logged to filarank_ai_logs with provider, model, token counts, and estimated cost, surfaced as a monthly spend figure on the Overview dashboard. Nothing runs unless a key is configured, AI actions hide themselves when AiService::isConfigured() is false.
#Choosing a model
The model dropdown in SEO Settings is built from config, and that list is the allowlist, add a model and it becomes selectable, with no code change and nothing hardcoded on the package side:
'ai' => [
'providers' => [
'openai' => [
'api_key' => env('OPENAI_API_KEY'),
'models' => ['gpt-4o-mini', 'gpt-4o', 'your-new-model'],
'rates' => [
'default' => ['in' => 0.15, 'out' => 0.60],
'gpt-4o' => ['in' => 2.50, 'out' => 10.00],
],
],
],
],
The first entry is the default when nothing has been chosen, so put your preferred model first. A saved selection is validated against the list on every read, so removing a model from config quietly falls back to that first entry rather than erroring.
rates are USD per 1M tokens and drive the spend estimate. Key a rate by model name to price it exactly; anything without its own entry uses default. Getting this wrong only skews the dashboard figure, never a request.
Two caveats when adding a model. Requests are built per provider, not per model, the OpenAI provider sends temperature and a JSON response format, so reasoning-style models that reject a custom temperature will fail with a 400. And because publishing the config replaces the package's ai block wholesale (mergeConfigFrom merges only top-level keys), a published copy won't inherit providers added in later versions. Either way, Test AI connection in Settings does a real round trip and surfaces the problem immediately.
To use a provider directly, or in tests:
use Usamamuneerchaudhary\FilaRank\Ai\AiService;
use Usamamuneerchaudhary\FilaRank\Ai\Providers\FakeProvider;
$suggestion = app(AiService::class)->suggestSeoFix([
'title' => $post->seo->title,
'description' => $post->seo->description,
'focus_keyword' => 'coffee beans',
'slug' => $post->slug,
'content' => $post->content,
]);
// Deterministic output for tests (local/testing environments only):
(new AiService)->useProvider(new FakeProvider)->suggestSeoFix([...]);
All provider failures are wrapped in AiRequestException with credentials stripped out, so a bad key or rate limit never leaks a secret into your logs or a notification.
#Sitemap
Enable the sitemap in SEO Settings; while it's off, the route returns 404 rather than an empty document. Entries are built from your scanned models, skipping records flagged noindex or without a public URL.
'sitemap' => [
'enabled' => true,
'path' => 'sitemap.xml',
'legacy_path' => 'filarank-sitemap.xml',
'index' => true,
'images' => true,
'html' => true,
'changefreq' => 'weekly',
'priority' => 0.5,
'cache_seconds' => 3600,
'ping' => false,
'ping_endpoints' => [
'https://www.bing.com/ping?sitemap={sitemap}',
],
],
With pinging enabled, endpoints are called after a record is saved. Requests are deferred until after the response, so a slow or dead endpoint never delays a content save. The sitemap placeholder in each ping URL is replaced with the URL-encoded sitemap URL. Google retired its ping endpoint in 2023, so the list is deliberately easy to repoint.
#Technical audit
SEO Health → Technical audit crawls the public URL of every record with SEO meta and reports missing titles, meta descriptions, canonicals, OG images, and focus keywords, plus broken status codes, absent JSON-LD, and images without alt text.
The crawler only fetches URLs on your own app.url host. Extra hosts must be allow-listed and must resolve to public addresses, which stops a careless getSeoUrl() from pointing the crawler at internal services or cloud metadata endpoints. Blocked URLs are recorded as an unsafe_url issue so the meta checks still run and you can see why the fetch was skipped.
'audit' => [
'allowed_hosts' => ['docs.example.com'],
'max_pages' => 500,
'delay_ms' => 0, // throttle between requests
],
Runs are bounded by max_pages and atomic: issues are collected in memory and swapped in one transaction, so a crawl that dies halfway leaves your existing issue list intact.
#Keyword research & rank tracking
Research pulls Google Autocomplete suggestions plus modifier expansions for a seed keyword, saves them as untracked rows, and lets you promote the ones you care about to tracked keywords. The table supports search, sorting, filtering by tracked state, and bulk track/dismiss.
use Usamamuneerchaudhary\FilaRank\Keywords\KeywordStore;
$store = app(KeywordStore::class);
$store->saveResearched(['coffee beans', 'best coffee beans']);
$store->track('coffee beans'); // never clears existing enrichment
$store->dismiss([$id]); // untracked rows only; tracked keywords are safe
Tracked keywords get daily position checks. SerpAPI is the supported provider, add the key in SEO Settings:
php artisan filarank:check-ranks
The command is scheduled automatically (rank_tracker.schedule_at, default 03:00) and exits cleanly with a warning when no key is configured, so an unconfigured install doesn't fail every night. Scraping Google breaks their terms of service and stops working without warning, so it's disabled unless you opt in with FILARANK_ALLOW_SCRAPE=true; until then the option is hidden from Settings.
Research rows accumulate, so prune them when you're done:
php artisan filarank:prune-keywords --days=30 # dry run, lists what would go
php artisan filarank:prune-keywords --days=30 --force # actually delete
Tracked keywords are never pruned, they hold your rank history.
#Backlinks & competitors
Both are deliberately simple: you own the data, FilaRank enriches it.
Backlinks imports a CSV export from Ahrefs, Semrush, or Search Console. Headers are matched flexibly (source_url/url/referring page, dr/domain_rating, and so on), and rows are matched on source + target URL, so re-importing a refreshed export updates rows instead of duplicating them. Optional AI toxicity scoring runs 25 unscored links at a time.
Competitors is a domain list with an "Analyse gap" action that asks the model which keywords a competitor likely targets that you don't. These are informed estimates from the domain and your own keyword list, not crawled ranking data, treat them as research prompts, not measurements.
#Configuration
See config/filarank.php for site-wide defaults (site name, title separator, default description / share image, Twitter handle), tag-rendering toggles, schema types, disabled checks, and score persistence.
Disable individual checks:
'analysis' => [
'disabled_checks' => ['outbound-links', 'passive-voice'],
],
Choose which JSON-LD nodes are emitted (also toggleable from SEO Settings):
'render' => [
'schema_types' => [
'organization' => true,
'website' => true,
'breadcrumb' => true,
'article' => true,
'faq' => true,
'product' => false,
],
],
List the models FilaRank should scan for the sitemap, audit, cannibalization, and link graph:
'scanned_models' => [
\App\Models\Post::class => ['label' => 'title'],
\App\Models\Page::class => ['label' => 'name'],
],
Leave it empty and FilaRank falls back to any model that already has SEO meta rows, so the dashboards aren't blank on a fresh install.
#Security notes
- API keys are encrypted at rest and shown masked in the UI. Paste a new value to replace one; leave the field blank to keep it.
- Provider errors pass through
Support\Redactorbefore being logged or displayed, stripping query-string credentials, bearer tokens, and known key formats. - The audit crawler is host-restricted (see Technical audit) and refuses redirects to untrusted hosts mid-request.
- The fake AI provider is refused outside
localandtesting, so a misconfigured production panel can't silently serve canned copy. - Google scraping is off by default and must be enabled explicitly in config.
#Writing your own check
Implement the Check contract and register it:
use Usamamuneerchaudhary\FilaRank\Analysis\Contracts\Check;
use Usamamuneerchaudhary\FilaRank\Analysis\{CheckResult, ContentContext, Status};
final class NoClickbaitTitle implements Check
{
public function id(): string { return 'no-clickbait'; }
public function group(): string { return 'seo'; }
public function isApplicable(ContentContext $context): bool
{
return filled($context->title);
}
public function run(ContentContext $context): CheckResult
{
$clickbait = str_contains(mb_strtolower($context->title), 'you won\'t believe');
return new CheckResult(
$this->id(),
$this->group(),
$clickbait ? Status::Bad : Status::Good,
$clickbait ? 'Avoid clickbait phrasing in titles.' : 'Title looks trustworthy.',
);
}
}
$report = SeoAnalyzer::analyzer()
->withCheck(new NoClickbaitTitle())
->analyze($context);
Groups are seo, readability, and geo; anything else is ignored by the score panels.
#Cross-record reports
All the cross-record logic lives in framework-free, unit-tested engines (src/Cannibalization, src/Linking, src/Redirects, src/Language, src/Keyphrase).
#Redirect manager (404 capture + one-click 301s)
Register the middleware in bootstrap/app.php:
use Usamamuneerchaudhary\FilaRank\Http\Middleware\HandleRedirects;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [HandleRedirects::class]);
})
The middleware serves matching redirects (with normalisation, chain-following A→B→C, and loop protection) and logs every 404. Logged 404s appear on the SEO Health page with a one-click "Create redirect" link that pre-fills the source path. Rules are cached for an hour and the cache is busted automatically on create/edit/delete.
Slug changes on published records create a 301 automatically. Reverting a slug clears the reverse rule, so renaming back and forth can't leave a redirect loop behind. Configure with:
'redirects' => [
'auto_on_slug_change' => true,
'only_when_published' => true,
],
#Internal linking suggestions & orphaned content
The SEO Health dashboard reports orphaned content (pages nothing links to) and under-linked cornerstones. The link graph compares URLs by normalised path, so absolute and relative links to the same page match. Programmatic access:
$graph = app(\Usamamuneerchaudhary\FilaRank\Reports\SeoReports::class)->linkGraph();
$graph->orphans();
$graph->suggestionsFor($record); // keyphrase-based, cornerstone-boosted
$graph->underlinkedCornerstones(3);
#Keyword cannibalization warnings
Any keyphrase targeted by more than one record is flagged, sorted most-contested first. Keywords are normalised (case- and slug-insensitive), so "Coffee Beans", "coffee beans", and "coffee-beans" collide. Includes each record's primary and related keyphrases.
#Cornerstone content flagging
A Cornerstone content toggle in the SEO section marks your most important pages. Cornerstone records are held to a higher bar by the cornerstone-depth check (≥900 words, properly subheaded) and flagged on the dashboard when too few internal links point to them (config('filarank.cornerstone.min_incoming_links')).
#Multiple focus keywords
A Related keyphrases tags field lets each record target secondary phrases. The primary keyword gets the full analysis; each related keyphrase gets the keyword-specific SEO checks (title / meta / slug / first paragraph / density / subheadings), shown in the analysis panel. Duplicates of the primary are de-duplicated automatically.
use Usamamuneerchaudhary\FilaRank\Keyphrase\KeyphraseAnalyzer;
$results = (new KeyphraseAnalyzer())->analyze($context); // primary first, then related
#Per-language readability packs
Readability heuristics (transition words, passive-voice detection, syllable counting, reading-ease formula) sit behind a LanguagePack interface. English, Dutch, French, German, and Spanish ship in the box. The content-language selector is hidden unless FILARANK_MULTILINGUAL=true. Add your own:
use Usamamuneerchaudhary\FilaRank\Language\LanguageRegistry;
app(LanguageRegistry::class)->register(new FrenchPack());
Extend AbstractLanguagePack and implement transitionWords(), countSyllables(), and the passive-voice word lists, see EnglishPack and DutchPack for the pattern.
#Styling
The panel UI is built from native Filament components plus hand-written filarank-* classes layered on Filament's design tokens, inlined once per process. There is no Tailwind build step and no viteTheme requirement, the plugin looks right in any panel, light or dark, out of the box.
#Testing
composer test # Pest (Laravel integration, requires composer install)
composer test:standalone # both engine suites, zero dependencies, just PHP
#Roadmap ideas
- AI content writer (draft full articles from keyword research)
- More schema types from the registry
- Deeper GSC CSV reports beyond the bulk importer
#License
Commercial. One license covers one production project (its development and staging environments included). Purchase at Anystack.
#Support
Email hello@usamamuneer.me.
Full documentation: docs.filarank.com
This documentation is built and hosted on Mintlify, a developer documentation platform.
The author
Coder, Blogger, Tech Speaker & Web Technologies Enthusiast. Passionate about working on open-source Programming languages & Tools while utilizing my Product Development skills.
From the same author
Spatie Model States Visualizer
State-aware Kanban board and status timeline components for Filament v5. Filament Model States provides a plug and play auto-detection for enums, Spatie Model States, or plain database values. Built to sit on top of: spatie/laravel-model-status > powers the timeline (status history log) spatie/laravel-model-states > powers transition validation and column discovery on the Kanban board (optional but recommended)
Author:
Usama Muneer
Adment
A simple custom ad & Google AdSense manager for Filament v5 with placements, responsive (and GIF/video) creatives, weighted A/B rotation, scheduling windows, geo/device targeting, impression & CTR analytics, obfuscated click tracking, AdSense Auto Ads & ad units, ads.txt management, and an optional public JSON API.
Author:
Usama Muneer
Command Palette
A Spotlight/CMD+K style command palette for quick navigation and actions across Filament panels.
Author:
Usama Muneer
Notifier
A powerful notification system that handles multi-channel notifications with template management, scheduling, and real-time delivery. Built for developers who need enterprise-grade notifications without the complexity.
Author:
Usama Muneer
Featured Plugins
A selection of plugins curated by the Filament team
Compact Theme
A theme that makes data-heavy panels easier to scan by fitting more useful information on each screen.
Filament
Spotlight Pro
Browse your Filament Panel with ease. Filament Spotlight Pro adds a Spotlight like Command Palette to your Filament Panel.
Dennis Koch
Custom Dashboards
Let your users build and share their own dashboards with a drag-and-drop interface. Define your data sources in PHP and let them do the rest.
Filament