First-class Firebase Security Rules support for JetBrains IDEs — Cloud Firestore and Cloud Storage.
Syntax highlighting, formatting, diagnostics, and symbol intelligence for .rules files —
so the IDE treats them as their own language instead of JavaScript, JSON, or plain text.
Why • What It Does • Compare • Install • Contributing • Spec
Firebase Security Rules — for both Cloud Firestore and Cloud Storage — are a real
language with their own grammar, scoping, and built-ins, but no JetBrains IDE
knows that. Open a .rules file and it is
treated as JavaScript, JSON, or plain text: no highlighting that understands
match and allow, no rules-aware formatting, no go-to-definition for your
helper functions, and no warning when a rule can never parse.
There is currently no freely available solution that fills this gap. HotRulez exists to fill that hole: a free, open-source plugin that makes Firebase Rules a first-class language in the IDE — entirely structural and static, never evaluating authorization or connecting to Firebase.
It is released under the MIT License and created by lezli01 at
lezli01.is-a.dev. Contributions are welcome — see
Contributing.
Open any .rules file and the IDE treats it as a first-class Firebase Rules
language:
- File recognition — every
*.rulesfile is recognized as Firebase Rules (by extension) and shown with a dedicated file icon. - Syntax highlighting — keywords, allow operations, built-ins, helpers, types, service names, function declarations and calls, path variables, recursive wildcards, strings, numbers, comments, operators, and invalid tokens, all separately colorable.
- Formatting — one reformat turns compact or messy rules into clean, consistently indented output while preserving your comments, blank lines, and multiline conditions.
- Diagnostics & quick-fixes — checks flag always-wrong constructs as errors and surface suspicious-but-legal structure, unresolved/unused symbols, and unknown built-in members as configurable warnings; most come with a one-keystroke fix (Alt+Enter), and the wording stays structural (it never claims a rule is "secure").
- Symbol intelligence — go to declaration, find usages, rename refactoring,
and scope-aware code completion for functions, parameters,
letbindings, and path variables, all built on a PSI resolve layer that honors Firebase Rules scoping and path-variable shadowing. - Authoring aids — a structure-view outline of services, matches, functions,
and
allowrules; code folding for block and comment regions; quick documentation (Ctrl+Q) with doc-grounded prose and an external Firebase link; and parameter info (Ctrl+P) for function and path-helper calls. - Editor conveniences — brace matching, quote auto-closing, and line/block comment toggling.
Everything is structural and static. The plugin never evaluates authorization or talks to Firebase; keep using Firebase's official tooling for deployment and authorization testing.
The highlighter exposes 24 categories, including semantic ones — service name, function declaration, and path variable — that a fast lexer alone cannot distinguish and that are resolved by a lightweight annotator. Every category is customizable with a live preview under Settings | Editor | Color Scheme | Firebase Rules.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Signed-in owners can read and write their own city
function isOwner(uid) {
return request.auth != null && resource.data.owner == uid;
}
match /cities/{city} {
allow read, write: if isOwner(request.auth.uid);
}
match /scores/{score} {
allow read: if resource.data.value is int
? resource.data.value >= 100
: resource.data.tier in ['gold', 'silver'];
}
match /logs/{document=**} {
allow read: if false;
}
}
}
Invoke the IDE's normal Reformat Code action on a .rules file. The
formatter uses IntelliJ's formatting APIs over the parsed PSI, so it makes
minimal whitespace changes — it never reorders rules or rewrites your
expressions.
Expanding a compact file. A whole document collapsed onto one line is expanded and indented, with a blank line inserted between block members such as a function declaration:
Before:
rules_version='2';service cloud.firestore{match /databases/{database}/documents{match /cities/{city}{allow read,write: if request.auth!=null;function ownsCity(uid){return resource.data.owner==uid;}}}}
After:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
allow read, write: if request.auth != null;
function ownsCity(uid) {
return resource.data.owner == uid;
}
}
}
}
Preserving multiline conditions. A condition you intentionally split across
lines stays split — the formatter indents the surrounding blocks without
collapsing the && continuation onto one line:
Before:
service cloud.firestore{match /databases/{database}/documents{allow read: if request.auth != null
&& request.auth.uid == resource.data.owner;}}
After:
service cloud.firestore {
match /databases/{database}/documents {
allow read: if request.auth != null
&& request.auth.uid == resource.data.owner;
}
}
Keeping comments in place. Both line (//) and block (/* */) comments are
preserved and reindented to align with the block they precede:
Before:
service cloud.firestore{/* service body */match /databases/{database}/documents{// city access
match /cities/{city}{allow read: if true;}}}
After:
service cloud.firestore {
/* service body */
match /databases/{database}/documents {
// city access
match /cities/{city} {
allow read: if true;
}
}
}
Path variables and recursive wildcards (/cities/{city}/{document=**}) keep
their tight spacing, and malformed-but-recoverable input is formatted block by
block rather than left untouched.
Severity decides the home. Always-wrong, grammar-inexpressible mistakes are reported as errors by an always-on annotator. Configurable warnings for file shape, suspicious usage, unresolved/unused symbols, and unknown built-in members live in four inspections you can tune under Settings | Editor | Inspections | Firebase Rules. Most diagnostics — errors and warnings alike — carry an Alt+Enter quick-fix. None of the wording asserts that a rule is secure or that a request would be authorized.
- Empty operation list —
'allow' requires at least one operation, for example 'allow read'. - Unknown operation —
allow read, fetch: …→Unknown Firebase Rules operation 'fetch'. Expected one of: get, list, read, create, update, delete, write. - Duplicate parameter name —
function f(uid, uid)→Duplicate parameter name 'uid'. - Function missing its return —
Function 'helper' must end with a 'return' statement. - Bare return —
return;→'return' requires an expression. - Too many recursive wildcards —
A match path may contain at most one recursive wildcard '{name=**}'. - Misplaced recursive wildcard (v1) —
In rules_version '1', a recursive wildcard '{name=**}' must be the last segment of a match path.
For example, the unknown operation here is reported on the fetch token:
match /cities/{city} {
allow read, fetch: if true; // error: Unknown Firebase Rules operation 'fetch'.
}
- Missing rules version —
Missing 'rules_version = '2';' declaration at the top of the file. - Non-v2 rules version —
rules_version = '1';→Expected 'rules_version = '2';' declaration at the top of the file; found version '1'. - Version after service —
'rules_version' must be declared before the 'service' block. - Missing service —
Missing 'service cloud.firestore { ... }' or 'service firebase.storage { ... }' block. - Unknown service name — both
cloud.firestoreandfirebase.storageare recognized; an unrecognized service such asservice firebase.database { … }is a soft warning (Expected 'service cloud.firestore' or 'service firebase.storage'; found 'service firebase.database'.) and the service-specific checks below are skipped. - Service missing its block —
'service cloud.firestore' is missing its rule block '{ ... }'. - Missing root match — the conventional root for the detected service:
Missing root 'match /databases/{database}/documents' block inside 'service cloud.firestore'.(Firestore) orMissing root 'match /b/{bucket}/o' block inside 'service firebase.storage'.(Cloud Storage). - Multiple services —
A rules file may declare only one 'service'; Firebase rejects multiple 'service' blocks per file.(Firestore and Cloud Storage rules belong in separate files.)
- Condition-less allow —
allow read;→'allow' rule has no 'if' condition. Add ': if <condition>' to restrict when the operation is permitted.A condition-lessallowis legal (it unconditionally grants the operation), so this is an opt-in nudge rather than an error. - Recursive wildcard without v2 —
Recursive wildcard '{document=**}' should be used with rules_version = '2'; its match semantics differ between rules versions. - Helper-call arity —
exists()→'exists()' takes exactly one argument but found 0.Applies toget,getAfter,exists, andexistsAfter.
A resolver-based inspection — the same scope resolution that powers go-to-definition and rename — flags symbols that don't line up, without ever evaluating authorization:
- Unresolved reference — a name resolving to no function, parameter,
let, or path variable that also isn't a built-in →Cannot resolve symbol 'isOwner'.Members (request.auth), path variables, and built-ins/helpers are never flagged. - Unused function /
let— a declaration nothing references →Function 'isOwner' is never used./Variable 'tmp' is never used. - Unused parameter — a parameter never read in its body →
Parameter 'uid' is never used.(reported only; removing it would change every call site).
A conservative, service-aware semantic check — the plugin's first that reasons about whether a member can exist on a receiver, not just whether a symbol resolves — reported as a weak warning (on by default):
- Unknown member on a closed built-in — accessing a member that a dialect's
fixed, doc-sourced set does not define on
request,resource,request.auth,request.resource, or (Firestore)request.query→'foo' is not a member of 'request'.A close typo offers a Change to 'X' rename (request.resourse→resource); a member that belongs to the other service gets a dialect-aware message instead —resource.sizein a Firestore file ('size' is a Cloud Storage member; …),request.methodin a Storage file. - It never fires on an open namespace — user document data
(
resource.data.*), custom auth claims (request.auth.token.*, including the MFA/SAML.firebase.*members), Cloud Storage object metadata (resource.metadata.*), or request params (request.params.*) — nor on a neutral (no-service) file, nor on a variable that merely shares a built-in's name. It infers no types.
Most diagnostics offer an Alt+Enter fix that edits the file for you:
- Insert or correct
rules_version = '2';, or move it above theserviceblock. - Change an unknown service to
cloud.firestoreorfirebase.storage; scaffold a missingserviceblock or rootmatch. - Replace an unknown operation with the closest real one, remove a duplicate parameter, or add a missing
return false;. - Add a placeholder
: if <condition>to a condition-lessallow. - Create a missing function for an unresolved call, or remove an unused function or
let. - Rename an unknown member to a near match on its receiver (Change to 'resource').
The "no automatic fix" cases are deliberate: an empty operation list, a bare
return;, multiple service blocks, and helper-call arity have no unambiguous
repair, so they report without one.
.rules files get the symbol features you expect from a first-class language,
all built on a PSI reference/resolve layer that links a use of a name to its
declaration using Firestore's real scoping rules — never by evaluating
authorization or connecting to Firebase.
Covered symbols: functions, function parameters, let bindings, and
path / wildcard variables ({city}, {document=**}).
- Go to declaration — Ctrl+click (or Go to Declaration) on a function call
jumps to its
function, on a path-variable use to its binding wildcard, and on a parameter orletuse to its binding. Calls resolve across service / match scope, including forward references and a call to a function declared in an enclosing scope. - Find usages — from any function, parameter,
let, or path variable, with scoping respected: a path variable's usages stop at a nestedmatchthat re-declares the same name. - Rename — renaming a function updates its declaration and every call site;
renaming a parameter or
letupdates its in-body uses; renaming a path variable updates only the correct match subtree and leaves a shadowing inner binding (and its uses) untouched. New names are validated as legal identifiers, and reserved keywords are rejected. - Code completion — context aware:
- operation names after
allow(get,list,read,create,update,delete,write); - structural keywords (
match,allow,function,return,let,if), withrules_versionandserviceat the top level; cloud.firestoreandfirebase.storageafterservice;- in expression position, the symbols visible at the caret — scope-aware, so
parameters are offered only inside their function, path variables only within
their subtree, and a
letonly after its declaration — plus the built-insrequestandresource, the literalstrue/false/null, and the detected service's path helpers (Cloud Firestore'sget/exists/getAfter/existsAfter, or Cloud Storage's cross-servicefirestore.get/firestore.exists); - shallow members after
request.,request.auth.,request.resource., andresource., drawn from a fixed, service-aware table built from the official Firebase docs — Firestore offersresource.data/id/__name__, while Cloud Storage offers the object metadata (resource.size,resource.contentType,resource.metadata, …).
- operation names after
Built-in variables and helpers are recognized (so they are never mistaken for
undefined names) but non-navigable — there is no declaration to jump to.
Member completion is a fixed, documented list, not type inference: the plugin
never infers the type of an expression or invents custom request.auth.token
claims.
Beyond editing, HotRulez helps you read and navigate a .rules file the way a
first-class language plugin does — all read-only projections of the parsed
structure, adding no new semantics.
- Structure view — the Structure tool window (Alt+7) and the navigation
bar show a
service → match → function / allowoutline, labeled with the service name, each match path,fn(params), andallow read, write. It offers alphabetical sorting (source order by default) and "Select in Structure View" caret sync, and degrades to a partial tree on a half-typed file.let/returnstatements and path variables are intentionally not surfaced. - Code folding — service, match, and function braced bodies fold to
{…}with the head (service name / match path / function signature) staying visible, and multi-line block comments fold to/*…*/. Nothing is collapsed by default, and an unclosed or empty block never produces a bogus fold. - Quick documentation — hover or press Ctrl+Q on an
allowoperation, a built-in (request,resource,firestore), arequest./resource.member, a path helper (get/exists/getAfter/existsAfter, or Storage'sfirestore.get/firestore.exists), or a type/namespace to see a short, doc-grounded summary plus an "open in browser" link to the official Firebase reference. Your own functions show their signature plus the comment above them; parameters,letbindings, and path variables show what they bind. Member docs are dialect-aware (a Storage-only member shows nothing in a Firestore file) and never fabricated — an unknown member or unresolved name shows nothing. - Parameter info — press Ctrl+P inside a call to see the signature and the highlighted current argument for your own functions and for the fixed-arity path helpers. It is a display aid only: it never validates argument count.
- Brace matching for
{},(), and[]. Curly braces are structural, so the IDE highlights the matching}for aservice,match, orfunctionblock and the Move Caret to Matching Brace action jumps between them. - Quote handling — typing
'or"auto-inserts the closing quote and types over an existing one. - Comment toggling — Comment with Line Comment inserts
//, and Comment with Block Comment wraps a selection in/* */.
HotRulez is not the only Firebase-related plugin on the JetBrains Marketplace, but
it is the only free, open-source one that treats the Firebase Security Rules
language as a first-class citizen. The table below compares the plugins that
touch the .rules DSL (Cloud Firestore and Cloud Storage), on rules-authoring
features only. For the full breakdown, sources, and caveats, see
docs/comparism.md.
| HotRulez | Firebase Rules | Firebase Pro | |
|---|---|---|---|
| Price | Free (MIT) | Paid | Paid |
| Open source | ✅ | ✅ | ❌ |
Handles the .rules language |
✅ | ✅ | ✅ |
| Syntax highlighting & formatting | ✅ | ✅ | ✅ |
| Structure view | ✅ | ✅ | ✅ |
| Scope-aware completion (symbols + members) | ✅ | ❔ | ✅ |
| Go to declaration / find usages | ✅ | 🟡 | ✅ |
| Rename refactoring | ✅ | ❔ | ❔ |
| Diagnostics + quick-fixes | ✅ | 🟡 | ✅ |
| Quick documentation (Ctrl+Q) | ✅ | ❔ | ✅ |
| Parameter info (Ctrl+P) | ✅ | ❔ | ❔ |
| Cloud Storage rules | ✅ | 🟡 | ✅ |
✅ supported • 🟡 partial / basic • ❔ not documented on the listing (may still be present) • ❌ not supported.
- No authorization evaluation. The plugin never decides whether a request is allowed or denied. All diagnostics are structural and syntactic.
- No Firebase integration. It does not connect to Firebase projects, emulators, credentials, or live Firestore data, and it hard-codes no project IDs.
- No type inference. Member completion after
request./resource.is a fixed, documented list, not the result of evaluating an expression's type. The resolver links names to declarations; it never asserts that a rule is correct or secure. - Firestore and Cloud Storage. Both
service cloud.firestoreandservice firebase.storageare supported; the dialect (service name, root-match shape, andrequest/resourcemembers) is detected from the file'sservicedeclaration. Other services (e.g.firebase.database, whose rules are JSON) are not modeled. - Conservative diagnostics. The grammar is intentionally permissive so the IDE keeps working while you edit. Constructs not confirmed by official Firebase docs are parsed without being flagged.
- Highlighting is approximate. Highlighting uses a fast, context-sensitive
lexer that is separate from the parser. For example,
/is always highlighted as a path separator (it shares the operator color), and an unterminated string is highlighted to the end of its line; the parser remains the source of truth for precise errors. - One fixed formatting style. There is no custom code-style settings UI, and the formatter applies no column alignment.
A complete file that exercises most of what the plugin understands:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /cities/{city} {
allow read, write: if isSignedIn() && resource.data.owner == request.auth.uid;
match /landmarks/{landmark} {
allow read: if isSignedIn();
allow create: if isSignedIn()
&& request.resource.data.name is string;
}
}
match /logs/{document=**} {
allow read: if false;
}
}
}
The same features work on a service firebase.storage file — the dialect is
detected from the service declaration — with the Cloud Storage root match
(/b/{bucket}/o) and object-metadata members (request.resource.size,
request.resource.contentType):
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
function isSignedIn() {
return request.auth != null;
}
match /users/{userId}/{allPaths=**} {
allow read: if isSignedIn() && request.auth.uid == userId;
allow write: if isSignedIn()
&& request.auth.uid == userId
&& request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
}
}
The project is a Kotlin-based IntelliJ Platform plugin built with Gradle Kotlin DSL and the IntelliJ Platform Gradle Plugin 2.x.
Important parts of the repository:
build.gradle.kts
settings.gradle.kts
src/main/grammar/ # FirebaseRules.bnf + FirebaseRules.flex
src/main/kotlin/dev/lezli/hotrulez/
FirebaseRulesLanguage.kt
FirebaseRulesFileType.kt
FirebaseRulesIcons.kt
lexer/
highlighting/
parser/
psi/
formatting/
diagnostics/ # annotator, inspections, quick-fixes (fixes/)
editor/
references/ # PSI named elements, resolver, reference
findusages/ # FindUsagesProvider + word scanner
refactoring/ # names validator + rename processor
completion/ # CompletionContributor
structureview/ # Structure tool-window outline
folding/ # FoldingBuilder
documentation/ # quick-docs provider + doc-prose table
parameterinfo/ # ParameterInfoHandler
src/main/resources/icons/
src/main/resources/META-INF/plugin.xml
src/test/kotlin/dev/lezli/hotrulez/
src/test/testData/formatter/
docs/spec.md
docs/tasks.md
The implementation is split by responsibility:
lexer/tokenizes Firebase Rules syntax for highlighting.highlighting/maps tokens to IDE text attributes and exposes the color page.parser/wires the generated parser/PSI into a recoverableParserDefinition.psi/defines the Firebase Rules PSI file and wrapper elements.formatting/provides IntelliJ formatter blocks and spacing rules.diagnostics/provides the annotator and inspections, with quick-fixes indiagnostics/fixes/.editor/provides the brace matcher, quote handler, and commenter.references/makes declarations named elements and resolves a name use to its declaration with Firestore scoping (the layer go-to-definition, find-usages, and rename all ride on).findusages/exposes declarations to Find Usages via a word scanner.refactoring/validates rename input and scopes path-variable rename.completion/provides scope-aware, doc-sourced code completion.structureview/provides the Structure tool-window outline.folding/provides code folding for braced blocks and block comments.documentation/provides quick documentation and the shared doc-prose table.parameterinfo/provides call parameter-info hints.plugin.xmlregisters every extension point above.
The parser, typed PSI, and parsing lexer are generated by Grammar-Kit and JFlex
from src/main/grammar/ into build/generated/sources/grammarkit (not
committed); generation runs automatically before compilation. The grammar is
deliberately permissive and recovery-oriented, so a malformed statement is
isolated to its own declaration instead of breaking the rest of the file. A
second, hand-written lexer (lexer/FirebaseRulesLexer.kt) is used only for
highlighting.
- JDK 21.
- The included Gradle wrapper.
- A JetBrains IDE compatible with build
252or newer.
The Gradle project currently targets IntelliJ IDEA 2025.2.6.2 for plugin
development and tests.
Run the test suite:
./gradlew testBuild the installable plugin ZIP:
./gradlew buildPluginThe plugin ZIP is written under:
build/distributions/
For CI-style runs, this repository uses:
./gradlew test --no-daemon
./gradlew buildPlugin --no-daemonFrom inside your IDE:
- Open
Settings/Preferences→Plugins. - Select the
Marketplacetab. - Search for Firebase Rules (or HotRulez) and click
Install. - Restart the IDE if prompted.
Or open the plugin page and use its Install button.
Prefer to pin a version or work offline? Download the prebuilt ZIP attached to a GitHub release, or build it yourself:
./gradlew buildPlugin # writes the ZIP to build/distributions/Then in a JetBrains IDE:
- Open
SettingsorPreferences. - Go to
Plugins. - Use the gear menu and choose
Install Plugin from Disk.... - Select the ZIP from
build/distributions/. - Restart the IDE if prompted.
After installation:
- Open a
.rulesfile to get Firebase Rules file recognition and highlighting. - Use the IDE's normal reformat action to format Firebase Rules files.
- Keep using Firebase's official tooling for deployment and authorization behavior checks.
HotRulez covers file recognition, highlighting, structural parsing, automatic formatting, diagnostics, symbol intelligence (go-to-definition, find usages, rename, and code completion), authoring aids (structure view, code folding, quick documentation, and parameter info), and editor conveniences (brace, quote, and comment handling). It performs no runtime authorization evaluation by design.
See docs/spec.md for the product and implementation spec, and docs/tasks.md for the task checklist.
Contributions of every size are welcome — bug reports, docs, new diagnostics, test cases, and features. Start here:
- Read the Contributing guide for development setup, build and test commands, and the commit-message convention.
- Be a good neighbor: this project follows a Code of Conduct.
- Have a question or an idea? Open a Discussion.
- Found a bug or want a feature? Open an issue.
Releases are automated with release-please, so pull requests use Conventional Commits titles and are squash-merged. Details are in CONTRIBUTING.md.
HotRulez is purely structural and never connects to Firebase, so its attack surface is small — but security reports are taken seriously. Please report vulnerabilities privately via GitHub's security advisories rather than a public issue. See SECURITY.md for details.
HotRulez is released under the MIT License. © 2026 lezli01.