API Rules and Filters - C++ SDK Documentation

Overview

API Rules are your collection access controls and data filters. They control who can perform actions on your collections and what data they can access.

Each collection has 5 rules, corresponding to specific API actions:

  • listRule - Controls who can list records
  • viewRule - Controls who can view individual records
  • createRule - Controls who can create records
  • updateRule - Controls who can update records
  • deleteRule - Controls who can delete records

Auth collections have an additional manageRule that allows one user to fully manage another user’s data.

Rule Values

Each rule can be set to:

  • null (locked) - Only authorized superusers can perform the action (default)
  • Empty string "" - Anyone can perform the action (superusers, authenticated users, and guests)
  • Non-empty string - Only users that satisfy the filter expression can perform the action

Important Notes

  1. Rules act as filters: API Rules also act as record filters. For example, setting listRule to status = "active" will only return active records.
  2. HTTP Status Codes:
    • 200 with empty items for unsatisfied listRule
    • 400 for unsatisfied createRule
    • 404 for unsatisfied viewRule, updateRule, deleteRule
    • 403 for locked rules when not a superuser
  3. Superuser bypass: API Rules are ignored when the action is performed by an authorized superuser.

Setting Rules via SDK

C++ SDK

#include "bosbase/bosbase.h"

bosbase::BosBase pb("http://localhost:8090");
pb.collection("_superusers").authWithPassword("admin@example.com", "password");

// Create collection with rules
auto collection = pb.collections->createBase("articles", nlohmann::json{
    {"fields", nlohmann::json::array({
        nlohmann::json{{"name", "title"}, {"type", "text"}, {"required", true}},
        nlohmann::json{
            {"name", "status"}, 
            {"type", "select"}, 
            {"options", nlohmann::json{{"values", nlohmann::json::array({"draft", "published"})}}}, 
            {"maxSelect", 1}
        },
        nlohmann::json{
            {"name", "author"}, 
            {"type", "relation"}, 
            {"options", nlohmann::json{{"collectionId", "users"}}}, 
            {"maxSelect", 1}
        }
    })},
    {"listRule", "@request.auth.id != \"\" || status = \"published\""},
    {"viewRule", "@request.auth.id != \"\" || status = \"published\""},
    {"createRule", "@request.auth.id != \"\""},
    {"updateRule", "author = @request.auth.id || @request.auth.role = \"admin\""},
    {"deleteRule", "author = @request.auth.id || @request.auth.role = \"admin\""}
});

// Update rules
pb.collections->update("articles", nlohmann::json{
    {"listRule", "@request.auth.id != \"\" && (status = \"published\" || status = \"draft\")"}
});

// Remove rule (set to empty string for public access)
pb.collections->update("articles", nlohmann::json{
    {"listRule", ""}  // Anyone can list
});

// Lock rule (set to null for superuser only)
pb.collections->setRule("articles", "deleteRule", std::nullopt);  // Only superusers can delete

Filter Syntax

The syntax follows: OPERAND OPERATOR OPERAND

Operators

Comparison Operators:

  • = - Equal
  • != - NOT equal
  • > - Greater than
  • >= - Greater than or equal
  • < - Less than
  • <= - Less than or equal

String Operators:

  • ~ - Like/Contains (auto-wraps right operand in % for wildcard match)
  • !~ - NOT Like/Contains

Array Operators (Any/At least one of):

  • ?= - Any Equal
  • ?!= - Any NOT equal
  • ?> - Any Greater than
  • ?>= - Any Greater than or equal
  • ?< - Any Less than
  • ?<= - Any Less than or equal
  • ?~ - Any Like/Contains
  • ?!~ - Any NOT Like/Contains

Logical Operators:

  • && - AND
  • || - OR
  • () - Grouping
  • // - Single line comments

Special Identifiers

@request.*

Access current request data:

@request.context - The context where the rule is used

pb.collections->setRule("posts", "listRule", "@request.context != \"oauth2\"");

@request.method - HTTP request method

pb.collections->setRule("posts", "updateRule", "@request.method = \"PATCH\"");

@request.headers.* - Request headers (normalized to lowercase, - replaced with _)

pb.collections->setRule("posts", "listRule", "@request.headers.x_token = \"test\"");

@request.query.* - Query parameters

pb.collections->setRule("posts", "listRule", "@request.query.page = \"1\"");

@request.auth.* - Current authenticated user

pb.collections->setRule("posts", "listRule", "@request.auth.id != \"\"");
pb.collections->setRule("posts", "viewRule", "@request.auth.email = \"admin@example.com\"");
pb.collections->setRule("posts", "updateRule", "@request.auth.role = \"admin\"");

@request.body.* - Submitted body parameters

pb.collections->setRule("posts", "createRule", "@request.body.title != \"\"");
pb.collections->setRule("posts", "updateRule", "@request.body.status:isset = false");  // Prevent changing status

@collection.*

Target other collections that aren’t directly related:

// Check if user has access to related collection
std::string rule = R"(
    @request.auth.id != "" && 
    @collection.news.categoryId ?= categoryId && 
    @collection.news.author ?= @request.auth.id
)";
pb.collections->setRule("categories", "listRule", rule);

