Policy Cheatsheet
Overview
Welcome to the Policy Cheatsheet!
This quick reference guide is designed to assist you in writing effective policies for your Tarobase app. For a deeper understanding of how policies work and best practices for creating secure and scalable policies, please visit the Policies page.
Policy Structure
A Tarobase policy is a JSON object that defines rules, fields, storage preferences, and hooks for specific paths in your data structure.
Basic Structure:
{
"path": {
"rules": {
"read": "<condition>",
"create": "<condition>",
"update": "<condition>",
"delete": "<condition>"
},
"fields": {
"requiredField": "String",
"optionalField": "UInt?"
},
"onchain": true | false,
"hooks": {
"onchain": {
"create": "<action>",
"update": "<action>",
"delete": "<action>"
}
}
}
}
Example:
{
"chatrooms/$roomId": {
"onchain": true,
"fields": {
"name": "String",
"feeAmount": "UInt",
"feeReceiverAddress": "Address",
"maxMembers": "UInt?",
"memberCount": "UInt",
"admin": "Address"
},
"rules": {
"read": "true",
"create": "@newData.admin == @user.address",
"update": "@data.admin == @user.address",
"delete": "@data.admin == @user.address"
}
}
}
Special Variables
@data
: The current state of the document before the write operation.@newData
: The proposed new state of the document during the write operation.@user.address
: The wallet address of the user making the request.get(path)
: Get data at a specific path.getAfter(path)
: Get data at a specific path after all pending changes in the batch are applied.
Conditions and Operators
Use standard JavaScript-like operators to define conditions:
- Logical Operators:
&&
(and),||
(or),!
(not) - Comparison Operators:
==
,!=
,<
,>
,<=
,>=
- Null Check:
variable != null
,variable == null
Rules
Define who can read and write to specific paths:
- Read Rule (
"read"
): Condition that must be true for a read operation to succeed - Create Rule (
"create"
): Condition that must be true for document creation - Update Rule (
"update"
): Condition that must be true for document updates - Delete Rule (
"delete"
): Condition that must be true for document deletion
Examples:
- Allow everyone to read:
"read": "true"
- Allow creation only if the user is the admin:
"create": "@newData.admin == @user.address"
Fields and Data Types
Specify the expected fields and their data types for on-chain storage:
Available Data Types:
String
Address
Int
UInt
Bool
Add ?
after any type to make it optional, e.g., "UInt?"
.
Example:
"fields": {
"createdBy": "Address",
"text": "String",
"amount": "UInt",
"maxMembers": "UInt?"
}
On-Chain Storage
To store data on-chain, set the "onchain" property to true:
"onchain": true
Note: When using on-chain storage, you must define all fields you will use in the "fields"
section.
Hooks
Hooks allow you to perform additional actions after a successful write operation.
Syntax:
"hooks": {
"onchain": {
"create": "<action>",
"update": "<action>",
"delete": "<action>"
}
}
Example:
"hooks": {
"onchain": {
"create": "@TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @user.address, get(/chatrooms/$roomId).feeReceiverAddress, get(/chatrooms/$roomId).feeAmount)"
}
}
Plugins
Plugins extend the functionality of your policies. For example, the @TokenPlugin provides token-related actions.
TokenPlugin Actions:
@TokenPlugin.transferWholeTokens(tokenAddress, fromAddress, toAddress, amount)
@TokenPlugin.transfer(tokenAddress, fromAddress, toAddress, amountInWei)
Example Usage in Hooks:
"hooks": {
"onchain": {
"create": "@TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @user.address, get(/chatrooms/$roomId).feeReceiverAddress, get(/chatrooms/$roomId).feeAmount)"
}
}
Sample policy with batch operations:
{
"chatrooms/$roomId/members/$memberAddress": {
"onchain": true,
"fields": {},
"rules": {
"read": "true",
"write": "$memberAddress == @user.address && getAfter('/chatrooms/$roomId').memberCount <= get(/chatrooms/$roomId).maxMembers && getAfter(/chatrooms/$roomId).memberCount -1 == get(/chatrooms/$roomId).memberCount"
},
"hooks": {
"onchain": {
"create": "@TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @user.address, get(/chatrooms/$roomId).feeReceiverAddress, get(/chatrooms/$roomId).feeAmount)"
}
}
}
}
Explanation:
- Read Rule: Allows reading by anyone
- Write Rule: Allows joining if:
- The member address matches the user's address
- The new member count doesn't exceed max members
- The member count is properly incremented
- Hook: Transfers fee amount in USDC to the fee receiver when a member joins
Best Practices
-
Principle of Least Privilege: Start with the minimal permissions necessary and expand as needed.
-
Use Dynamic Segments: Leverage $variable in paths to make policies reusable.
-
Explicit Field Definitions: Always define fields and data types when using on-chain storage.
-
Validate Data: Use conditions in rules to ensure data integrity.
-
Test Policies Thoroughly: Before deploying, test your policies to ensure they behave as expected.
-
Use Batch Operations: Leverage getAfter() for referencing pending changes in batch operations.
-
Document Your Policies: Add comments or maintain separate documentation to explain complex rules.
Quick Reference
Allow Read to Everyone:
"read": "true"
Allow Creation Only to Admin:
"create": "@newData.admin == @user.address"
Check for Non-Null Fields:
"@newData.text != null"
Prevent Updates or Deletes:
"update": "false",
"delete": "false"
Use Hooks for Actions:
"hooks": {
"onchain": {
"create": "<action>"
}
}
Common Use Cases
User Authentication:
Allow users to read and write their own data:
"users/$userId": {
"rules": {
"read": "@walletAddress == $userId",
"write": "@walletAddress == $userId"
}
}
Public Data with Controlled Writes:
Allow everyone to read but restrict who can write:
"publicData/$id": {
"rules": {
"read": "true",
"write": "@walletAddress == 0xAdminAddress"
}
}
Feel free to refer back to this cheatsheet whenever you're crafting or updating your Tarobase policies. If you have any questions or need further assistance, don't hesitate to reach out in our discord community.