I have been writing Cloud Firestore rules for years, and I edit them where I do the rest of my work: Android Studio. Every time, the same small insult: open a .rules file and the IDE just shrugs. Firestore Security Rules, the gate that decides who reads and writes your production data, get the same treatment as a .txt scratchpad: no highlighting that knows match from allow, no go-to-definition for a helper I wrote forty lines up, no warning when a rule can never parse. The only fixes on offer were paid plugins, so I built a free one: HotRulez, first-class language support for Firebase Security Rules.
// the file that guards your database deserves better than a glorified text box.
A real language, treated like noise
Firebase Security Rules are a genuine language: their own grammar, their own scoping, their own built-ins. But no IDE knows that. The editor guesses JavaScript or JSON, gets it wrong, and hands you a security-critical file with none of the tooling you’d expect for code that matters.
That gap is the whole reason HotRulez exists. It is free and open-source under MIT (no subscription to syntax-highlight a config file), and it runs in the JetBrains IDE you already work in, Android Studio included. One thing it deliberately is not: a security checker. It is entirely structural and static. It never evaluates authorization, never connects to Firebase, never claims a rule is “secure.” It understands the shape of your rules, not their verdict.
And because Cloud Storage rules are the same language (only the service line changes), HotRulez supports those .rules files just as well.
What it actually does
Every *.rules file gets recognized by extension, with its own file icon and 24 customizable highlighting categories. Some of those are semantic (service name, function declaration, path variable), distinctions a lexer alone can’t make, resolved by a lightweight annotator over the parse tree.
Here’s a representative Firestore file, the kind HotRulez highlights and 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;
}
}
}
In that file, isSignedIn is a real symbol. Go-to-declaration jumps to it, find-usages lists every call, rename touches exactly the right ones. The same holds for function parameters, let bindings, and path variables like {city}, and the reference layer honors Firestore’s actual scoping, so renaming a path variable touches only its match subtree and leaves a shadowing inner binding alone. Cloud Storage rules work too (service firebase.storage, the /b/{bucket}/o root, members like request.resource.size), with the dialect detected from the service declaration.
Formatting and diagnostics that stay honest
One Reformat Code action turns compact rules into clean, consistently-indented output. It runs over the parsed structure, so it makes minimal whitespace changes only: it never reorders rules or rewrites expressions, preserves comments and blank lines, and won’t collapse a && you intentionally split across lines. Watch a whole file glued onto one line:
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;}}}}
become this, with a single keystroke:
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;
}
}
}
}
Then there are 18 structural checks. The split is deliberate: always-wrong, grammar-inexpressible mistakes are errors; suspicious-but-legal structure is a configurable warning. Typo an operation and it stops you cold:
match /cities/{city} {
allow read, fetch: if true; // error: Unknown Firebase Rules operation 'fetch'.
}
A function missing its return, a duplicate parameter name, a missing rules_version = '2';, a helper() called with the wrong number of arguments. All caught structurally. The wording never asserts a rule is secure or authorized, because HotRulez can’t know that and won’t pretend.
It resolves names to declarations. It never claims your rule is correct.
How it’s built, and what it won’t do
It’s Kotlin on the IntelliJ Platform SDK. The parser, typed PSI, and lexer are generated by Grammar-Kit and JFlex from a .bnf and .flex grammar that’s deliberately recovery-oriented: a malformed statement is isolated to its own declaration instead of breaking highlighting for the rest of the file. A separate hand-written lexer drives fast highlighting; the parser stays the source of truth for precise errors. Building it needs JDK 21 and a JetBrains IDE on build 252+.
The limitations are the honest other half. No type inference: member completion after request. is a fixed, docs-sourced list, not inferred types. Firestore and Cloud Storage only, not Realtime Database (whose rules are JSON anyway). One fixed formatting style, conservative diagnostics by design. It makes your .rules files navigable and catches the mistakes that can’t compile. It does not test your authorization logic, and you should keep Firebase’s official tooling for deployment and security testing.
It isn’t on the JetBrains Marketplace yet, so installing means one of two paths: grab the prebuilt ZIP from a GitHub release, or build from source with ./gradlew buildPlugin and find it in build/distributions/. Then Settings → Plugins → gear → “Install Plugin from Disk…”, point it at the ZIP, restart, and open any .rules file.
The trade I made, stated plainly: HotRulez gives you structure, navigation, and the errors that can’t lie. And it gives up on guessing whether a rule is safe, because guessing that would be worse than silent. It’s fully functional and open under MIT, so issues, ideas, and pull requests are welcome on the repo. More of what I work on lives at lezli01.is-a.dev.