diff --git a/apps/docs/content/docs/developers/api/rate-limits.mdx b/apps/docs/content/docs/developers/api/rate-limits.mdx
index ecd50558e..d2e31b1d4 100644
--- a/apps/docs/content/docs/developers/api/rate-limits.mdx
+++ b/apps/docs/content/docs/developers/api/rate-limits.mdx
@@ -65,3 +65,4 @@ When you exceed a resource limit:
- [Authentication](/docs/developers/getting-started/authentication) - API authentication guide
- [API Versioning](/docs/developers/api/versioning) - API version management
- [First API Call](/docs/developers/getting-started/first-api-call) - Getting started with the API
+- [Organisation Limits](/docs/self-hosting/configuration/organisation-limits) - Admins: set per-organisation resource quotas and rate limits (the HTTP rate limit above is separate and not admin-settable)
diff --git a/apps/docs/content/docs/policies/enterprise-edition.mdx b/apps/docs/content/docs/policies/enterprise-edition.mdx
index a611db5cd..f821e73ec 100644
--- a/apps/docs/content/docs/policies/enterprise-edition.mdx
+++ b/apps/docs/content/docs/policies/enterprise-edition.mdx
@@ -76,6 +76,8 @@ The Enterprise Edition is required when you:
4. Restart your Documenso instance
5. Verify the license is active in the **Admin Panel** under the **Stats** section
+ See [Apply Your License Key](/docs/self-hosting/configuration/license) for the full walkthrough, including how to enable individual features once licensed.
+
@@ -197,7 +199,7 @@ See [Support](/docs/policies/support) for complete support options.
1. Sign the Enterprise license agreement
2. Receive license key and access credentials
3. Deploy using [self-hosting guides](/docs/self-hosting) or access Documenso Cloud
- 4. Configure Enterprise features with support assistance
+ 4. Apply the key — see [Apply Your License Key](/docs/self-hosting/configuration/license) — and configure Enterprise features with support assistance
@@ -238,6 +240,7 @@ See [Support](/docs/policies/support) for complete support options.
## Related
+- [Apply Your License Key](/docs/self-hosting/configuration/license) - Step-by-step license activation
- [Community Edition](/docs/policies/community-edition) - AGPL-3.0 open-source license
- [Licenses](/docs/policies/licenses) - Complete licensing overview and FAQ
- [Support](/docs/policies/support) - Support channels and response times
diff --git a/apps/docs/content/docs/self-hosting/configuration/environment.mdx b/apps/docs/content/docs/self-hosting/configuration/environment.mdx
index 4b910b43b..bc4acd1a6 100644
--- a/apps/docs/content/docs/self-hosting/configuration/environment.mdx
+++ b/apps/docs/content/docs/self-hosting/configuration/environment.mdx
@@ -443,11 +443,11 @@ Telemetry collects only: app version, installation ID, and node ID. No personal
## Enterprise Features
-These variables require an active [Enterprise Edition](/docs/policies/enterprise-edition) license. Obtain a license key from [license.documenso.com](https://license.documenso.com) and set it below to unlock enterprise features such as SSO, embed editor, and 21 CFR Part 11 compliance.
+These variables require an active [Enterprise Edition](/docs/policies/enterprise-edition) license. Obtain a license key from [license.documenso.com](https://license.documenso.com) and set it below to unlock enterprise features such as SSO, embed editor, and 21 CFR Part 11 compliance. See [Apply Your License Key](/docs/self-hosting/configuration/license) for step-by-step setup.
| Variable | Description |
| ------------------------------------ | ------------------------------------------------ |
-| `NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY` | License key for enterprise features |
+| `NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY` | License key for enterprise features — see [Apply Your License Key](/docs/self-hosting/configuration/license) for how to apply it |
| `NEXT_PRIVATE_STRIPE_API_KEY` | Stripe API key for billing |
| `NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET` | Stripe webhook secret |
| `NEXT_PRIVATE_SES_ACCESS_KEY_ID` | AWS SES access key for email domain verification |
@@ -510,4 +510,5 @@ NEXT_PRIVATE_SIGNING_PASSPHRASE="your-certificate-password"
- [Email Configuration](/docs/self-hosting/configuration/email) - Configure email delivery
- [Storage Configuration](/docs/self-hosting/configuration/storage) - Set up S3 storage
- [Signing Certificate](/docs/self-hosting/configuration/signing-certificate) - Configure document signing
+- [Organisation Limits](/docs/self-hosting/configuration/organisation-limits) - Set per-organisation document, email, and API limits from the admin panel
- [Troubleshooting](/docs/self-hosting/maintenance/troubleshooting) - Common configuration issues
diff --git a/apps/docs/content/docs/self-hosting/configuration/index.mdx b/apps/docs/content/docs/self-hosting/configuration/index.mdx
index 0e80e0efc..de5830a4d 100644
--- a/apps/docs/content/docs/self-hosting/configuration/index.mdx
+++ b/apps/docs/content/docs/self-hosting/configuration/index.mdx
@@ -29,6 +29,11 @@ description: Configure your self-hosted Documenso instance with environment vari
description="Digital signature certificate setup."
href="/docs/self-hosting/configuration/signing-certificate"
/>
+
## Required Configuration
diff --git a/apps/docs/content/docs/self-hosting/configuration/license.mdx b/apps/docs/content/docs/self-hosting/configuration/license.mdx
new file mode 100644
index 000000000..eea50c154
--- /dev/null
+++ b/apps/docs/content/docs/self-hosting/configuration/license.mdx
@@ -0,0 +1,107 @@
+---
+title: Apply Your License Key
+description: Activate your Enterprise license key to unlock enterprise features on your self-hosted instance.
+---
+
+import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
+import { Callout } from 'fumadocs-ui/components/callout';
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+
+A license key activates the Enterprise features available to your self-hosted instance, such as CSC signing, SSO, embed white-labelling, and 21 CFR Part 11 compliance.
+
+
+ The license key applies to your **whole instance**, not an individual user account. There's one
+ key per deployment.
+
+
+## Prerequisites
+
+- An active Enterprise license key — contact [sales](https://documen.so/enterprise) to set up an
+ Enterprise subscription, then copy your key from [license.documenso.com](https://license.documenso.com).
+ See [Enterprise Edition](/docs/policies/enterprise-edition) for details.
+- A running self-hosted Documenso instance that you're able to restart
+
+## Step 1: Set the environment variable
+
+Set `NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY` to your license key.
+
+
+
+
+Add the variable to your `.env` file (or directly under `environment:` in `compose.yml`):
+
+```bash
+NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY=your-license-key-here
+```
+
+Then apply it:
+
+```bash
+docker compose up -d
+```
+
+
+
+
+```bash
+docker run -d \
+ --name documenso \
+ -e NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY=your-license-key-here \
+ documenso/documenso:latest
+```
+
+
+
+
+If you're running Documenso directly (not in a container), add the variable to your `.env` file:
+
+```bash
+NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY=your-license-key-here
+```
+
+
+
+
+## Step 2: Restart the instance
+
+The license key is only read once, at process startup. Setting the variable in a running container or shell has no effect until the process restarts.
+
+```bash
+# Docker Compose
+docker compose restart documenso
+
+# Docker
+docker restart documenso
+```
+
+On startup, Documenso validates the key against the Documenso license server and caches the result locally for future startups, so a brief license-server outage won't lock you out.
+
+## What the license enables
+
+A valid license doesn't turn every enterprise feature on everywhere — activation depends on the feature:
+
+- **CSC signing** activates instance-wide automatically once the license is active and CSC transport is configured. See [CSC / QES Signing](/docs/self-hosting/configuration/signing-certificate/csc-qes) for the full setup.
+- **SSO, embed white-labelling, 21 CFR Part 11, and similar** are provisioned per organisation. Follow each feature's own guide to configure it once the license is active.
+
+## Troubleshooting
+
+
+
+ - Confirm the key is present in the environment the running process actually reads — `docker
+ exec` into the container and check `env | grep LICENSE` if unsure.
+ - Confirm the instance was fully restarted after the variable was set, not just reloaded.
+ - Re-copy the key to rule out truncation or accidental whitespace.
+
+
+ Instance-wide features (like CSC signing) also need their own configuration — an active license
+ alone isn't enough. Check that feature's guide to confirm the required settings are in place.
+ Per-organisation features additionally need to be provisioned for the organisation that's using
+ them.
+
+
+
+## See Also
+
+- [Environment Variables](/docs/self-hosting/configuration/environment) - Complete configuration reference
+- [Enterprise Edition](/docs/policies/enterprise-edition) - What's included and how to purchase a license
+- [CSC / QES Signing](/docs/self-hosting/configuration/signing-certificate/csc-qes) - Enable CSC-based signing
diff --git a/apps/docs/content/docs/self-hosting/configuration/meta.json b/apps/docs/content/docs/self-hosting/configuration/meta.json
index 32b92f853..6cc250f64 100644
--- a/apps/docs/content/docs/self-hosting/configuration/meta.json
+++ b/apps/docs/content/docs/self-hosting/configuration/meta.json
@@ -2,12 +2,14 @@
"title": "Configuration",
"pages": [
"environment",
+ "license",
"database",
"email",
"storage",
"background-jobs",
"signing-certificate",
"telemetry",
+ "organisation-limits",
"advanced"
]
}
diff --git a/apps/docs/content/docs/self-hosting/configuration/organisation-limits.mdx b/apps/docs/content/docs/self-hosting/configuration/organisation-limits.mdx
new file mode 100644
index 000000000..2459812ce
--- /dev/null
+++ b/apps/docs/content/docs/self-hosting/configuration/organisation-limits.mdx
@@ -0,0 +1,111 @@
+---
+title: Organisation Limits
+description: View and set per-organisation document, email, and API limits on a self-hosted Documenso instance using the admin panel's subscription claims.
+---
+
+import { Callout } from 'fumadocs-ui/components/callout';
+
+Per-organisation limits — document, email, and API usage, plus feature toggles and team/member caps — are controlled by **subscription claims**. You configure them in the admin panel, not through environment variables.
+
+There are three distinct kinds of limit:
+
+| Limit | Caps | Admin-settable |
+| ---------------------- | ------------------------------------------------- | ----------------------- |
+| Resource quota | Documents, emails, and API requests **per month** | Yes — per claim and org |
+| Resource rate limit | The same resources over a short window (e.g. `1h`) | Yes — per claim and org |
+| Global HTTP rate limit | API requests per IP (100/min, hardcoded) | No — see [Limitations](#limitations) |
+
+## Prerequisites
+
+- A running self-hosted Documenso instance.
+- An account with the **`ADMIN`** role — an account-level role, separate from organisation and team roles. New accounts are created with the `USER` role only. Grant the first admin by adding `ADMIN` to that user's `roles` directly in the database; after that, an existing admin can grant the role to others under **Admin Panel > Users > _(user)_ > Roles > Update user**.
+
+Open the admin panel at `/admin`. The sidebar sections used below are **Claims**, **Organisations**, and **Organisation Stats**.
+
+## Viewing usage
+
+**One organisation:** open **Admin Panel > Organisations** and select it. The **Organisation usage** section shows the current period's document, email, and API usage against its quotas.
+
+**All organisations:** open **Admin Panel > Organisation Stats** to sort and filter monthly usage. Filter by **claim** and by **period** (a UTC calendar month, shown as `YYYY-MM`), and switch between **Show usage**, **Show usage with quotas**, and **Show daily averages**.
+
+
+ Usage counts **attempts**, not only successful actions. A request that exceeds a quota is still counted before it is rejected, so displayed usage can read higher than the number of actions that succeeded.
+
+
+## Subscription claims
+
+A subscription claim is a named bundle of limits and feature flags (for example `Free`, `Individual`, `Teams`, `Platform`, or `Enterprise`). Claims are **templates**: when an organisation is created it receives a private copy of its claim and reads from that copy afterwards. Editing a claim template therefore affects organisations created later, not existing ones — to change an existing organisation, [edit it directly](#change-limits-for-one-organisation).
+
+### Claim fields
+
+Under **Admin Panel > Claims** (`/admin/claims`), each claim has:
+
+| Field | Controls |
+| ----------------------- | --------------------------------------------------------------------------------- |
+| **Name** | The claim's display name. |
+| **Team Count** | Teams allowed. `0` = unlimited. |
+| **Member Count** | Members allowed. `0` = unlimited. |
+| **Envelope Item Count** | Uploaded files allowed per envelope. Minimum `1`. |
+| **Recipient Count** | Recipients allowed per document. `0` = unlimited. |
+| **Feature Flags** | Feature toggles (see [Feature flags](#feature-flags)). |
+| **Limits** | Monthly quota and rate-limit windows for Documents, Emails, and API. |
+| **Email transport** | Transport the claim uses. *Default (system mailer)* uses the instance default. |
+
+### Quotas and rate limits
+
+The **Limits** section has a column for **Documents**, **Emails**, and **API**, each with two controls:
+
+- **Monthly quota** — how many of that resource are allowed per calendar month. An **empty** field is unlimited; **`0`** blocks the resource entirely.
+- **Rate limit windows** — optional short-window caps, each a duration and a maximum. A window is a number and a unit (`s`, `m`, `h`, `d`), such as `5m`, `1h`, or `24h`, and must be unique within the resource.
+
+
+ Quotas and counts use opposite conventions for "unlimited": an **empty** quota is unlimited (and `0` blocks the resource), whereas `0` in the **Team**, **Member**, and **Recipient Count** fields means unlimited.
+
+
+### Feature flags
+
+The **Feature Flags** section toggles capabilities such as Unlimited documents, Branding, Hide Documenso branding, Email domains, Embed authoring, Embed signing, White label for embed authoring/signing, 21 CFR, HIPAA, Authentication portal, Allow Legacy Envelopes, Signing reminders, QES signing, and Disable emails.
+
+Some flags are Enterprise features. If your license does not include one, it is marked and cannot be enabled (you can still turn it off). See [Enterprise Edition](/docs/policies/enterprise-edition).
+
+### Create or edit a claim template
+
+1. Go to **Admin Panel > Claims**.
+2. Select **New claim**, or select an existing claim to edit it.
+3. Set the counts, feature flags, and the **Limits** section.
+4. Save. Changes apply to organisations created afterwards, not existing ones.
+
+### Change limits for one organisation
+
+To change limits for an existing organisation, edit it directly rather than its claim template.
+
+1. Go to **Admin Panel > Organisations** and open the organisation.
+2. Adjust its quota, rate-limit, feature-flag, or email-transport fields.
+3. Save. Changes take effect immediately.
+
+The organisation also shows the **Inherited subscription claim** it was created from.
+
+## Usage reset
+
+Monthly quota usage is keyed to the **UTC calendar month**. There is no scheduled reset job — when the month rolls over, the new period's counter starts at `0`.
+
+## Limitations
+
+The **global HTTP rate limit is not configurable.** Documenso enforces a hardcoded **100 requests per minute per IP address** on its API endpoint groups (`/api/v1`, `/api/v2`, and the tRPC API are limited separately), returning `429 Too Many Requests`. It is a per-IP safeguard applied at the HTTP layer — not per-organisation, not stored on any claim, and not adjustable from the admin panel. See [Rate Limits](/docs/developers/api/rate-limits).
+
+## Troubleshooting
+
+| Symptom | Cause and fix |
+| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
+| An organisation hit its limit unexpectedly | Usage counts rejected over-quota attempts. Compare usage against the quota under **Organisation Stats > Show usage with quotas**. |
+| A resource is blocked entirely, not just capped | The **Monthly quota** is `0`, which blocks the resource. Leave it empty for unlimited. |
+| Emails are not sending for an organisation | Check whether the **Disable emails** flag is enabled on the organisation's claim — it blocks all emails regardless of quota. |
+| A claim template edit had no effect | Template edits are not retroactive. Edit the organisation directly under **Admin Panel > Organisations**. |
+
+---
+
+## See Also
+
+- [Environment Variables](/docs/self-hosting/configuration/environment) - All configuration options
+- [Rate Limits](/docs/developers/api/rate-limits) - The global HTTP API rate limit (separate from claims)
+- [Enterprise Edition](/docs/policies/enterprise-edition) - Features unlocked by license flags
diff --git a/apps/docs/content/docs/self-hosting/configuration/signing-certificate/csc-qes.mdx b/apps/docs/content/docs/self-hosting/configuration/signing-certificate/csc-qes.mdx
index 0495430d7..4998baaf7 100644
--- a/apps/docs/content/docs/self-hosting/configuration/signing-certificate/csc-qes.mdx
+++ b/apps/docs/content/docs/self-hosting/configuration/signing-certificate/csc-qes.mdx
@@ -49,7 +49,7 @@ The callback URL is fixed — Documenso derives it from `NEXT_PUBLIC_WEBAPP_URL`
### Enterprise Edition license
-CSC mode is gated by the `instanceCscSigning` license flag. Without a valid Enterprise license, the transport refuses to start (`CSC_UNLICENSED`).
+CSC mode is gated by the `instanceCscSigning` license flag. Without a valid Enterprise license, the transport refuses to start (`CSC_UNLICENSED`). See [Apply Your License Key](/docs/self-hosting/configuration/license) to activate one.
diff --git a/apps/docs/content/docs/self-hosting/index.mdx b/apps/docs/content/docs/self-hosting/index.mdx
index 3f8f8d3c2..99e4ecb01 100644
--- a/apps/docs/content/docs/self-hosting/index.mdx
+++ b/apps/docs/content/docs/self-hosting/index.mdx
@@ -141,7 +141,7 @@ See the [Quick Start guide](/docs/self-hosting/getting-started/quick-start) for
Self-hosted Documenso includes full core functionality under the AGPL-3.0 license. If you need enterprise features such as SSO, embed editor white label, or 21 CFR Part 11 compliance, you can activate them with a license key.
-See [Enterprise Edition](/docs/policies/enterprise-edition) for details and [Licenses](/docs/policies/licenses) for a comparison.
+See [Enterprise Edition](/docs/policies/enterprise-edition) for details and [Licenses](/docs/policies/licenses) for a comparison. Already have a key? See [Apply Your License Key](/docs/self-hosting/configuration/license).
---
diff --git a/apps/remix/app/components/dialogs/folder-create-dialog.tsx b/apps/remix/app/components/dialogs/folder-create-dialog.tsx
index c2cf21eaa..395feaf37 100644
--- a/apps/remix/app/components/dialogs/folder-create-dialog.tsx
+++ b/apps/remix/app/components/dialogs/folder-create-dialog.tsx
@@ -1,3 +1,4 @@
+import { ZNameSchema } from '@documenso/lib/types/name';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
@@ -23,7 +24,7 @@ import { useParams } from 'react-router';
import { z } from 'zod';
const ZCreateFolderFormSchema = z.object({
- name: z.string().min(1, { message: 'Folder name is required' }),
+ name: ZNameSchema,
});
type TCreateFolderFormSchema = z.infer;
@@ -65,7 +66,7 @@ export const FolderCreateDialog = ({ type, trigger, parentFolderId, ...props }:
toast({
description: t`Folder created successfully`,
});
- } catch (err) {
+ } catch (_err) {
toast({
title: t`Failed to create folder`,
description: t`An unknown error occurred while creating the folder.`,
diff --git a/apps/remix/app/components/dialogs/folder-delete-dialog.tsx b/apps/remix/app/components/dialogs/folder-delete-dialog.tsx
index e05f943b3..aca0e7e31 100644
--- a/apps/remix/app/components/dialogs/folder-delete-dialog.tsx
+++ b/apps/remix/app/components/dialogs/folder-delete-dialog.tsx
@@ -122,7 +122,7 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
Confirm by typing:{' '}
- {deleteMessage}
+ {deleteMessage}
diff --git a/apps/remix/app/components/dialogs/folder-update-dialog.tsx b/apps/remix/app/components/dialogs/folder-update-dialog.tsx
index 1c57bc27d..db859431a 100644
--- a/apps/remix/app/components/dialogs/folder-update-dialog.tsx
+++ b/apps/remix/app/components/dialogs/folder-update-dialog.tsx
@@ -1,5 +1,6 @@
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
+import { ZNameSchema } from '@documenso/lib/types/name';
import { trpc } from '@documenso/trpc/react';
import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
import { Button } from '@documenso/ui/primitives/button';
@@ -23,8 +24,6 @@ import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
-import { useOptionalCurrentTeam } from '~/providers/team';
-
export type FolderUpdateDialogProps = {
folder: TFolderWithSubfolders | null;
isOpen: boolean;
@@ -32,7 +31,7 @@ export type FolderUpdateDialogProps = {
} & Omit;
export const ZUpdateFolderFormSchema = z.object({
- name: z.string().min(1),
+ name: ZNameSchema,
visibility: z.nativeEnum(DocumentVisibility).optional(),
});
@@ -40,7 +39,6 @@ export type TUpdateFolderFormSchema = z.infer;
export const FolderUpdateDialog = ({ folder, isOpen, onOpenChange }: FolderUpdateDialogProps) => {
const { t } = useLingui();
- const team = useOptionalCurrentTeam();
const { toast } = useToast();
const { mutateAsync: updateFolder } = trpc.folder.updateFolder.useMutation();
diff --git a/apps/remix/app/components/dialogs/organisation-create-dialog.tsx b/apps/remix/app/components/dialogs/organisation-create-dialog.tsx
index 183dfbeec..c76bc00a2 100644
--- a/apps/remix/app/components/dialogs/organisation-create-dialog.tsx
+++ b/apps/remix/app/components/dialogs/organisation-create-dialog.tsx
@@ -336,7 +336,7 @@ const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }:
>