// Using aliases for multiple joins
std::string complexRule = R"(
    @request.auth.id != "" &&
    @collection.courseRegistrations.user ?= id &&
    @collection.courseRegistrations:auth.user ?= @request.auth.id &&
    @collection.courseRegistrations.courseGroup ?= @collection.courseRegistrations:auth.courseGroup
)";
pb.collections->setRule("courses", "listRule", complexRule);

@ Macros (Datetime)

All macros are UTC-based:

  • @now - Current datetime as string
  • @second - Current second (0-59)
  • @minute - Current minute (0-59)
  • @hour - Current hour (0-23)
  • @weekday - Current weekday (0-6)
  • @day - Current day
  • @month - Current month
  • @year - Current year
  • @yesterday - Yesterday datetime
  • @tomorrow - Tomorrow datetime
  • @todayStart - Beginning of current day
  • @todayEnd - End of current day
  • @monthStart - Beginning of current month
  • @monthEnd - End of current month
  • @yearStart - Beginning of current year
  • @yearEnd - End of current year

Example:

pb.collections->setRule("posts", "listRule", "@request.body.publicDate >= @now");
pb.collections->setRule("posts", "listRule", "created >= @todayStart && created <= @todayEnd");

Field Modifiers

:isset

Check if a field was submitted in the request (only for @request.* fields):

// Prevent changing role field
pb.collections->setRule("users", "updateRule", "@request.body.role:isset = false");

// Require email field
pb.collections->setRule("users", "createRule", "@request.body.email:isset = true");

:length

Check the number of items in an array field (multiple file, select, relation):

// Check submitted array length
pb.collections->setRule("posts", "createRule", "@request.body.tags:length > 1 && @request.body.tags:length <= 5");

// Check existing record array length
pb.collections->setRule("posts", "listRule", "categories:length = 2");
pb.collections->setRule("posts", "listRule", "documents:length >= 1");

:each

Apply condition on each item in an array field:

// Check if all submitted select options contain "create"
pb.collections->setRule("users", "createRule", "@request.body.permissions:each ~ \"create\"");

// Check if all existing field values have "pb_" prefix
pb.collections->setRule("posts", "listRule", "tags:each ~ \"pb_%\"");

:lower

Perform case-insensitive string comparisons:

// Case-insensitive comparison
pb.collections->setRule("posts", "listRule", "@request.body.title:lower = \"test\"");
pb.collections->setRule("posts", "updateRule", "status:lower ~ \"active\"");

geoDistance Function

Calculate Haversine distance between two geographic points in kilometers:

// Offices within 25km of location
pb.collections->setRule("offices", "listRule", "geoDistance(address.lon, address.lat, 23.32, 42.69) < 25");

// Using request data
std::string rule = "geoDistance(location.lon, location.lat, @request.query.lon, @request.query.lat) < @request.query.radius";
pb.collections->setRule("offices", "listRule", rule);

Common Rule Examples

Allow Only Authenticated Users

nlohmann::json rules = {
    {"listRule", "@request.auth.id != \"\""},
    {"viewRule", "@request.auth.id != \"\""},
    {"createRule", "@request.auth.id != \"\""},
    {"updateRule", "@request.auth.id != \"\""},
    {"deleteRule", "@request.auth.id != \"\""}
};
pb.collections->setRules("posts", rules);

Owner-Based Access

nlohmann::json rules = {
    {"viewRule", "@request.auth.id != \"\" && author = @request.auth.id"},
    {"updateRule", "@request.auth.id != \"\" && author = @request.auth.id"},
    {"deleteRule", "@request.auth.id != \"\" && author = @request.auth.id"}
};
pb.collections->setRules("posts", rules);

Role-Based Access

// Assuming users have a "role" select field
pb.collections->setRule("posts", "listRule", "@request.auth.id != \"\" && @request.auth.role = \"admin\"");
pb.collections->setRule("posts", "updateRule", "@request.auth.role = \"admin\" || author = @request.auth.id");

Public with Authentication

// Public can view published, authenticated can view all
pb.collections->setRule("posts", "listRule", "@request.auth.id != \"\" || status = \"published\"");
pb.collections->setRule("posts", "viewRule", "@request.auth.id != \"\" || status = \"published\"");

Filtered Results

// Only show active records
pb.collections->setRule("posts", "listRule", "status = \"active\"");

// Only show records from last 30 days
pb.collections->setRule("posts", "listRule", "created >= @yesterday");

// Only show records matching user's organization
pb.collections->setRule("posts", "listRule", "@request.auth.id != \"\" && organization = @request.auth.organization");

Complex Rules

// Multiple conditions
std::string rule = R"(
    @request.auth.id != "" && 
    (status = "published" || status = "draft") && 
    author = @request.auth.id
)";
pb.collections->setRule("posts", "listRule", rule);

// Nested relations
pb.collections->setRule("posts", "listRule", "@request.auth.id != \"\" && author.role = \"staff\"");

