Quotas
Storage limits and quota management
Overview
Quotas control how much storage each user or organization can consume. When a user or organization reaches their quota limit, further record creation or modification is blocked until storage is freed or the quota is upgraded.
Quotas are tied to accountability - the accountable entity's quota is charged for storage used by a record.
Default Quotas
Every user receives a default storage quota when they sign up. This is configured
via the DEFAULT_STORAGE_SIZE_QUOTA environment variable (in megabytes):
# Default is 500 MB if not specified
DEFAULT_STORAGE_SIZE_QUOTA=500Organizations and groups start with zero quota by default - they must be explicitly assigned a quota through the payment system or database configuration.
Checking Quota Usage
Use getQuota() to retrieve quota information:
// Get your own quota
const myQuota = await lr.getQuota();
console.log(myQuota.totalStorageAvailable); // Total bytes available
console.log(myQuota.usedStorage); // Bytes currently used
console.log(myQuota.remainingStorageAvailable); // Bytes remaining
console.log(myQuota.isUpgraded); // Whether quota was upgraded via payment
// Get an organization's quota (must be member, host, or accountable)
const orgQuota = await lr.getQuota(organizationId);You can only check quotas for entities you have a relationship with (member, host, or accountable). Attempting to check another user's quota returns an empty object.
Quota Enforcement
When storage quota is exceeded:
- Record creation is blocked - New records cannot be created
- Record modification is blocked - Existing records cannot be updated
- The operation fails silently - The change is not persisted
// Handle quota violations
lr.setQuotaViolationErrorHandler((response) => {
console.log('Storage quota exceeded');
// Show upgrade prompt or warning to user
// If you do not throw an error here the create command
// will not throw either.
// You can decide not to throw and handle all violations here.
throw new Error('Quota exceeded')
});
try {
await lr.Record.createKeyValue({ /* large data */ });
} catch (error) {
// This is the error you throw in the QuotaViolationErrorHandler
// Handle quota violation
}Upgrading Quotas
Quotas can be upgraded through payment providers. Currently, Paddle is the only implemented payment provider.
How Quota Upgrades Work
- User initiates a subscription through Paddle checkout
- Paddle sends a webhook notification to your LinkedRecords server
- LinkedRecords creates a quota event that increases the user's storage limit
- The
isUpgradedflag becomestruein quota responses
Paddle Integration
Configure Paddle via environment variables:
PADDLE_API_KEY=your_paddle_api_key
PADDLE_API_URL=https://api.paddle.com
PADDLE_NOTIFICATION_SECRET=your_webhook_secretPaddle webhook notifications are sent to /payment_events/paddle. The webhook
payload must include:
custom_data.nodeId- The user ID or organization ID to upgradecustom_data.total_storage_availablein the product - The new storage limit in bytes
Example product configuration in Paddle:
{
"custom_data": {
"total_storage_available": "5242880000"
}
}Subscription Events
LinkedRecords handles these Paddle subscription events:
| Event | Effect |
|---|---|
subscription.created | Increases quota to the subscribed amount |
subscription.updated (with cancel) | Reverts quota to default when subscription ends |
subscription.canceled | Reverts quota to default |
Management URLs
The accountable party can access Paddle subscription management URLs through the quota response:
const quota = await lr.getQuota(organizationId);
// Only available to the accountable party
const urls = quota.accounteeInformation?.data?.management_urls;
if (urls) {
console.log(urls.cancel); // URL to cancel subscription
console.log(urls.update_payment_method); // URL to update payment
}Management URLs are only visible to the accountable party, not to members or hosts of the organization.
Quotas and Organizations
When users create resources for an organization, transfer accountability to ensure storage counts against the organization's quota:
// Personal document (counts against user's quota)
const personalDoc = await lr.Record.createKeyValue(
{ title: 'My Notes' },
[['$it', 'isA', 'Document']]
);
// Organization document (counts against org's quota)
const orgDoc = await lr.Record.createKeyValue(
{ title: 'Project Spec' },
[
['$it', 'isA', 'Document'],
[organizationId, '$isAccountableFor', '$it'],
]
);This pattern allows organizations to purchase larger quotas and have team members' work count against the shared quota rather than individual limits.
Quota Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
DEFAULT_STORAGE_SIZE_QUOTA | Default storage for new users (MB) | 500 |
QUOTA_COUNT_KV_RECORDS | Count KeyValue records in quota | false |
QUOTA_COUNT_LT_RECORDS | Count LongText records in quota | false |
Blob records are always counted in quota calculations when S3 storage is configured.
Database-Level Quota Assignment
For administrative purposes, quotas can be assigned directly in the database:
INSERT INTO quota_events (node_id, total_storage_available, valid_from)
VALUES ('us-user-id-here', 1073741824, NOW()); -- 1 GBThis is useful for:
- Testing quota behavior
- Granting quotas outside the payment flow
- Administrative overrides
Best Practices
-
Set appropriate defaults - Configure
DEFAULT_STORAGE_SIZE_QUOTAbased on your expected usage patterns -
Use organization quotas - For team features, assign quotas to organizations rather than individual users
-
Handle violations gracefully - Use
setQuotaViolationErrorHandlerto show helpful messages when users hit limits -
Monitor usage - Periodically check quota usage to identify users who may need upgrades
-
Transfer accountability - Ensure team resources are accountable to the organization so quota is correctly tracked