This file provides guidance for Claude (Anthropic) when working with this codebase.
Countly is a product analytics platform. Backend: Node.js 22+, MongoDB. Frontend: Vue 2, Element UI, Backbone (legacy). Architecture is plugin-based.
npm run start:all:dev # Start all services (API:3001, Frontend:6001)
npx grunt dist-all # Build static assets (required after JS changes)
npx grunt locales # Build locale files
npx grunt sass # Compile SASS only
# Testing
npm run test:unit # Unit tests
npm run test:plugin -- name # Single plugin tests
countly plugin lint name # Lint plugin
countly shellcheck # Validate shell scriptsALWAYS follow these - no exceptions:
-
API endpoints must use validation:
const { validateRead, validateCreate, validateUpdate, validateDelete } = require('../../../api/utils/rights.js'); validateRead(params, FEATURE_NAME, () => { /* handler */ });
-
Write operations must include app_id:
// CORRECT - prevents cross-app access db.collection("items").deleteOne({_id: id, app_id: params.app_id + ""});
-
Cast user input to strings for auth:
params.username = params.username + "";
-
Use spawn, not exec for shell commands:
cp.spawn("command", [userInput]); // Safe // exec("command " + userInput); // VULNERABLE
-
Never use v-html with user data in Vue templates.
| What | Where |
|---|---|
| Plugin code | plugins/<name>/api/api.js |
| Vue views | plugins/<name>/frontend/public/javascripts/countly.views.js |
| Templates | plugins/<name>/frontend/public/templates/ |
| Localization | plugins/<name>/frontend/public/localization/<name>.properties |
| Tests | plugins/<name>/tests.js |
var plugins = require('../../pluginManager.js');
var common = require('../../../api/utils/common.js');
const { validateRead } = require('../../../api/utils/rights.js');
const FEATURE_NAME = 'myfeature';
plugins.register("/o/myfeature", function(ob) {
var params = ob.params;
validateRead(params, FEATURE_NAME, function() {
// Validate input
var argProps = {
'id': { 'required': true, 'type': 'String' }
};
var validation = common.validateArgs(params.qstring, argProps, true);
if (!validation.obj) {
common.returnMessage(params, 400, 'Error: ' + validation.errors);
return;
}
// Query with app_id
common.db.collection('mydata').findOne(
{_id: validation.obj.id, app_id: params.app_id + ""},
function(err, result) {
common.returnOutput(params, result || {});
}
);
});
});var MyComponent = countlyVue.views.create({
template: countlyVue.T("/myplugin/templates/main.html"),
mixins: [countlyVue.mixins.auth(FEATURE_NAME)],
data: function() {
return { items: [] };
},
computed: {
// Prefer computed over watchers
},
methods: {
refresh: function() {
// Called for auto-refresh
}
}
});
app.route('/dashboard/myfeature', 'myfeature', function() {
new countlyVue.views.BackboneWrapper({ component: MyComponent }).render();
});// Read batcher for hot documents
common.readBatcher.getOne("collection", {_id: id}, callback);
// Write batcher for multiple updates
common.writeBatcher.add("collection", id, {$inc: {count: 1}});
// Always use projection
db.collection('x').findOne({_id: id}, {projection: {field: 1}});// Required if your plugin creates per-app collections
plugins.register("/i/apps/create", function(ob) {
common.db.collection('mydata' + ob.appId).createIndex({"field": 1});
});
plugins.register("/i/apps/delete", function(ob) {
common.db.collection('mydata' + ob.appId).drop();
});
plugins.register("/i/app_users/delete", function(ob) {
common.db.collection("mydata" + ob.app_id).deleteMany({uid: {$in: ob.uids}});
});- Use SASS with SCSS syntax
- BEM naming with
cly-vue-prefix - Bulma classes use
bu-prefix - Use
@use, never@import
| Don't | Do Instead |
|---|---|
this.$parent.value = x |
Emit events: this.$emit('update', x) |
| Deep watchers | Watch specific properties |
v-html with user data |
Use {{ }} text interpolation |
| Query without app_id | Always include app_id in queries |
exec(cmd + userInput) |
spawn(cmd, [userInput]) |
replace(' ', '-') |
replace(/ /g, '-') for all occurrences |
For comprehensive guidelines, read:
CODING_GUIDELINES.md- Full development standardsdocs/SECURITY.md- Security requirementsdocs/VUEJS_GUIDELINES.md- Vue patternsdocs/CSS_STYLE_GUIDE.md- SASS/BEM conventionsdocs/UI_TESTING.md- Cypress testingtest/README.md- Test suite documentationplugins/empty/- Sample plugin structure