// Back relations
pb.collections->setRule("posts", "listRule", "@request.auth.id != \"\" && comments_via_author.id != \"\"");

Using Filters in Queries

Filters can also be used in regular queries (not just rules):

// List with filter
std::map<std::string, nlohmann::json> query = {
    {"filter", "status = \"published\" && created >= @todayStart"}
};
auto result = pb.collection("articles").getList(1, 20, query);

// Complex filter
query = {
    {"filter", "(title ~ \"test\" || description ~ \"test\") && status = \"published\""}
};
result = pb.collection("articles").getList(1, 20, query);

// Using relation filters
query = {
    {"filter", "author.role = \"admin\" && categories.id ?= \"CAT_ID\""}
};
result = pb.collection("articles").getList(1, 20, query);

// Geo distance filter
query = {
    {"filter", "geoDistance(location.lon, location.lat, 23.32, 42.69) < 25"}
};
result = pb.collection("offices").getList(1, 20, query);

Complete Example

#include "bosbase/bosbase.h"
#include <iostream>

int main() {
    bosbase::BosBase pb("http://localhost:8090");
    pb.collection("_superusers").authWithPassword("admin@example.com", "password");

    // Create users collection with role field
    auto users = pb.collections->createAuth("users", nlohmann::json{
        {"fields", nlohmann::json::array({
            nlohmann::json{{"name", "name"}, {"type", "text"}, {"required", true}},
            nlohmann::json{
                {"name", "role"}, 
                {"type", "select"}, 
                {"options", nlohmann::json{{"values", nlohmann::json::array({"user", "staff", "admin"})}}}, 
                {"maxSelect", 1}
            }
        })}
    });

    // Create articles collection with comprehensive rules
    auto articles = pb.collections->createBase("articles", nlohmann::json{
        {"fields", nlohmann::json::array({
            nlohmann::json{{"name", "title"}, {"type", "text"}, {"required", true}},
            nlohmann::json{{"name", "content"}, {"type", "editor"}, {"required", true}},
            nlohmann::json{
                {"name", "status"}, 
                {"type", "select"}, 
                {"options", nlohmann::json{{"values", nlohmann::json::array({"draft", "published", "archived"})}}}, 
                {"maxSelect", 1}
            },
            nlohmann::json{
                {"name", "author"}, 
                {"type", "relation"}, 
                {"options", nlohmann::json{{"collectionId", users["id"]}}}, 
                {"maxSelect", 1}, 
                {"required", true}
            },
            nlohmann::json{
                {"name", "categories"}, 
                {"type", "relation"}, 
                {"options", nlohmann::json{{"collectionId", "categories"}}}, 
                {"maxSelect", 5}
            },
            nlohmann::json{{"name", "published_at"}, {"type", "date"}}
        })},
        // Public can see published, authenticated can see their own or published
        {"listRule", R"(@request.auth.id != "" && (author = @request.auth.id || status = "published") || status = "published")"},
        // Same logic for viewing
        {"viewRule", R"(@request.auth.id != "" && (author = @request.auth.id || status = "published") || status = "published")"},
        // Only authenticated users can create
        {"createRule", "@request.auth.id != \"\""},
        // Owners or admins can update, but prevent changing status after publishing
        {"updateRule", R"(@request.auth.id != "" && (author = @request.auth.id || @request.auth.role = "admin") && (@request.body.status:isset = false || status != "published"))"},
        // Only owners or admins can delete
        {"deleteRule", "@request.auth.id != \"\" && (author = @request.auth.id || @request.auth.role = \"admin\")"}
    });

    // Authenticate as regular user
    pb.collection("users").authWithPassword("user@example.com", "password123");

    // User can create article
    auto user = pb.authStore()->record();
    auto article = pb.collection("articles").create(nlohmann::json{
        {"title", "My Article"},
        {"content", "<p>Content</p>"},
        {"status", "draft"},
        {"author", user["id"]}
    });

    // User can update their own article
    pb.collection("articles").update(article["id"].get<std::string>(), nlohmann::json{
        {"title", "Updated Title"}
    });

    // User can list their own articles or published ones
    std::map<std::string, nlohmann::json> query = {
        {"filter", "@request.auth.id = author"}
    };
    auto myArticles = pb.collection("articles").getList(1, 20, query);

    // User can also query with additional filters
    query = {
        {"filter", "status = \"published\" && created >= @todayStart"}
    };
    auto published = pb.collection("articles").getList(1, 20, query);

    return 0;
}

Best Practices

  1. Test Rules Thoroughly: Always test API rules with different user roles and scenarios
  2. Use Specific Rules: Be as specific as possible to avoid security issues
  3. Document Rules: Keep documentation of what each rule does
  4. Validate Input: Combine rules with field validation for better security
  5. Use Field Modifiers: Leverage :isset, :length, :each, :lower for more control
  6. Consider Performance: Complex rules with multiple joins may impact performance
  7. Test Edge Cases: Test with null values, empty strings, and missing fields
  8. Review Regularly: Periodically review rules for security and correctness