Compare commits

...
193 Commits
Author SHA1 Message Date
github-actions bd2ee16fc4 chore: extract translations 2026-08-14 10:04:34 +00:00
Catalin Pit 688ef2fdf3 fix: docker healtcheck (#3176) 2026-08-14 13:02:48 +03:00
Konrad a5e37af3e8 chore: update Polish translations (#3210) 2026-08-14 10:40:42 +10:00
David Nguyen 617f8cc204 feat: show feature gated setting pages (#3167) 2026-08-12 17:33:08 +10:00
Christopher Ryan 1bd09480e6 feat(remix): support serving app under a sub-path via NEXT_PUBLIC_BASE_PATH (#2824) 2026-08-12 16:12:28 +10:00
github-actions[bot] 962cffc9f5 chore: extract translations (#3163) 2026-08-10 15:17:49 +10:00
David Nguyen 820b319474 fix: restore overriden translations (#3161) 2026-08-10 15:13:45 +10:00
David Nguyen 797f5c0e79 fix: improve unified settings (#3160) 2026-08-10 14:57:19 +10:00
Lucas Smith fc95ee9ead fix: handle completion when already signed (#3159)
Previously attempting to complete a document which is already completed
you'd get a generic error toast. Now when completing a document that you
have already completed you are redirected to the completed page.

Handles cases where two mutations managed to fire racing eachother.
2026-08-10 09:25:41 +10:00
David Nguyen d6cf3fec4b feat: unify settings (#3128) 2026-08-09 16:00:55 +10:00
Lucas Smith f0ab7c112e fix: add more logging for errors on sign or complete (#3151) 2026-08-05 11:29:38 +10:00
Lucas Smith 8bfcec8ee6 fix: add logging for errors on sign or complete (#3149) 2026-08-05 08:12:55 +10:00
Lucas Smith 9c27ce6d18 feat: replace document status tabs with filter pills (#3145)
Swaps the tab row and dropdowns for faceted filter pills (status,
sender, period) with a shared reset, and moves URL param handling
to nuqs.

<img width="2198" height="1674" alt="image"
src="https://github.com/user-attachments/assets/6996431c-09c8-45c3-bc30-f0a1e503c941"
/>
2026-08-03 22:52:11 +10:00
Ephraim Duncan b3c609a549 feat: bulk download documents (#2711) 2026-08-03 20:55:43 +10:00
Ephraim Duncan 29020bcbed docs(webhooks): correct retry policy, timeout and payload reference (#3132)
## Description

Corrects the webhooks documentation, which described delivery behavior
that does not exist in the implementation.

## Changes Made

- Replaced the fabricated retry schedule (5 attempts / immediate-to-2h
backoff) with the real provider-dependent behavior: retries belong to
the job provider (`NEXT_PRIVATE_JOBS_PROVIDER`) — local (default) 4
total attempts back-to-back, BullMQ 3 attempts with exponential backoff
from 1s, Inngest 5 attempts with platform backoff.
- Fixed the webhook timeout from 30 seconds to 10 seconds
(`WEBHOOK_TIMEOUT_MS = 10_000`, hard abort).
- Clarified failure semantics: non-2xx fails, 3xx redirects are not
followed (`redirect: 'manual'`), network/SSRF-blocked calls record
response code 0; failed deliveries mark only the `WebhookCall` record —
the webhook itself is never auto-disabled.
- Corrected URL requirements: `http://` is accepted; documented the SSRF
guard (private/loopback blocked,
`NEXT_PRIVATE_WEBHOOK_SSRF_BYPASS_HOSTS` bypass for self-hosters).
- Added `envelopeId` to both field tables and all payload/recipient JSON
examples; framed numeric `id` as the legacy v1 identifier.
- Removed a documented `documentMeta` field that exists in neither the
Zod schema nor Prisma; fixed timezone/dateFormat examples to the
hardcoded `Etc/UTC` / `yyyy-MM-dd hh:mm a` values.
- Added missing `REJECTED`/`CANCELLED` statuses and
`TEMPLATE_DIRECT_LINK` source; fixed `templateId` to `null` on
TEMPLATE_* examples; documented the previously missing
`RECIPIENT_EXPIRED` event across setup, events, and verification pages.

## Testing Performed

Docs-only change. Every claim verified against the implementation
(`execute-webhook-call.ts`, job clients, `webhook-payload.ts`,
`assert-webhook-url.ts`, webhook-router schema).
2026-08-03 12:16:39 +10:00
Ephraim Duncan 6ec67d1c4d feat: rejected and expired recipient filters (#2889) 2026-07-30 10:41:45 +10:00
Ephraim Duncan a457e1ef7d feat: add copy button for license key in admin panel (#3123)
## Description

Adds a copy button next to the show/hide toggle for the license key in
the admin panel license card (Admin Panel → Stats).

- Ghost button matching the existing eye-toggle styling (`h-6 w-6`,
`CopyIcon`)
- Uses the standard `useCopyToClipboard` + toast pattern (same as
`template-direct-link-badge.tsx`)
- Copies the key regardless of masked/visible state

New translation strings will be picked up by the next `chore: extract
translations` run.

## Before / After

![Before and after: copy button added next to the license key show/hide
toggle](https://artifacts.duncan.land/documenso-pr3123-license-copy)

> Screenshots taken against a locally mocked ACTIVE license (the mock is
not part of this PR).

## Testing

- `npx tsc --noEmit -p apps/remix` clean
- `biome check` clean
- Smoke-tested in browser: clicking the button fires the "Copied to
clipboard" toast (visible in the screenshot above)
2026-07-27 02:18:03 +00:00
Catalin Pit e4897fa686 fix: sort CC recipients last (#2930) 2026-07-24 16:29:46 +10:00
David Nguyen c02dfaba1a feat: rework command search (#3109) 2026-07-23 13:16:44 +09:00
David Nguyen 54befb5962 fix: update stripe team member billing (#2991) 2026-07-23 14:09:06 +10:00
David Nguyen 26f0c4c5b7 chore: deprecate endpoints (#3022) 2026-07-23 13:57:09 +10:00
Lucas Smith 7f85388eb7 fix: increase global API rate limits to 1000/min (#3081) 2026-07-21 15:58:36 +10:00
Lucas Smith 3cf2963cd0 v2.16.0 2026-07-21 15:06:36 +10:00
Catalin Pit cc5ef3df16 feat: add branding preferences reset dialog (#3032) 2026-07-20 16:40:25 +09:00
Catalin Pit 4b72e7d546 feat: add document preferences reset dialog (#3039) 2026-07-20 16:02:39 +09:00
David Nguyen ba0dead96f fix: render error messages for invalid templates (#3088)
Currently direct templates can be created without the required
signatures fields for signers.

This means that the document can be fully signed by everyone but will
ultimately fail the sealing step which leaves the document in an
unrecoverable state.
2026-07-20 16:57:38 +10:00
Lucas Smith 40472bc26c fix: react hydration errors (#3099)
Move PostHog init out of the React tree so the client tree matches
the server render (mismatched useIds could abort hydration, leaving
dead event handlers). Also expose the CSP nonce so Radix scroll-lock
styles aren't blocked.
2026-07-16 12:21:43 +10:00
Ephraim Duncan 3ff7f70a7d feat: redesign api tokens settings page (#3076) 2026-07-15 12:42:13 +09:00
Lucas Smith 5c41740859 chore: deps upgrade 2026-07-14 (#3095) 2026-07-14 23:15:45 +10:00
David Nguyen d6268b1d7d fix: missing noreply email for resend (#3094) 2026-07-14 20:13:40 +09:00
Ephraim Duncan 12223c79cb fix(ui): improve destructive button contrast in dark mode (#3071) 2026-07-14 18:40:40 +09:00
David Nguyen b16f979eb3 fix: improve editor autosave (#3057) 2026-07-14 17:44:22 +09:00
Ephraim Duncan db031e2865 fix: replace tailwind class typos 2026-07-14 17:21:30 +09:00
github-actions[bot] 4e0038f2e8 chore: extract translations (#3060) 2026-07-14 17:16:21 +09:00
Lucas Smith c5efd34e95 v2.15.0 2026-07-13 12:43:22 +10:00
roshboi 21cff7a727 docs: update rate limit doc for organisation limit for self hosters (#3089) 2026-07-12 14:13:23 +08:00
Tanushree AhirandCatalin Pit 400b6a24f1 feat: enable real-time validation for email and password fields (#2204)
## Description

This MR improves the real-time validation experience on the signup page.
Currently, both the email and password fields validate only on blur,
causing users to continue seeing error messages even after correcting
their input until they click outside the field.

This update switches the React Hook Form configuration from mode:
'onBlur' to mode: 'onChange', ensuring validations update immediately as
the user types.
This results in a smoother and less confusing signup experience.

## Related Issue

Fixes #2200

## Changes Made

- Updated React Hook Form configuration in the signup form from:
- mode: 'onBlur' ➜ mode: 'onChange'
- Ensures real-time validation for email and password fields
- No schema or logic changes — only validation trigger updated


## Testing Performed

- Manually tested the signup page for:
- Real-time password rule updates
- Real-time email validation feedback
- Error disappearance immediately after correcting input

## Checklist

- [x] I have tested these changes locally and they work as expected.
- [ ] I have added/updated tests that prove the effectiveness of these
changes.
- [x] I have updated the documentation to reflect these changes, if
applicable.
- [x] I have followed the project's coding style guidelines.
- [x] I have addressed the code review feedback from the previous
submission, if applicable.

## Additional Notes

This is a small UX-focused improvement; the validation schema remains
unchanged.

Co-authored-by: Catalin Pit <catalinpit@gmail.com>
2026-07-10 13:41:31 +03:00
roshboi 1b1e3d197b Add "Apply Your License Key" self-hosting guide (#3077)
Adds a dedicated self-hosting page documenting how to apply an
Enterprise license key, and cross-links it from the related docs.

New page: self-hosting/configuration/license.mdx — "Apply Your License
Key"
Set NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY (Docker Compose / docker run /
.env tabs)
2026-07-08 16:11:49 +10:00
Lucas Smith a276e18e1f chore: upgrade deps (#3065) 2026-07-08 16:11:00 +10:00
Catalin Pit 50f272be87 fix: admin organisation limits and usage UI (#3014) 2026-07-02 16:50:11 +10:00
Catalin Pit a55e6d9484 fix: block invisible & control characters and URLs in names (#2978) 2026-07-02 16:20:56 +10:00
David Nguyen d35d13db23 fix: remove presigned branding upload (#3053) 2026-07-02 15:51:19 +10:00
Lucas Smith 337f85f021 chore: upgrade libpdf (#3058) 2026-07-02 15:09:07 +10:00
github-actions[bot] 2332b0316b chore: extract translations (#3013) 2026-07-02 14:58:56 +10:00
David Nguyen 393b51d484 fix: add sticky form update button (#3056) 2026-07-02 14:52:28 +10:00
Arun Kumar 5a8335e0eb fix: webhook payload contains stale deletedAt on document cancellation (#2980) 2026-07-01 17:19:21 +10:00
Kendry Grullon 562d78e2d7 feat: add granular signin disable flags and OIDC auto-redirect (#2857) 2026-06-30 16:08:09 +10:00
Grégory Chevalier 3b110cf70d fix: french translation for confirmation message (#3050) 2026-06-30 15:52:59 +10:00
David Nguyen 7062fadf0b fix: add additional team group permission checks (#3052) 2026-06-30 15:45:48 +10:00
Martin Glaser 9cdd2e7ff9 fix(email): render Preview inside Body across all email templates (#3004) 2026-06-29 16:07:43 +10:00
David Nguyen a70b0702c3 fix: add missing teams branding guard (#3049) 2026-06-29 14:50:35 +10:00
David Nguyen 1f170ef5e5 fix: scope organisation group deletion (#3047) 2026-06-29 14:11:31 +10:00
Lucas Smith 8f68393241 fix: tighten permission and validation checks (#3046) 2026-06-29 13:15:13 +10:00
Lucas Smith 381293af0c fix: invite email placeholder (#3045)
- **fix: interpolate inviterEmail in invite email mailto link**
- **fix: add alt attributes to email template images**
2026-06-28 22:01:20 +10:00
David Nguyen 97835b8dbb feat: add field multiselect (#3031) 2026-06-28 15:08:11 +10:00
David Nguyen 977d07330b fix: auto select field on drop (#3028) 2026-06-28 15:07:33 +10:00
Lucas Smith 037170f625 v2.14.0 2026-06-28 12:38:38 +10:00
Lucas Smith c219305eb1 chore: add tests for cert and audit log download via api (#3043) 2026-06-27 19:54:14 +10:00
David Nguyen 96ab78c33f fix: resolve permission issues (#3029) 2026-06-26 14:46:30 +10:00
Lucas Smith 241929bb97 feat: add API endpoints for downloading certificate and audit log PDFs (#3025)
Add V2 API routes to download an envelope's certificate and audit log
separately, and align the internal cert/audit log downloads to use
envelopeId.

Enforces document visibility via getEnvelopeWhereInput and loads field
signatures so certificates render correctly.
2026-06-26 00:01:55 +10:00
Lucas Smith 94adea149d chore: general repo maintenance and docs cleanup (#3030)
Refresh README/docs for the current stack, add a security policy,
note the external-PR pause, and remove dead config and workflows.
2026-06-25 23:59:25 +10:00
David Nguyen 9c5eb43a26 fix: migrate emails to jobs (#3024) 2026-06-25 13:57:17 +10:00
Konrad e0ef11e8c3 chore(i18n): update Polish translation (#3020) 2026-06-25 13:24:40 +10:00
Lucas Smith 187b612568 chore: add translations (#3012) 2026-06-23 15:12:23 +10:00
Lucas Smith b37529a1cf fix: show warning on overlapping fields (#3017) 2026-06-23 15:11:57 +10:00
Lucas Smith 04f6e76178 feat: cap automated reminders before resend (#3016)
Stop sending automated reminders after a configurable threshold
(default 5) and reset the count on manual resend.
2026-06-23 15:11:52 +10:00
Lucas Smith f2525ae95b feat: add API endpoint to reject documents on behalf of recipients (#3007)
Programmatically record an external rejection on behalf of a recipient
who declined outside the platform. Flags the rejection as external in
the audit log, optionally attributes it to a specific team member via
actAsEmail, and enforces team membership and document visibility.
2026-06-22 21:59:07 +10:00
David Nguyen 2f24a8eab2 fix: set send status on resend (#3011) 2026-06-22 17:00:24 +10:00
David Nguyen d9b7722325 fix: correctly use default distribute envelope tab (#3010) 2026-06-22 16:27:50 +10:00
github-actions[bot] 783123f72b chore: extract translations (#2987) 2026-06-22 16:06:57 +10:00
Lucas Smith e8ed1c3d99 fix: respect branding enabled for recipient routes (#3009) 2026-06-22 16:06:06 +10:00
David Nguyen c23d739f76 feat: allow additional envelope duplicate settings (#3008) 2026-06-22 14:41:38 +10:00
Lucas Smith 0bf58ca66e feat: add custom brand colours to emails (#3005) 2026-06-22 14:33:34 +10:00
David Nguyen dee3259088 fix: remove old dialogs (#3006) 2026-06-22 14:17:22 +10:00
Nandini Dhanrale 6ad1a2dfaf fix: signing request email renders blank when organisation/team branding is enabled (#2968) 2026-06-22 14:15:12 +10:00
Abdelrahman Abdelhamed 306e7fe5ed fix: render unicode characters in typed signatures (#2728) 2026-06-22 13:40:56 +10:00
Yash Singh 219db32fdf fix: only send S3 checksums when required to support S3-compatible storage (#2984) 2026-06-22 13:35:37 +10:00
David Nguyen 948d1bbf12 fix: improve team member removal ux (#3001) 2026-06-22 12:16:55 +10:00
Lucas Smith 40d20ad068 v2.13.0 2026-06-18 16:03:26 +10:00
Lucas Smith a99bdf5e20 fix: include envelopeId in webhook payload (#2998) 2026-06-18 14:32:15 +10:00
Lucas Smith 4f346d3c2d feat: add cancellable document status (#2992)
Adds a CANCELLED envelope status that privileged members (owner or team
admin/manager) can move a pending document into. Sending recipient
notifications via a background job while retaining the document in the
dashboard as proof of distribution.

Includes a dedicated Cancelled tab, single and bulk cancel actions,
the ENVELOPE_CANCELLED mutability guard, and e2e coverage for
permissions
and visibility.
2026-06-18 13:52:35 +10:00
Lucas Smith d5ce222482 feat: add CSC AES/QES signing (v1 instance-wide config) (#2874)
Adds Cloud Signature Consortium (CSC) integration for AES/QES signing
against a configured TSP. v1 ships as instance-wide configuration via
environment variables, with per-envelope signature level selection,
license gating, and an OAuth-driven signing flow (capture + FIFO
signers, SAD session, blocking/in-progress recipient pages).

Includes signature level compatibility checks (role, signing order,
dictate next signer), envelope mutability assertions, Prisma migration
for signature level and CSC tables, and docs for the new signing
certificate options.
2026-06-16 23:37:34 +10:00
Lucas Smith 9b59f1a273 v2.12.0 2026-06-16 11:00:17 +10:00
David Nguyen 15549a6758 fix: add early quota warning (#2986)
## Description

- Add banners when plans near fair use limits
- Automated email alerts to our support when nearing fair use limits
2026-06-15 16:02:18 +10:00
Lucas Smith eb45d1e5a9 fix: reconcile billing when stripe subscription is missing (#2988)
Catch resource_missing in the subscription route, return null so the
billing page still loads, and fire a sync to converge the stale row.
2026-06-15 15:58:40 +10:00
Lucas Smith 0aa84cecc8 chore: add translations (#2975) 2026-06-15 14:29:42 +10:00
Lucas Smith 702e747375 fix: use correct property for embed edit editor config (#2985) 2026-06-15 14:29:13 +10:00
Lucas Smith 3887aa67c8 fix: rework stripe webhooks into idempotent subscription sync (#2977)
Replace per-event webhook handlers with a single sync function that
fetches the current state from Stripe and converges the local
subscription, claim, and organisation type.

- Create organisations upfront before checkout, restricted as
  "pending payment" until the first payment syncs
- Add rate-limited subscription sync route, triggered on checkout
  success so the UI doesn't wait on webhooks
- Surface pending payment state in banner, billing table, and limits
2026-06-12 16:01:03 +10:00
Konrad b84b87cea6 fix(i18n): add space between tag and text (#2972) 2026-06-11 10:31:19 +10:00
David Nguyen ac0a0086d6 chore: update verify email docs (#2970) 2026-06-10 13:09:53 +10:00
David Nguyen 8c11266747 fix: correctly orphan envelopes and stripe cancel on delete (#2967) 2026-06-09 15:52:14 +10:00
Ephraim Duncan 3c0345f755 fix: link signing brand logos (#2881) 2026-06-09 15:51:25 +10:00
Lucas Smith 3e47f1913c chore: add translations (#2929) 2026-06-09 15:48:50 +10:00
David Nguyen 9ccf50ed95 fix: correctly strip background job values (#2965) 2026-06-09 15:33:41 +10:00
Catalin Pit ecc98fbd41 feat: enhance document signing page with field canvas style integration (#2876) 2026-06-09 15:05:22 +10:00
Romone6 58f0f5da43 fix: load typed signature font on profile page (#2963) 2026-06-09 14:31:32 +10:00
David Nguyen d5c6cf4ad5 feat: allow changing field types (#2873) 2026-06-09 13:48:40 +10:00
github-actions[bot] 90462bf414 chore: extract translations (#2923) 2026-06-09 13:37:34 +10:00
David Nguyen 791a54bb8b chore: update docs trusted emails (#2964) 2026-06-09 13:33:54 +10:00
Rana Pratap Sarangi 5f4e0ccf6b fix: exclude rejected documents from inbox count (#2893) 2026-06-09 10:49:18 +10:00
Clayton Chew 583e35c768 fix: ensures new expire on setSessionCookie (#2708) 2026-06-09 10:47:19 +10:00
Ephraim Duncan 1e129580b8 fix: point Railway deploy button at the working template URL (#2962) 2026-06-09 10:05:45 +10:00
Arun Kumar 184cbd6770 fix: guard missing password reset token and fix broken reset link URL (#2928) 2026-06-08 14:31:26 +10:00
roshboi b53295a9d5 docs: clarify email verification instructions for free and paid plans (#2959) 2026-06-08 14:22:44 +10:00
David Nguyen 8448e333cf fix: update new quota and rates UX (#2954) 2026-06-08 14:14:22 +10:00
Ephraim Duncan 03b5fe6117 fix: update link-only notification settings (#2821) 2026-06-08 12:59:50 +10:00
Arun Kumar f60698a353 docs: add registration form screenshot to create account page (#2908) 2026-06-08 12:45:56 +10:00
Konrad 7c48ae6ff4 chore(i18n): fix typo in Polish translation (#2948) 2026-06-08 12:40:07 +10:00
David Nguyen 4ee789ea37 fix: add multi email transport system (#2942) 2026-06-05 21:19:20 +10:00
David Nguyen ebf5b75a19 fix: add email disable flag (#2931) 2026-06-05 17:55:10 +10:00
David Nguyen 0ecde7ac1e feat: add header metadata to emails (#2927) 2026-06-04 16:48:57 +10:00
Catalin Pit c41e387220 docs(embedding): document iframe URL fragment options (#2915) 2026-06-04 09:12:44 +03:00
David Nguyen 7f796ed74e fix: update org stats table ui (#2924) 2026-06-03 16:48:20 +10:00
Lucas Smith 0a21598fec chore: add translations (#2922) 2026-06-03 16:20:16 +10:00
David Nguyen 240bef1a66 fix: add org insight document complete stat (#2920) 2026-06-03 16:15:59 +10:00
github-actions[bot] 9583e79056 chore: extract translations (#2919) 2026-06-03 16:11:30 +10:00
David Nguyen 993a494784 fix: add email reporting (#2918) 2026-06-03 16:05:39 +10:00
Konrad 743d31651f fix(i18n): mark Bio string for translation (#2910) 2026-06-03 16:00:37 +10:00
Konrad ce96238464 fix(ui): direct signing templates window layout (#2909) 2026-06-03 16:00:24 +10:00
github-actions[bot] 8b8e7e9f2e chore: extract translations (#2867) 2026-06-03 15:54:52 +10:00
roshboi 50006ca053 fix: prevent sending emails for free organisation claims (#2917) 2026-06-03 14:28:00 +10:00
David Nguyen c3135a3ce7 fix: filter ccers from delete email (#2914) 2026-06-02 15:27:36 +10:00
David Nguyen d2f60b13fd fix: correctly log cc emails (#2913) 2026-06-02 15:04:02 +10:00
David Nguyen c50a01d004 fix: improve field signing (#2830) 2026-06-01 19:48:20 +10:00
David Nguyen 4bda501d51 feat: add stripe sync (#2877) 2026-06-01 18:17:16 +10:00
Lucas Smith a7713f7228 chore: add translations (#2885) 2026-06-01 17:27:46 +10:00
David Nguyen 536142be03 feat: add admin org stats (#2904) 2026-06-01 17:26:51 +10:00
David Nguyen 44c4826e92 fix: track monthly usage for unlimited quotas (#2894) 2026-05-31 13:34:10 +10:00
David Nguyen 61138cdd81 fix: add dynamic rate limits (#2892) 2026-05-31 00:34:28 +10:00
Lucas Smith 22ceff43e3 feat: admin-configurable email blocklist (#2884) 2026-05-29 01:12:55 +10:00
Lucas Smith a84da2f2c7 chore: disabled account enforcement (#2882) 2026-05-28 22:19:13 +10:00
Lucas Smith 7e8da85bd8 feat: block disposable email signups (#2883)
Reject disposable / throwaway email providers (mailinator, yopmail,
10minutemail, ...) across all signup paths: email/password, Google,
Microsoft, personal OIDC and organisation OIDC. Backed by the
mailchecker package (offline, ~55k domains, subdomain-aware).

Exposes a SIGNUP_DISPOSABLE_EMAIL error code so the signup form and
SSO redirect alert can show a dedicated message instead of the
generic 'signup disabled' one.
2026-05-28 21:15:27 +09:00
David Nguyen d304d8720c fix: add temp email rate limit (#2879) 2026-05-28 17:09:09 +10:00
Kendry Grullon 9da2db2e67 feat(storage): add native Azure Blob transport (#2871) 2026-05-27 11:58:39 +07:00
Nikhil Shukla 993df7dc21 fix(docs): correct broken internal docs links (#2869) 2026-05-27 12:39:48 +10:00
ザヘド 807d094cf2 fix: email dictated direct template signer (#2810) 2026-05-27 12:30:31 +10:00
Lucas Smith 3cef238f46 chore: add translations (#2854) 2026-05-26 15:40:11 +10:00
David Nguyen 886c40a46b fix: add constraint on name schema (#2866) 2026-05-26 15:39:35 +10:00
David Nguyen b1b82b775c fix: add missing doc page ref (#2865) 2026-05-26 15:18:20 +10:00
Anish Patil 0fe697c26c fix: handle duplicate organization URL update errors gracefully (#2808) 2026-05-26 14:56:23 +10:00
redouanegrib 7c0031679a docs: implement global error handling and troubleshooting matrix (#2784) 2026-05-26 14:55:40 +10:00
Durgesh Shekhawat 6bb0496224 fix: prevent division by zero in progress bar when requiredRecipientFields is empty (#2855) 2026-05-26 14:41:12 +10:00
Ephraim Duncan eedf483957 fix(prisma): stop large-team-seed running on import (#2852) 2026-05-26 14:13:12 +10:00
Abdulazez (Abza) 5421b0d1cc fix: prevent prop array mutation by spreading allRecipients before sort (#2840) 2026-05-26 14:09:54 +10:00
Abdulazez (Abza) fa2c53bd72 fix: prevent React state mutation by spreading envelope.recipients before sort (#2839) 2026-05-26 14:04:48 +10:00
Lucas Smith 6ac67e646c fix: always show captcha (#2860) 2026-05-25 19:56:24 +07:00
github-actions[bot] 6a20fefd7b chore: extract translations (#2806) 2026-05-22 14:41:35 +10:00
Abdulazez (Abza) 43fe558459 fix: prevent crash when removing last dropdown option in removeValue (#2843) 2026-05-22 14:40:40 +10:00
Abdulazez (Abza) 0a6b0452dc fix: handleInitialsFieldClick now returns initialsToInsert instead of initials (#2838) 2026-05-22 14:31:46 +10:00
David Nguyen fec5d55250 fix: move document complete email to a job (#2835) 2026-05-22 14:21:26 +10:00
Abdulazez (Abza) f1b235819e fix: remove duplicate loadingSpinnerGroup.destroy() in DROPDOWN sign (#2841) 2026-05-22 14:19:57 +10:00
Abdulazez (Abza) d0f9f68689 fix: correct reversed comparison in admin organisations table pagination (#2842) 2026-05-22 14:18:42 +10:00
roshboi f93a98e9a5 chore: updated certification status (#2850)
## Description
Updated HIPAA status to compliant
2026-05-21 15:49:41 +10:00
roshboi c0ea4c60e4 fix(docs): correct API example URLs from /documents to /document (#2836)
## Description

Corrected API endpoint path from /api/v2/documents to /api/v2/document

The current example in the docs(/api/v2/documents) returns a 404
NOT_FOUND object.
2026-05-20 18:17:14 +10:00
Ephraim Duncan 2cb4cc29ea feat: allow admins to create users (#2082) 2026-05-19 20:37:03 +10:00
Lucas Smith d9b5f01e21 chore: add translations (#2833) 2026-05-19 16:19:44 +10:00
Lucas Smith bc3acba72c fix: use captcha imperatively (#2832) 2026-05-19 14:38:40 +10:00
Ephraim Duncan 247a0158bd refactor(ui): replace hardcoded colors with semantic tokens (#2749) 2026-05-19 14:19:31 +10:00
Lucas Smith 9e0b567686 chore: deps upgrade (#2831) 2026-05-18 22:25:48 +10:00
David Nguyen 8f6be474a9 fix: improve api logging (#2820) 2026-05-15 13:41:35 +10:00
Ephraim Duncan 8f5bdef384 docs: require English for PRs and issues (#2819) 2026-05-15 12:30:13 +10:00
David Nguyen 999942014e chore: update docs for self hosters (#2816) 2026-05-14 15:07:10 +10:00
Tarana 194b2134cc docs: remove leftover Next.js commands and update to Remix-compatible syntax (#2695) 2026-05-14 12:06:59 +10:00
Ephraim Duncan b8df02750b fix: convert DOCX template uploads to PDF (#2807) 2026-05-14 11:59:27 +10:00
Lucas Smith 191170923a v2.11.0 2026-05-13 22:21:57 +10:00
Lucas Smith 4078c6b46d chore: add translations (#2805) 2026-05-13 17:06:56 +10:00
github-actions[bot] abbca79b48 chore: extract translations (#2804) 2026-05-13 16:34:21 +10:00
Gaurav goswami d6dd2b3292 perf: compress signing-celebration.png from 20MB to 4MB (#2781) 2026-05-13 15:46:32 +10:00
David Nguyen cfaad6efc9 feat: add admin org deletion (#2795) 2026-05-13 15:28:27 +10:00
github-actions[bot] 9a45b3564f chore: extract translations (#2796) 2026-05-13 15:20:04 +10:00
David Nguyen 8b171c9a30 chore: update docs to use editor instead of authoring (#2800)
## Description

Update docs to use the term "Editor" instead of "Authoring" to reduce
confusion.
2026-05-13 15:17:55 +10:00
Lucas Smith a8efb6f495 fix: remove translation tag from css textarea placeholder (#2803) 2026-05-13 15:17:34 +10:00
Lucas SmithandEphraim Duncan bc184d445f feat: support DOCX uploads via Gotenberg (#2801)
Uploaded .docx files are converted to PDF on the server using a
Gotenberg
sidecar before entering the normal envelope pipeline. The feature is
opt-in via NEXT_PRIVATE_DOCUMENT_CONVERSION_URL; when unset, only PDF
uploads are accepted.

A per-process circuit breaker opens for 30s after a conversion failure
to shed load.

Ships a dev Dockerfile that layers Microsoft Core Fonts and additional
language fonts
onto the upstream Gotenberg image for better fidelity.

Co-authored-by: Ephraim Duncan
<55143799+ephraimduncan@users.noreply.github.com>

Co-authored-by: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com>
2026-05-13 15:06:21 +10:00
David Nguyen 8dfd548c08 chore: remove github action caches (#2802) 2026-05-13 15:06:06 +10:00
Anish Patil 73a7335c89 refactor: remove unnecessary DateRange type assertion (#2790) 2026-05-13 13:11:13 +10:00
Timur Ercan be3e45427f chore: update fair use policy (#2798)
## Description

refined fair use policy with examples and guidelines
2026-05-12 14:58:39 +02:00
Abdelhamid Henni 57eb40d6aa chore: update French translations (#2717) 2026-05-12 15:29:52 +10:00
David Nguyen 684fab1909 chore: add section on personal organisations for SSO users (#2793) 2026-05-12 15:20:05 +10:00
Lucas Smith d794ceb8da chore: add translations (#2788) 2026-05-12 14:28:10 +10:00
github-actions[bot] 87315adb0f chore: extract translations (#2786) 2026-05-11 17:27:06 +10:00
Ephraim Duncan 0a7794be61 feat: protect signing URLs from indexing, caching, and embedding (#2469) 2026-05-11 17:24:58 +10:00
Ephraim Duncan f15d6f0150 perf: dynamically import posthog (#2622) 2026-05-11 15:58:15 +10:00
Lucas Smith 0b86ece1d5 feat: add custom branding for signing pages (#2785)
Platform-plan organisations and teams can now customise non-embed
signing pages with six brand colour tokens, a border-radius, and
a free-text custom CSS block (up to 256 KB).

- Stored on OrganisationGlobalSettings / TeamGlobalSettings;
  teams inherit from the org via brandingEnabled === null.
- CSS is sanitised on save (PostCSS) so we can inline it at SSR
  with no per-render parsing.
- Rendered via a nonce'd <style> scoped under .documenso-branded,
  using native CSS nesting so user selectors don't need scoping.
- Gated on the existing embedSigningWhiteLabel claim (or
  self-hosted) — reuses the embed white-label decision.
2026-05-11 13:03:02 +10:00
Ephraim Duncan a197bf113f feat: add granular signup disable flags (#2765) 2026-05-09 01:16:13 +00:00
Lucas Smith ec8728b33e chore: add translations (#2774) 2026-05-08 16:22:32 +10:00
github-actions[bot] 22122f51da chore: extract translations (#2772) 2026-05-08 16:22:08 +10:00
David Nguyen 8671f269e8 fix: lint project (#2693) 2026-05-08 16:04:22 +10:00
David Nguyen edbf65969b fix: replace linter with biome (#2645) 2026-05-08 15:40:31 +10:00
David Nguyen 207135d6f3 feat: add new field overflow methods (#2715) 2026-05-08 15:14:27 +10:00
Lucas Smith 4877d1964a chore: add translations (#2771) 2026-05-07 15:32:14 +10:00
Lucas Smith f66751668a fix: paginate and search member/group pickers (#2768) 2026-05-07 15:03:38 +10:00
github-actions[bot] bc3aa9c858 chore: extract translations (#2737) 2026-05-07 11:39:39 +10:00
Catalin Pit b79b4bd111 feat: add DD-MM-YYYY date format variants (#2767) 2026-05-06 23:34:05 +10:00
1954 changed files with 93420 additions and 43786 deletions
@@ -0,0 +1,138 @@
---
date: 2026-05-06
title: Platform Signing Page Branding
---
## What
Platform-plan organisations (and their teams) can customise the **non-embed
signing pages** (`/sign/:token`, `/d/:token`, and the sibling
complete/expired/rejected/waiting pages) with:
- Six brand colour tokens (background, foreground, primary, primary-foreground,
border, ring) plus a border-radius length.
- A free-text custom CSS block (up to 256 KB).
Settings live on `OrganisationGlobalSettings` and `TeamGlobalSettings`. Teams
inherit from the org via the existing `brandingEnabled === null` mechanism.
## Why
- Embed customers already have white-label CSS; Platform customers want the
same coverage on direct signing URLs that they iframe or link to.
- Persisting on org/team (not per envelope) means it's set-and-forget.
- Sanitising **on save** lets us inline the verbatim string at SSR — no
per-render parsing cost, no `<style>.innerHTML` injection on the client.
- Reusing the existing `embedSigningWhiteLabel` claim flag keeps "if you can
white-label an embed, you can white-label this" as one decision.
## How
### Storage (`packages/prisma/schema.prisma`)
Two new fields on each settings model. No new tables.
| Field | Org type | Team type |
| ---------------- | ------------------ | ------------------ |
| `brandingColors` | `Json?` (nullable) | `Json?` (nullable) |
| `brandingCss` | `String @default("")` | `String?` |
Colours are validated against `ZCssVarsSchema`. The team's `null` means
"inherit"; an empty colour object is collapsed to `null` server-side so a
team toggling `brandingEnabled = true` without filling in colours doesn't
silently override the org's defaults with nothing.
### Sanitiser (`packages/lib/utils/sanitize-branding-css.ts`)
PostCSS + `postcss-selector-parser`. Runs on save only.
- Drops selectors containing `::before`/`::after`/`::backdrop`/`::marker` or
the universal `*`.
- Drops integrity-breaking properties (`display`, `position`, `transform`,
layout-affecting dimensions, text-hiding properties).
- Drops declaration values containing `url(`, `expression(`, `@import`,
`javascript:`.
- Strips `!important`.
- Allows `@media` only; drops other at-rules.
- **Does not** rewrite selectors. Scoping happens at render time via native
CSS nesting under `.documenso-branded { ... }`.
- Final-pass tripwire: if a literal `</style` somehow survives serialization,
reject the entire output. PostCSS already escapes `<` to `\3c` whenever it
would form `</...`; the explicit check is belt-and-braces in case a future
serializer regresses.
- Returns `{ css, warnings[] }`. Warnings are surfaced in the UI.
Border-radius is the only token interpolated raw into a `<style>` block; it
is regex-validated (`CSS_LENGTH_REGEX`) at both the Zod schema and the
runtime `toNativeCssVars` call. Belt-and-braces against schema drift.
### Render (`apps/remix/app/components/general/recipient-branding.tsx`)
Each recipient loader calls `loadRecipientBrandingByTeamId` and threads the
payload through to `<RecipientBranding>`, which emits a single
nonce-attributed `<style>`:
```
.documenso-branded {
--background: ...; ...
<user css>
}
```
Native CSS nesting expands user rules under the wrapper. The body class is
applied unconditionally to recipient routes in `root.tsx` via `useMatches()`
so portaled Radix content (dialogs, popovers, tooltips, dropdowns) inherits
the scope.
CSP for recipient routes already supports `<style nonce>`; no policy
changes needed.
### Plan gate
`organisationClaim.flags.embedSigningWhiteLabel || !IS_BILLING_ENABLED()`.
Self-hosted instances always allow. The outer paywall for logo/URL/details
stays on `allowCustomBranding` (Team plan and up); only the new
colour/CSS section is Platform-only.
### UI (`apps/remix/app/components/forms/branding-preferences-form.tsx`)
Extends the existing branding form. Six `<ColorPicker showHex>` (rewritten
to use the native `<input type="color">` instead of `react-colorful`, which
was removed) in a 2-col grid, plus a free-text radius input and an
`<Accordion>` revealing a mono `<Textarea>`. Defaults come from
`packages/lib/constants/theme.ts` (light-mode hex mirror of `theme.css`).
Warnings from the sanitiser are surfaced in an `<Alert variant="warning">`
after save, and the `brandingCss` textarea is re-synced from the persisted
value so the user sees exactly what was stored. Other fields are
deliberately NOT reset on settings refetch — that would clobber in-flight
edits.
### TRPC
`update-organisation-settings` and `update-team-settings` accept the new
fields, run them through `sanitizeBrandingCss` + `normalizeBrandingColors`,
and return any sanitiser warnings to the client. The team route treats
`null` as "inherit"; an empty post-sanitisation string is collapsed to
`null` (team) so an empty override doesn't mask the org's CSS.
## Known accepted limitations
- The sanitiser does not prevent hostile-but-syntactically-valid CSS
(`color: transparent`, low-contrast values, etc.). The customer is
branding **their own** signing pages — we focus on integrity (no
overlay/hide/exfiltrate), not aesthetic policing.
- User rules targeting `body`/`html`/`:root` no-op once nested under the
wrapper class. Documented for users.
- CSS nesting baseline is Chrome 120+ / Firefox 117+ / Safari 16.5+.
Acceptable for the Platform-tier audience.
- No automated `theme.css``theme.ts` sync check; fat comment in
`theme.ts` reminds devs to update both.
- Per-section team inherit is coarse — `brandingEnabled = null` inherits
everything from the org. Per-field inherit toggles are deferred.
## Out of scope
Live preview, embed-route sanitiser unification, email/PDF certificate
branding, custom font upload, the full ~30 colour tokens in the picker UI,
wiring `hidePoweredBy` through to the actual footer.
@@ -0,0 +1,122 @@
---
date: 2026-05-28
title: Custom Brand Logo Url
---
# Problem
`brandingUrl` (the configured "Brand Website") is persisted and editable in branding
settings, but historically it was never consumed anywhere. It flowed into the database,
the settings form, and the admin read-only view, but never affected any rendered output.
We want `brandingUrl` to actually do something, with deliberately different behavior per
surface.
# Relationship we're going for
`brandingUrl` is an **email-only** linking concept. It is intentionally **not** used on
in-app signing surfaces.
| Surface | Custom branding logo configured | `brandingUrl` behavior |
| --- | --- | --- |
| Transactional emails (logo) | Logo shown | Logo links to `brandingUrl` when it is a safe http(s) URL; otherwise plain image |
| Transactional emails (footer) | n/a | `brandingUrl` rendered as a link in the footer when it is a safe http(s) URL |
| Signing pages (V1 + V2, normal + direct-template) | Logo shown | Ignored — logo is a plain image with no link |
| Signing pages (no custom logo) | Documenso fallback shown | Fallback keeps its internal `/` link |
| Embedded signing | Logo shown | Ignored (logo not linked) |
| Embedded authoring/editor | Logo shown | Ignored |
| Settings / admin branding previews | n/a | Unchanged (display only) |
Rationale:
- On signing pages the recipient is mid-task; sending them off to an external marketing
site via the logo is undesirable, so the custom logo is a plain image there.
- In emails the logo and a footer link to the brand's own site are a normal, expected
pattern and reinforce that the email is legitimately from that brand.
# Decisions
## Scope
- Use `brandingUrl` only in transactional email rendering:
- The shared email logo component links the custom branding logo to `brandingUrl`.
- The shared email footer renders `brandingUrl` as a link.
- On signing surfaces, render a configured custom branding logo as a plain image with no
link wrapper. Leave the Documenso fallback logo's internal `/` link untouched.
- Do not change embedded signing, embedded authoring/editor, or settings/admin previews.
- No Prisma schema or database migration. `brandingUrl` already exists and is editable.
## URL safety
Rendering must be defensive because old/imported data can bypass the branding form's URL
validation. Only treat the stored value as a usable Brand Website when it parses as an
absolute `http:` or `https:` URL.
- Empty, missing, invalid, relative, or non-http(s) values are treated as "no Brand
Website" and produce a plain logo / no footer link.
- Do not mutate stored settings or run a cleanup migration.
- Factored into a single shared helper so both email logo and footer apply identical rules:
- `packages/email/utils/branding-url.ts` -> `getSafeBrandingUrl(value): string | null`.
## Email rendering
- New shared component `packages/email/template-components/template-branding-logo.tsx`
(`TemplateBrandingLogo`) renders either:
- the custom branding logo, wrapped in a `Link` to the safe `brandingUrl` with
`target="_blank"` when one exists, or a plain `Img` when not; or
- the Documenso fallback logo (`/static/logo.png`) when custom branding is disabled or
no logo is set.
- This component replaced the duplicated `brandingEnabled && brandingLogo ? <Img/> : <fallback/>`
ternary that was copy-pasted across all transactional email templates.
- `packages/email/template-components/template-footer.tsx` renders `brandingUrl` as a
footer link (via `getSafeBrandingUrl`) when branding is enabled and the URL is safe.
The branding context already exposes `brandingUrl` (`packages/email/providers/branding.tsx`),
populated by `teamGlobalSettingsToBranding` / `organisationGlobalSettingsToBranding`
(which spread `...settings`), so no additional plumbing into the email branding context was
required.
## Signing rendering
- `apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx`:
custom logo renders as a bare `<img>`. `brandingUrl` is not read; the local branding type
and loader payload no longer carry it.
- `apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx` (V2,
shared by normal and direct-template signing): custom logo renders as a bare `<img>`; the
Documenso fallback keeps its `<Link to="/">`.
- `apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx`: V1 loader branding payload no
longer includes `brandingUrl`.
- `packages/lib/server-only/envelope/get-envelope-for-recipient-signing.ts` and
`get-envelope-for-direct-template-signing.ts`: `brandingUrl` removed from the V2
`EnvelopeForSigningResponse.settings` schema/payload since it is not consumed there.
# History
An earlier iteration of this plan wired `brandingUrl` into the in-app signing pages so a
custom logo linked to the Brand Website (external `<a target="_blank">`, internal `/`
fallback otherwise) and added `brandingUrl` to the V1/V2 signing payloads. That direction
was reversed: signing-page logos are now plain images and `brandingUrl` is email-only. The
signing payload additions were removed.
# Test coverage
`packages/app-tests/e2e/signing-branding.spec.ts`:
- V1 normal `/sign/:token`: custom logo is a plain image, not inside a link, and no
`brandingUrl` link is present.
- V2 normal `/sign/:token` and V2 direct-template: same plain-image assertions.
- V2 with no custom logo: Documenso fallback still links to `/`.
- Embedded signing: no custom-logo Brand Website link is rendered.
# Acceptance criteria
- A custom branding logo on any signing surface (V1, V2 normal, V2 direct-template, embedded)
renders as a plain image with no link, and `brandingUrl` is never rendered as a link there.
- Documenso fallback logos continue linking to `/`.
- In transactional emails, when a custom logo and a safe `brandingUrl` are configured, the
email logo links to `brandingUrl` (new tab) and the footer shows the Brand Website link.
- In transactional emails, when `brandingUrl` is empty/invalid/relative/non-http(s), the logo
is a plain image and no footer Brand Website link is shown.
- URL safety is enforced through the single shared `getSafeBrandingUrl` helper.
- Settings/admin branding previews are unchanged.
- No schema or migration changes.
@@ -0,0 +1,146 @@
---
date: 2026-05-28
title: Rejected Expired Recipient Filters
---
## Context
Customers need to find (a) envelopes/documents in the `REJECTED` state and (b) envelopes
with at least one recipient whose signing link has **expired**. Today the UI only exposes
`INBOX / PENDING / COMPLETED / DRAFT / ALL` tabs, and the public API has no way to filter by
expired recipient links — forcing a fetch-all-`PENDING`-then-inspect-each-recipient workaround.
Two key facts from exploration shaped this plan:
- **`REJECTED` is already fully wired in the backend** — the where-clause (`find-documents.ts`),
stats counts (`get-stats.ts`), tRPC response schema, `ExtendedDocumentStatus` enum, and the
`FRIENDLY_STATUS_MAP` display all handle it. It is simply absent from the UI tab array.
- **Renewing expired links already works.** `resendDocument` refreshes `expiresAt` and clears
`expirationNotifiedAt` for unsigned, non-CC recipients (`resend-document.ts:98-121`), exposed
publicly via `POST /api/v2/document/redistribute` and `/api/v2/envelope/redistribute` and via the
resend/redistribute UI dialogs. No new renew mechanism is needed — only documentation/wording.
Expiration is a per-recipient condition (not an envelope status). The approved design models it
in the UI as an `EXPIRED` **pseudo-status tab** (reusing the existing tab machinery, mirroring how
`REJECTED` works) and in the public API as an orthogonal boolean `hasExpiredRecipients`. Both share
one EXISTS predicate.
Definition of "expired recipient" (matches `isRecipientExpired`, `packages/lib/utils/recipients.ts:118`):
a `Recipient` with `expiresAt IS NOT NULL AND expiresAt <= now() AND signingStatus = NOT_SIGNED AND role != CC`.
## Approach
### A. Shared EXISTS predicate (reused 4x, justified)
Add a local `hasExpiredRecipient(eb)` helper — modeled on the existing per-file `recipientExists` /
`senderEmailIs` helpers — to `find-documents.ts`, `get-stats.ts`, and `find-envelopes.ts`. It is the
single source of truth for the expired condition above (using `new Date()` for `now`, matching the
`period` filter's `.toJSDate()` style).
### B. REJECTED tab (UI only — backend already done)
- `apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents._index.tsx`: add
`ExtendedDocumentStatus.REJECTED` to the tab array (lines 149-155). Count badge, highlight, and
`?status=REJECTED` filtering already work via existing machinery.
### C. EXPIRED pseudo-status (UI + internal stats)
1. `packages/prisma/types/extended-document-status.ts`: add `EXPIRED: 'EXPIRED'`. Internal-only —
the public `DocumentStatus` enum is unaffected. This intentionally surfaces TS errors at the three
exhaustive/`Record<ExtendedDocumentStatus>` sites below, forcing them to be handled.
2. `packages/lib/server-only/document/find-documents.ts`:
- Add `.with(ExtendedDocumentStatus.EXPIRED, ...)` to **both** `applyPersonalFilters` and
`applyTeamFilters`, mirroring the `COMPLETED` branch's access control (deleted + visibility +
owner/recipient access) with `hasExpiredRecipient(eb)` AND-ed in. Do **not** constrain
`Envelope.status` — the EXISTS already restricts to unsigned recipients.
3. `packages/lib/server-only/document/get-stats.ts`:
- Add an `expiredQuery` mirroring `pendingQuery`'s access control + `hasExpiredRecipient(eb)`.
- Add it to the `Promise.all`, add `[ExtendedDocumentStatus.EXPIRED]: expired` to the `stats`
record. **Do not** add `expired` to the `all` sum (it overlaps `PENDING`).
4. `packages/trpc/server/document-router/find-documents-internal.types.ts`: add
`[ExtendedDocumentStatus.EXPIRED]: z.number()` to the `stats` response object. (`status` already
accepts the extended enum via `z.nativeEnum(ExtendedDocumentStatus)`.)
5. `apps/remix/app/components/general/document/document-status.tsx`: add an `EXPIRED` entry to
`FRIENDLY_STATUS_MAP``label: msg` Expired, an icon (e.g. lucide `TimerOff`, matching the
`/sign/$token/expired` page), and a distinct color (e.g. `text-orange-500`) to differentiate from
`REJECTED` (red).
6. `documents._index.tsx`: add `[ExtendedDocumentStatus.EXPIRED]: 0` to the `stats` `useState`
initializer and `ExtendedDocumentStatus.EXPIRED` to the tab array. Final order:
`INBOX, PENDING, COMPLETED, DRAFT, REJECTED, EXPIRED, ALL`.
7. (Optional, recommended) `apps/remix/app/components/tables/documents-table-empty-state.tsx`: add
tailored `EXPIRED` and `REJECTED` empty-state copy (currently both fall through to `.otherwise()`).
### D. Public API boolean `hasExpiredRecipients` (document + envelope, v2)
1. `packages/lib/server-only/document/find-documents.ts`: add `hasExpiredRecipients?: boolean` to
`FindDocumentsOptions`; when true, apply `.where((eb) => hasExpiredRecipient(eb))` inside
`buildBaseQuery` (orthogonal/additive to any `status`).
2. `packages/trpc/server/document-router/find-documents.types.ts`: add a query-safe boolean
`hasExpiredRecipients` to `ZFindDocumentsRequestSchema` with a `.describe(...)`. Mirror the
existing boolean-query-param handling in `find-document-audit-logs.types.ts`
(`filterForRecentActivity`) — avoid raw `z.coerce.boolean()` (the "false" -> true footgun); use a
string transform if needed. Pass it through in `find-documents.ts` (public handler).
3. `packages/lib/server-only/envelope/find-envelopes.ts`: add `hasExpiredRecipients?: boolean` to
`FindEnvelopesOptions` + the `hasExpiredRecipient(eb)` helper + the additive `.where`.
4. `packages/trpc/server/envelope-router/find-envelopes.types.ts`: add the same param to
`ZFindEnvelopesRequestSchema`; pass it through in the envelope-router find handler.
The param auto-appears in the generated `/api/v2/openapi.json`.
Note: REST v1 `GET /api/v1/documents` is deprecated and lacks status filtering — left unchanged.
`REJECTED` is already a valid public `status` value (`DocumentStatus.REJECTED`), so no API change is
needed for rejected filtering.
### E. Renew expired links — documentation only
No functional change. Document that resending renews expired links:
- Update the `.description` in `packages/trpc/server/document-router/redistribute-document.types.ts`
and `packages/trpc/server/envelope-router/redistribute-envelope.types.ts` to state that
redistributing refreshes the signing-link expiration for unsigned recipients.
- Optionally adjust resend/redistribute dialog copy
(`apps/remix/app/components/dialogs/document-resend-dialog.tsx`,
`envelope-redistribute-dialog.tsx`) to mention it renews expired links.
## Files To Modify (summary)
| Area | File |
|------|------|
| Enum | `packages/prisma/types/extended-document-status.ts` |
| Where-clause + API option | `packages/lib/server-only/document/find-documents.ts` |
| Stats counts | `packages/lib/server-only/document/get-stats.ts` |
| Envelope find (API) | `packages/lib/server-only/envelope/find-envelopes.ts` |
| Internal tRPC stats schema | `packages/trpc/server/document-router/find-documents-internal.types.ts` |
| Public doc API schema + handler | `packages/trpc/server/document-router/find-documents.types.ts`, `find-documents.ts` |
| Public envelope API schema + handler | `packages/trpc/server/envelope-router/find-envelopes.types.ts`, `find-envelopes.ts` |
| Status display | `apps/remix/app/components/general/document/document-status.tsx` |
| Tabs + stats init | `apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents._index.tsx` |
| Empty state (optional) | `apps/remix/app/components/tables/documents-table-empty-state.tsx` |
| Renew docs | `redistribute-document.types.ts`, `redistribute-envelope.types.ts` (+ resend dialogs, optional) |
## Reused Utilities / Patterns
- `recipientExists` / `senderEmailIs` (per-file Kysely EXISTS helpers) — the template for the new
`hasExpiredRecipient` helper.
- `REJECTED` branches in `find-documents.ts` (lines 279, 416) and `rejectedQuery` in `get-stats.ts`
(line 227) — the template for the `EXPIRED` branches / `expiredQuery`.
- `isRecipientExpired` (`packages/lib/utils/recipients.ts:118`) — defines the `expiresAt <= now`
semantics to match.
- Existing tab machinery in `documents._index.tsx` (`getTabHref`, count badge, personal-org `.filter`)
— works unchanged for the new tabs.
- `resendDocument` / `trpc.document.redistribute` / `trpc.envelope.redistribute` — existing renew path.
## Verification
1. **Typecheck** (the enum change forces all exhaustive/Record sites): `npm run typecheck -w @documenso/remix`.
2. **Seed + UI** (dev server already running): seed a team via `seedTeam`, send a document, then:
- Reject one as a recipient -> it appears under the new **Rejected** tab with a count.
- Force expiry (set a recipient `expiresAt` in the past, e.g. via Prisma Studio or a short
`envelopeExpirationPeriod`) -> the doc appears under the new **Expired** tab with a count, and the
count excludes signed/CC recipients.
3. **Public API**: `GET /api/v2/document?hasExpiredRecipients=true` and
`GET /api/v2/envelope?hasExpiredRecipients=true` (Bearer API token) return only envelopes with >=1
expired unsigned recipient; confirm `GET /api/v2/document?status=REJECTED` works. Verify the param
appears in `/api/v2/openapi.json`.
4. **Renew**: on an expired doc, run resend/redistribute (UI dialog or
`POST /api/v2/document/redistribute`) -> recipient `expiresAt` is refreshed, the doc leaves the
Expired tab, and the signing link no longer redirects to `/sign/$token/expired`.
5. **E2E** (optional): extend `packages/app-tests/e2e/envelopes/envelope-expiration-send.spec.ts`
with an Expired-tab assertion.
6. Do **not** modify/commit `packages/lib/translations/*.po`; run `npm run translate` only if needed
for new `msg`/`Trans` strings, and keep generated `.po` files out of the branch.
## Open Questions
- Exact icon/color for the `EXPIRED` tab (proposed: `TimerOff`, `text-orange-500`).
- Whether to add the optional tailored empty-state copy now or defer.
+47 -3
View File
@@ -48,7 +48,7 @@ NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documen
NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
# [[SIGNING]]
# The transport to use for document signing. Available options: local (default) | gcloud-hsm
# The transport to use for document signing. Available options: local (default) | gcloud-hsm | csc
NEXT_PRIVATE_SIGNING_TRANSPORT="local"
# OPTIONAL: The passphrase to use for the local file-based signing transport.
NEXT_PRIVATE_SIGNING_PASSPHRASE=
@@ -70,6 +70,14 @@ NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_FILE_PATH=
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS=
# OPTIONAL: The Google Secret Manager path to retrieve the certificate for the gcloud-hsm signing transport.
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH=
# OPTIONAL: The base URL of the Cloud Signature Consortium (CSC) provider for the csc signing transport.
NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL=
# OPTIONAL: The OAuth client ID registered with the CSC provider for the csc signing transport.
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_ID=
# OPTIONAL: The OAuth client secret registered with the CSC provider for the csc signing transport.
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_SECRET=
# OPTIONAL: Default signature level for envelopes created on a CSC instance when the caller doesn't specify one. Available options: AES (default) | QES. Explicit AES/QES requests always pass through unchanged.
NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL=
# OPTIONAL: Comma-separated list of timestamp authority URLs for PDF signing (enables LTV and archival timestamps).
NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY=
# OPTIONAL: Contact info to embed in PDF signatures. Defaults to the webapp URL.
@@ -95,7 +103,7 @@ NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID="documenso"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY="password"
# [[SMTP]]
# OPTIONAL: Defines the transport to use for sending emails. Available options: smtp-auth (default) | smtp-api | mailchannels
# OPTIONAL: Defines the transport to use for sending emails. Available options: smtp-auth (default) | smtp-api | resend | mailchannels
NEXT_PRIVATE_SMTP_TRANSPORT="smtp-auth"
# OPTIONAL: Defines the host to use for sending emails.
NEXT_PRIVATE_SMTP_HOST="127.0.0.1"
@@ -160,10 +168,32 @@ NEXT_PRIVATE_REDIS_PREFIX="documenso"
NEXT_PUBLIC_POSTHOG_KEY=""
# OPTIONAL: Leave blank to disable billing.
NEXT_PUBLIC_FEATURE_BILLING_ENABLED=
# OPTIONAL: Leave blank to allow users to signup through /signup page.
# OPTIONAL: Set to "true" to disable all signup methods (email, Google, Microsoft, OIDC, including the organisation OIDC portal).
NEXT_PUBLIC_DISABLE_SIGNUP=
# OPTIONAL: Set to "true" to disable email/password signup only.
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP=
# OPTIONAL: Set to "true" to block new-account creation through Google. Existing linked users can still sign in.
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP=
# OPTIONAL: Set to "true" to block new-account creation through Microsoft. Existing linked users can still sign in.
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=
# OPTIONAL: Set to "true" to block new-account creation through OIDC (including the organisation portal).
NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=
# OPTIONAL: Comma-separated list of email domains allowed to sign up (e.g., example.com,acme.org).
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=
# OPTIONAL: Set to "true" to disable all signin methods (email, Google, Microsoft, OIDC).
NEXT_PUBLIC_DISABLE_SIGNIN=
# OPTIONAL: Set to "true" to disable email/password signin only. Also closes /forgot-password and /reset-password.
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN=
# OPTIONAL: Set to "true" to hide the Google signin button.
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN=
# OPTIONAL: Set to "true" to hide the Microsoft signin button.
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN=
# OPTIONAL: Set to "true" to hide the OIDC signin button.
NEXT_PUBLIC_DISABLE_OIDC_SIGNIN=
# OPTIONAL: When OIDC is the only enabled signin transport, /signin auto-redirects
# to the OIDC provider (rendering only a spinner). Set to "true" to disable this
# and keep showing the signin page.
NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT=
# OPTIONAL: Set to true to use internal webapp url in browserless requests.
NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS=false
@@ -203,3 +233,17 @@ NEXT_PRIVATE_LOGGER_FILE_PATH=
# [[PLAIN SUPPORT]]
NEXT_PRIVATE_PLAIN_API_KEY=
# [[DOCUMENT CONVERSION]]
# OPTIONAL: Base URL of a Gotenberg-compatible service used to convert uploaded
# DOCX files to PDF on the server. When unset, DOCX uploads are disabled and
# only PDF is accepted. The dev docker compose exposes Gotenberg on port 3005.
# NEXT_PRIVATE_DOCUMENT_CONVERSION_URL="http://localhost:3005"
# OPTIONAL: Per-request timeout in milliseconds for the conversion service.
# Defaults to 30000 (30s) if unset.
# NEXT_PRIVATE_DOCUMENT_CONVERSION_TIMEOUT_MS=30000
# OPTIONAL: HTTP Basic auth credentials for the conversion service. Set both
# when the service is started with `--api-enable-basic-auth` (the dev compose
# does this; the matching values there are `documenso` / `password`).
# NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME=documenso
# NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD=password
-8
View File
@@ -1,8 +0,0 @@
# Config files
*.config.js
*.config.cjs
# Statically hosted javascript files
apps/*/public/*.js
apps/*/public/*.cjs
scripts/
-16
View File
@@ -1,16 +0,0 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
extends: ['@documenso/eslint-config'],
rules: {
'@next/next/no-img-element': 'off',
'no-unreachable': 'error',
'react-hooks/exhaustive-deps': 'off',
},
settings: {
next: {
rootDir: ['apps/*/'],
},
},
ignorePatterns: ['lingui.config.ts', 'packages/lib/translations/**/*.js'],
};
+1 -2
View File
@@ -34,7 +34,7 @@ body:
label: Browser [e.g., Chrome, Firefox]
- type: input
attributes:
label: Version [e.g., 2.0.1]
label: Version [e.g., 2.13.0]
- type: checkboxes
attributes:
label: Please check the boxes that apply to this issue report.
@@ -44,4 +44,3 @@ body:
- label: I have included relevant environment information.
- label: I have included any relevant screenshots.
- label: I understand that this is a voluntary contribution and that there is no guarantee of resolution.
- label: I want to work on creating a PR for this issue if approved
+11
View File
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/documenso/documenso/security/advisories/new
about: Please report security vulnerabilities privately via GitHub Security Advisories. Do not open a public issue.
- name: Questions & Discussions
url: https://github.com/documenso/documenso/discussions
about: Ask questions, share ideas, and discuss Documenso with the community.
- name: Discord
url: https://documen.so/discord
about: Chat with the community and the team.
@@ -33,4 +33,3 @@ body:
- label: I have explained the use case or scenario for this feature.
- label: I have included any relevant technical details or design suggestions.
- label: I understand that this is a suggestion and that there is no guarantee of implementation.
- label: I want to work on creating a PR for this issue if approved
-11
View File
@@ -15,17 +15,6 @@ body:
description: 'Are there any additional context or information that might be relevant to the improvement suggestion.'
validations:
required: false
- type: dropdown
id: assignee
attributes:
label: 'Do you want to work on this improvement?'
multiple: false
options:
- 'No'
- 'Yes'
default: 0
validations:
required: true
- type: checkboxes
attributes:
label: 'Please check the boxes that apply to this improvement suggestion.'
+11
View File
@@ -1,3 +1,14 @@
<!--
We are no longer accepting external pull requests.
Aside from a small group of trusted contributors we reach out to directly,
new external PRs will usually be closed with a request to open an issue instead.
This is a security decision. See https://documenso.com/blog/why-we-re-pausing-external-pull-requests
If you're a trusted contributor or maintainer, continue below.
Otherwise, please open a detailed issue: https://github.com/documenso/documenso/issues/new/choose
-->
## Description
<!--- Describe the changes introduced by this pull request. -->
@@ -1,40 +0,0 @@
---
name: Test Addition
about: Submit a new test, either unit or end-to-end (E2E), for review and inclusion
---
## Description
<!--- Provide a clear and concise description of the new test you are adding. -->
<!--- Explain the purpose of the test and what it aims to validate. -->
## Related Issue
<!--- If this test addition is related to a specific issue, reference it here using #issue_number. -->
<!--- For example, "Fixes #123" or "Addresses #456". -->
## Test Details
<!--- Describe the details of the test you're adding. -->
<!--- Include information about inputs, expected outputs, and any specific scenarios. -->
- Test Name: Name of the test
- Type: [Unit / E2E]
- Description: Brief description of what the test checks
- Inputs: What inputs the test uses (if applicable)
- Expected Output: What output or behavior the test expects
## Checklist
<!--- Please check the boxes that apply to this pull request. -->
<!--- You can add or remove items as needed. -->
- [ ] I have written the new test and ensured it works as intended.
- [ ] I have added necessary documentation to explain the purpose of the test.
- [ ] I have followed the project's testing guidelines and coding style.
- [ ] I have addressed any review feedback from previous submissions, if applicable.
## Additional Notes
<!--- Provide any additional context or notes for the reviewers. -->
<!--- This might include explanations about the testing approach or any potential concerns. -->
+1 -19
View File
@@ -1,4 +1,4 @@
name: 'Setup node and cache node_modules'
name: 'Setup node'
inputs:
node_version:
required: false
@@ -16,25 +16,7 @@ runs:
shell: bash
run: corepack enable npm
- name: Cache npm
uses: actions/cache@v3
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Cache node_modules
uses: actions/cache@v3
id: cache-node-modules
with:
path: |
node_modules
packages/*/node_modules
apps/*/node_modules
key: modules-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
shell: bash
run: |
npm ci --no-audit
+1 -12
View File
@@ -1,19 +1,8 @@
name: Install playwright binaries
description: 'Install playwright, cache and restore if necessary'
description: 'Install playwright'
runs:
using: 'composite'
steps:
- name: Cache playwright
id: cache-playwright
uses: actions/cache@v3
with:
path: |
~/.cache/ms-playwright
${{ github.workspace }}/node_modules/playwright
key: playwright-${{ hashFiles('**/package-lock.json') }}
restore-keys: playwright-
- name: Install playwright
if: steps.cache-playwright.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
shell: bash
-18
View File
@@ -41,14 +41,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build Docker Image
uses: docker/build-push-action@v5
with:
@@ -56,13 +48,3 @@ jobs:
context: .
file: ./docker/Dockerfile
tags: documenso-${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- # Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
+1 -7
View File
@@ -1,13 +1,10 @@
name: 'Welcome New Contributors'
on:
pull_request:
types: ['opened']
issues:
types: ['opened']
permissions:
pull-requests: write
issues: write
jobs:
@@ -20,10 +17,7 @@ jobs:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: |
Thank you for creating your first Pull Request and for being a part of the open signing revolution! 💚🚀
<br /> Feel free to hop into our community in [Discord](https://documen.so/discord)
issue-message: |
Thank you for opening your first issue and for being a part of the open signing revolution!
<br /> One of our team members will review it and get back to you as soon as it possible 💚
<br /> One of our team members will review it and get back to you as soon as possible 💚
<br /> Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord)
@@ -1,63 +0,0 @@
name: 'Issue Assignee Check'
on:
issues:
types: ['assigned']
permissions:
issues: write
jobs:
countIssues:
if: ${{ github.event.issue.assignee }} && github.event.action == 'assigned' && github.event.sender.type == 'User'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: npm
- name: Install Octokit
run: npm install @octokit/rest@18
- name: Check Assigned User's Issue Count
id: parse-comment
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { Octokit } = require("@octokit/rest");
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const username = context.payload.issue.assignee.login;
console.log(`Username Extracted: ${username}`);
const { data: issues } = await octokit.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
assignee: username,
state: 'open'
});
const issueCount = issues.length;
console.log(`Issue Count For ${username}: ${issueCount}`);
if (issueCount > 3) {
let issueCountMessage = `### 🚨 Documenso Police 🚨`;
issueCountMessage += `\n@${username} has ${issueCount} open issues assigned already. Consider whether this issue should be assigned to them or left open for another contributor.`;
await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: issueCountMessage,
headers: {
'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`,
}
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
name: 'PR Labeler'
on:
- pull_request_target
- pull_request
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
-64
View File
@@ -1,64 +0,0 @@
name: 'PR Review Reminder'
on:
pull_request:
types: ['opened', 'ready_for_review']
permissions:
pull-requests: write
jobs:
checkPRs:
if: ${{ github.event.pull_request.user.login }} && github.event.action == ('opened' || 'ready_for_review')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: npm
- name: Install Octokit
run: npm install @octokit/rest@18
- name: Check user's PRs awaiting review
id: parse-prs
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { Octokit } = require("@octokit/rest");
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const username = context.payload.pull_request.user.login;
console.log(`Username Extracted: ${username}`);
const { data: pullRequests } = await octokit.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
sort: 'created',
direction: 'asc',
});
const userPullRequests = pullRequests.filter(pr => pr.user.login === username && (pr.state === 'open' || pr.state === 'ready_for_review'));
const prCount = userPullRequests.length;
console.log(`PR Count for ${username}: ${prCount}`);
if (prCount > 3) {
let prReminderMessage = `🚨 @${username} has ${prCount} pull requests awaiting review. Please consider reviewing them when possible. 🚨`;
await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: prReminderMessage,
headers: {
'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`,
}
});
}
+1 -28
View File
@@ -1,7 +1,7 @@
name: 'Validate PR Name'
on:
pull_request_target:
pull_request:
types:
- opened
- reopened
@@ -16,24 +16,6 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- name: Check PR creator's previous activity
id: check_activity
run: |
CREATOR=$(curl -s "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" | jq -r '.user.login')
ACTIVITY=$(curl -s "https://api.github.com/search/commits?q=author:${CREATOR}+repo:${{ github.repository }}" | jq -r '.total_count')
if [ "$ACTIVITY" -eq 0 ]; then
echo "::set-output name=is_new::true"
else
echo "::set-output name=is_new::false"
fi
- name: Count PRs created by user
id: count_prs
run: |
CREATOR=$(curl -s "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" | jq -r '.user.login')
PR_COUNT=$(curl -s "https://api.github.com/search/issues?q=type:pr+is:open+author:${CREATOR}+repo:${{ github.repository }}" | jq -r '.total_count')
echo "::set-output name=pr_count::$PR_COUNT"
- uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
@@ -44,8 +26,6 @@ jobs:
with:
header: pr-title-lint-error
message: |
Hey There! and thank you for opening this pull request! 📝👋🏼
We require pull request titles to follow the [Conventional Commits Spec](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
@@ -53,10 +33,3 @@ jobs:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
- if: ${{ steps.lint_pr_title.outputs.error_message == null && steps.check_activity.outputs.is_new == 'false' && steps.count_prs.outputs.pr_count < 2}}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
message: |
Thank you for following the naming conventions for pull request titles! 💚🚀
+1 -1
View File
@@ -21,4 +21,4 @@ jobs:
stale-pr-message: 'This PR has not seen activitiy for a while. It will be closed in 30 days unless further activity is detected.'
close-pr-message: 'This PR has been closed because of inactivity.'
exempt-pr-labels: 'WIP,on-hold,needs review'
exempt-issue-labels: 'WIP,on-hold,needs review,roadmap,assigned,needs triage'
exempt-issue-labels: 'WIP,on-hold,needs review,roadmap,status: assigned,status: triage'
+1 -2
View File
@@ -31,8 +31,7 @@ vscode:
extensions:
- aaron-bond.better-comments
- bradlc.vscode-tailwindcss
- dbaeumer.vscode-eslint
- esbenp.prettier-vscode
- biomejs.biome
- mikestead.dotenv
- unifiedjs.vscode-mdx
- GitHub.vscode-pull-request-github
+1 -1
View File
@@ -1,3 +1,3 @@
legacy-peer-deps = true
prefer-dedupe = true
min-release-age = 7
# min-release-age = 7
-20
View File
@@ -1,20 +0,0 @@
node_modules
.next
public
**/**/node_modules
**/**/.next
**/**/public
packages/lib/translations/**/*.js
*.lock
*.log
*.test.ts
.gitignore
.npmignore
.prettierignore
.DS_Store
.eslintignore
# Docs MDX - Prettier strips indentation from code blocks inside components
apps/docs/content/**/*.mdx
+18 -6
View File
@@ -1,11 +1,11 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"eslint.validate": ["typescript", "typescriptreact", "javascript", "javascriptreact"],
"javascript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.useAliasesForRenames": false,
"typescript.enablePromptUseWorkspaceTsdk": true,
@@ -15,8 +15,20 @@
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
},
"prisma.pinToPrisma6": true
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"prisma.pinToPrisma6": true,
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
}
+11 -4
View File
@@ -1,7 +1,14 @@
# General Issues
# Report security vulnerabilities privately via GitHub Security Advisories (preferred).
Contact: https://github.com/documenso/documenso/security/advisories/new
# Alternatively, report critical issues privately by email.
Contact: mailto:security@documenso.com
# Security policy
Policy: https://github.com/documenso/documenso/security/policy
# General (non-security) issues
Contact: https://github.com/documenso/documenso/issues/new?assignees=&labels=bug&projects=&template=bug-report.yml
# Report critical issues privately to let us take appropriate action before publishing.
Contact: mailto:security@documenso.com
Preferred-Languages: en
Canonical: https://documenso.com/.well-known/security.txt
Canonical: https://documenso.com/.well-known/security.txt
+1 -1
View File
@@ -8,7 +8,7 @@
- `npm run test:e2e` - Run E2E tests with Playwright
- `npm run test:dev -w @documenso/app-tests` - Run single E2E test in dev mode
- `npm run test-ui:dev -w @documenso/app-tests` - Run E2E tests with UI
- `npm run format` - Format code with Prettier
- `npm run format` - Format code with Biome
- `npm run dev` - Start development server for Remix app
**Important:** Do not run `npm run build` to verify changes unless explicitly asked. Builds take a long time (~2 minutes). Use `npx tsc --noEmit` for type checking specific packages if needed.
+1 -3
View File
@@ -42,8 +42,8 @@ Documenso is an open-source document signing platform built as a **monorepo** us
| Package | Description | Port |
| -------------------------- | -------------------------------------------------------- | ---- |
| `@documenso/remix` | Main application - React Router (Remix) with Hono server | 3000 |
| `@documenso/documentation` | Documentation site (Next.js + Nextra) | 3002 |
| `@documenso/openpage-api` | Public analytics API | 3003 |
| `@documenso/docs` | Documentation site | 3004 |
### Core Packages (`packages/`)
@@ -65,8 +65,6 @@ Documenso is an open-source document signing platform built as a **monorepo** us
| Package | Description |
| ---------------------------- | ------------------------- |
| `@documenso/app-tests` | E2E tests (Playwright) |
| `@documenso/eslint-config` | Shared ESLint config |
| `@documenso/prettier-config` | Shared Prettier config |
| `@documenso/tailwind-config` | Shared Tailwind config |
| `@documenso/tsconfig` | Shared TypeScript configs |
+24 -6
View File
@@ -1,13 +1,31 @@
# Contributing to Documenso
If you plan to contribute to Documenso, please take a moment to feel awesome ✨ People like you are what open source is about ♥. Any contributions, no matter how big or small, are highly appreciated.
> **We are no longer accepting external pull requests.**
>
> Aside from a small group of trusted contributors we reach out to directly, we no longer merge external PRs. New pull requests will usually be closed with a request to open an issue instead. This is a security decision, not a judgement on your work. Read [Why We're Pausing External Pull Requests](https://documenso.com/blog/why-we-re-pausing-external-pull-requests) for the full reasoning.
>
> Documenso stays open source. You can still read, audit, run, and fork the code. The best way to contribute is through detailed issues.
## Before getting started
## How to contribute now
- Before jumping into a PR be sure to search [existing PRs](https://github.com/documenso/documenso/pulls) or [issues](https://github.com/documenso/documenso/issues) for an open or closed item that relates to your submission.
- Select an issue from [here](https://github.com/documenso/documenso/issues) or create a new one
- Consider the results from the discussion on the issue
- Accept the [Contributor License Agreement](https://documen.so/cla) to ensure we can accept your contributions.
The most useful contribution is a detailed issue. Treat it like a spec. The more detail, the better:
- The problem you're trying to solve, and who it affects
- How you expect the feature or change to behave
- Edge cases, constraints, and anything you've already considered
- Examples, mockups, or references where they help
Before opening an issue, search [existing issues](https://github.com/documenso/documenso/issues) and [discussions](https://github.com/documenso/documenso/discussions) for related items. If a proposal is detailed and fits where Documenso is heading, we'll pick it up and build against it.
For security vulnerabilities, do not open a public issue. Follow our [Security Policy](./SECURITY.md) instead.
---
The sections below are for trusted contributors working with us directly, and for anyone running Documenso locally or maintaining a fork.
## English only PRs and Issues
Please write all issues, pull requests, and related comments in English so maintainers and the wider contributor community can follow the discussion.
## Taking issues
+37 -157
View File
@@ -11,6 +11,8 @@
·
<a href="https://documenso.com">Website</a>
·
<a href="https://docs.documenso.com">Documentation</a>
·
<a href="https://github.com/documenso/documenso/issues">Issues</a>
·
<a href="https://documen.so/live">Upcoming Releases</a>
@@ -49,16 +51,18 @@ Join us in creating the next generation of open trust infrastructure.
## Community and Next Steps 🎯
- Check out the first source code release in this repository and test it.
- Try Documenso by self-hosting it or signing up at [documenso.com](https://documenso.com).
- Tell us what you think in the [Discussions](https://github.com/documenso/documenso/discussions).
- Join the [Discord server](https://documen.so/discord) for any questions and getting to know to other community members.
- Join the [Discord server](https://documen.so/discord) for any questions and getting to know other community members.
- ⭐ the repository to help us raise awareness.
- Spread the word on Twitter that Documenso is working towards a more open signing tool.
- Fix or create [issues](https://github.com/documenso/documenso/issues), that are needed for the first production release.
- Open detailed [issues](https://github.com/documenso/documenso/issues) to report bugs or propose features.
## Contributing
- To contribute, please see our [contribution guide](https://github.com/documenso/documenso/blob/main/CONTRIBUTING.md).
> **Note**: We no longer accept external pull requests, aside from a small group of trusted contributors we reach out to directly. The best way to contribute is through detailed issues. Read [Why We're Pausing External Pull Requests](https://documenso.com/blog/why-we-re-pausing-external-pull-requests) for the reasoning.
- Documenso stays open source. You can read, audit, run, and fork the code.
- To report issues or propose changes, see our [contribution guide](https://github.com/documenso/documenso/blob/main/CONTRIBUTING.md).
## Contact us
@@ -79,17 +83,21 @@ Contact us if you are interested in our Enterprise plan for large organizations
<a href=""><img src="" alt=""></a>
</p>
- [Typescript](https://www.typescriptlang.org/) - Language
- [ReactRouter](https://reactrouter.com/) - Framework
- [TypeScript](https://www.typescriptlang.org/) - Language
- [React Router v7](https://reactrouter.com/) - Framework
- [Hono](https://hono.dev/) - Server
- [Prisma](https://www.prisma.io/) - ORM
- [Tailwind](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
- [Tailwind CSS](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) + [Radix UI](https://www.radix-ui.com/) - Component Library
- [react-email](https://react.email/) - Email Templates
- [Lingui](https://lingui.dev/) - Internationalization
- [tRPC](https://trpc.io/) - API
- [@documenso/pdf-sign](https://www.npmjs.com/package/@documenso/pdf-sign) - PDF Signatures (launching soon)
- [React-PDF](https://github.com/wojtekmaj/react-pdf) - Viewing PDFs
- [PDF-Lib](https://github.com/Hopding/pdf-lib) - PDF manipulation
- [@libpdf/core](https://www.npmjs.com/package/@libpdf/core) - PDF Signatures
- [pdf.js](https://mozilla.github.io/pdf.js/) - Viewing PDFs
- [@cantoo/pdf-lib](https://github.com/cantoo-scribe/pdf-lib) - PDF manipulation
- [Stripe](https://stripe.com/) - Payments
- [Biome](https://biomejs.dev/) - Linting & Formatting
- [Playwright](https://playwright.dev/) - E2E Testing
<!-- - Support for [opensignpdf (requires Java on server)](https://github.com/open-pdf-sign) is currently planned. -->
@@ -146,45 +154,7 @@ npm run d
### Manual Setup
Follow these steps to setup Documenso on your local machine:
1. [Fork this repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
After forking the repository, clone it to your local device by using the following command:
```sh
git clone https://github.com/<your-username>/documenso
```
2. Run `npm i` in the root directory
3. Create your `.env` from the `.env.example`. You can use `cp .env.example .env` to get started with our handpicked defaults.
4. Set the following environment variables:
- NEXTAUTH_SECRET
- NEXT_PUBLIC_WEBAPP_URL
- NEXT_PRIVATE_DATABASE_URL
- NEXT_PRIVATE_DIRECT_DATABASE_URL
- NEXT_PRIVATE_SMTP_FROM_NAME
- NEXT_PRIVATE_SMTP_FROM_ADDRESS
5. Create the database schema by running `npm run prisma:migrate-dev`
6. Run `npm run translate:compile` in the root directory to compile lingui
7. Run `npm run dev` in the root directory to start
8. Register a new user at http://localhost:3000/signup
---
- Optional: Seed the database using `npm run prisma:seed -w @documenso/prisma` to create a test user and document.
- Optional: Create your own signing certificate.
- To generate your own using these steps and a Linux Terminal or Windows Subsystem for Linux (WSL), see **[Create your own signing certificate](./SIGNING.md)**.
- Optional: Configure job provider for document reminders.
- The default local job provider does not support scheduled jobs required for document reminders.
- To enable reminders, set `NEXT_PRIVATE_JOBS_PROVIDER=inngest` and provide `NEXT_PRIVATE_INNGEST_EVENT_KEY` in your `.env` file.
Follow the [manual setup guide](https://docs.documenso.com/docs/developers/local-development/manual) to configure Documenso on your local machine.
### Run in Gitpod
@@ -204,138 +174,48 @@ If you're a visual learner and prefer to watch a video walkthrough of setting up
## Docker
We provide a Docker container for Documenso, which is published on both DockerHub and GitHub Container Registry.
We provide official Docker images on [DockerHub](https://hub.docker.com/r/documenso/documenso) and [GitHub Container Registry](https://ghcr.io/documenso/documenso).
- DockerHub: [https://hub.docker.com/r/documenso/documenso](https://hub.docker.com/r/documenso/documenso)
- GitHub Container Registry: [https://ghcr.io/documenso/documenso](https://ghcr.io/documenso/documenso)
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
Please note that you will need to provide environment variables for connecting to the database, mailserver, and so forth.
For detailed instructions on how to configure and run the Docker container, please refer to the [Docker README](./docker/README.md) in the `docker` directory.
For setup instructions, see the [Docker Deployment](https://docs.documenso.com/docs/self-hosting/deployment/docker) and [Docker Compose](https://docs.documenso.com/docs/self-hosting/deployment/docker-compose) guides.
## Self Hosting
We support a variety of deployment methods, and are actively working on adding more. Stay tuned for updates!
We support a variety of deployment methods including Docker, Docker Compose, Railway, Kubernetes, and manual deployment.
### Fetch, configure, and build
For full instructions, requirements, and configuration details, see the [Self Hosting documentation](https://docs.documenso.com/docs/self-hosting).
First, clone the code from Github:
### One-Click Deploys
```
git clone https://github.com/documenso/documenso.git
```
#### Railway
Then, inside the `documenso` folder, copy the example env file:
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/DjrRRX?referralCode=EZR3s0&utm_medium=integration&utm_source=template&utm_campaign=generic)
```
cp .env.example .env
```
The following environment variables must be set:
- `NEXTAUTH_SECRET`
- `NEXT_PUBLIC_WEBAPP_URL`
- `NEXT_PRIVATE_DATABASE_URL`
- `NEXT_PRIVATE_DIRECT_DATABASE_URL`
- `NEXT_PRIVATE_SMTP_FROM_NAME`
- `NEXT_PRIVATE_SMTP_FROM_ADDRESS`
> If you are using a reverse proxy in front of Documenso, don't forget to provide the public URL for the `NEXT_PUBLIC_WEBAPP_URL` variable!
Now you can install the dependencies and build it:
```
npm i
npm run build
npm run prisma:migrate-deploy
```
Finally, you can start it with:
```
cd apps/remix
npm run start
```
This will start the server on `localhost:3000`. For now, any reverse proxy can then do the frontend and SSL termination.
> If you want to run with another port than 3000, you can start the application with `next -p <ANY PORT>` from the `apps/remix` folder.
### Run as a service
You can use a systemd service file to run the app. Here is a simple example of the service running on port 3500 (using 3000 by default):
```bash
[Unit]
Description=documenso
After=network.target
[Service]
Environment=PATH=/path/to/your/node/binaries
Type=simple
User=www-data
WorkingDirectory=/var/www/documenso/apps/remix
ExecStart=/usr/bin/next start -p 3500
TimeoutSec=15
Restart=always
[Install]
WantedBy=multi-user.target
```
### Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/bG6D4p)
### Render
#### Render
[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/documenso/documenso)
### Koyeb
#### Koyeb
[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?type=git&repository=github.com/documenso/documenso&branch=main&name=documenso-app&builder=dockerfile&dockerfile=/docker/Dockerfile)
## Elestio
#### Elestio
[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/documenso)
## Security
If you believe you have found a security vulnerability in Documenso, please report it through our [Security Policy](https://github.com/documenso/documenso/security/policy). We prioritize private reports via [GitHub Security Advisories](https://github.com/documenso/documenso/security/advisories/new). See [SECURITY.md](./SECURITY.md) for scope and details.
## Troubleshooting
For troubleshooting self-hosted deployments, see the [Troubleshooting guide](https://docs.documenso.com/docs/self-hosting/maintenance/troubleshooting) and [Tips & Common Pitfalls](https://docs.documenso.com/docs/self-hosting/getting-started/tips).
### I'm not receiving any emails when using the developer quickstart.
When using the developer quickstart, an [Inbucket](https://inbucket.org/) server will be spun up in a docker container that will store all outgoing emails locally for you to view.
The Web UI can be found at http://localhost:9000, while the SMTP port will be on localhost:2500.
### Support IPv6
If you are deploying to a cluster that uses only IPv6, You can use a custom command to pass a parameter to the Remix start command
For local docker run
```bash
docker run -it documenso:latest npm run start -- -H ::
```
For k8s or docker-compose
```yaml
containers:
- name: documenso
image: documenso:latest
imagePullPolicy: IfNotPresent
command:
- npm
args:
- run
- start
- --
- -H
- '::'
```
### I can't see environment variables in my package scripts.
Wrap your package script with the `with:env` script like such:
+38
View File
@@ -0,0 +1,38 @@
# Security Policy
We take the security of Documenso seriously. As a platform trusted with legally binding documents, the safety of the project and the people who rely on it is a priority for us. We're grateful to the security researchers who help keep it that way. If you've found an issue, we'd genuinely like to hear about it.
## Reporting a Vulnerability
Report security vulnerabilities privately. Do not open a public issue, discussion, or pull request for security reports.
We accept reports through two channels, in order of preference:
1. **GitHub Security Advisories (preferred)**. Use the [private vulnerability reporting form](https://github.com/documenso/documenso/security/advisories/new). This is our primary channel and lets us triage and work with you on a fix.
2. **Email**. If you cannot use GitHub Security Advisories, email [security@documenso.com](mailto:security@documenso.com).
Include the affected version, a clear description, steps to reproduce, and the potential impact.
## Triage and Response
We triage reports as we have availability. We read every report we receive, and we appreciate the time and effort it takes to put one together.
We also run [Codex](https://openai.com/codex/) security analysis across the codebase. If Codex has already reported the issue you're sending us, we may close your report as a duplicate. Please don't take this as a reflection on your work; it just means our automated tooling happened to surface the same thing first.
## Scope
This policy covers vulnerabilities in the Documenso application code in this repository.
The items below are out of scope and will not be accepted. They are deployment, infrastructure, and configuration concerns that belong with the operator's firewall, network, and environment setup, not the application:
- Server-Side Request Forgery (SSRF) and related network-egress concerns
- DNS rebinding and other DNS-level issues
- Rate limiting, denial of service, and volumetric attacks
- TLS and certificate configuration, HTTP security headers, and other reverse-proxy or web-server configuration
- Findings that depend on insecure self-hosted infrastructure or misconfiguration
If you're unsure whether something is in scope, report it privately anyway and we'll happily take a look.
## Supported Versions
Security fixes are applied to the latest release. Run the most recent version of Documenso.
+6 -64
View File
@@ -1,67 +1,9 @@
# Creating your own signing certificate
# Signing Certificate
For the digital signature of your documents you need a signing certificate in .p12 format (public and private key). You can buy one (not recommended for dev) or use the steps to create a self-signed one:
Documenso needs a signing certificate to digitally sign documents. For full, up-to-date instructions on generating, converting, and configuring a certificate, see the official documentation:
1. Generate a private key using the OpenSSL command. You can run the following command to generate a 2048-bit RSA key:
- [Signing Certificate](https://docs.documenso.com/docs/self-hosting/configuration/signing-certificate): Overview and all certificate options
- [Local Certificate](https://docs.documenso.com/docs/self-hosting/configuration/signing-certificate/local): Generate a self-signed `.p12` certificate with OpenSSL
- [Google Cloud HSM](https://docs.documenso.com/docs/self-hosting/configuration/signing-certificate/google-cloud-hsm): Sign using Google Cloud KMS
`openssl genrsa -out private.key 2048`
2. Generate a self-signed certificate using the private key. You can run the following command to generate a self-signed certificate:
`openssl req -new -x509 -key private.key -out certificate.crt -days 365`
This will prompt you to enter some information, such as the Common Name (CN) for the certificate. Make sure you enter the correct information. The `-days` parameter sets the number of days for which the certificate is valid.
3. Combine the private key and the self-signed certificate to create the p12 certificate. You can run the following commands to do this:
```bash
# Set certificate password securely (won't appear in command history)
read -s -p "Enter certificate password: " CERT_PASS
echo
# Create the p12 certificate using the environment variable
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt \
-password env:CERT_PASS \
-keypbe PBE-SHA1-3DES \
-certpbe PBE-SHA1-3DES \
-macalg sha1
```
4. **IMPORTANT**: A certificate password is required to prevent signing failures. Make sure to use a strong password (minimum 4 characters) when prompted. Certificates without passwords will cause "Failed to get private key bags" errors during document signing.
5. Place the certificate `/apps/remix/resources/certificate.p12` (If the path does not exist, it needs to be created)
## Docker
> We are still working on the publishing of docker images, in the meantime you can follow the steps below to create a production ready docker image.
Want to create a production ready docker image? Follow these steps:
- cd into `docker` directory
- Make `build.sh` executable by running `chmod +x build.sh`
- Run `./build.sh` to start building the docker image.
- Publish the image to your docker registry of choice (or) If you prefer running the image from local, run the below command
```
docker run -d --restart=unless-stopped -p 3000:3000 -v documenso:/app/data --name documenso documenso:latest
```
Command Breakdown:
- `-d` - Let's you run the container in background
- `-p` - Passes down which ports to use. First half is the host port, Second half is the app port. You can change the first half anything you want and reverse proxy to that port.
- `-v` - Volume let's you persist the data
- `--name` - Name of the container
- `documenso:latest` - Image you have built
## Deployment
We support a variety of deployment methods, and are actively working on adding more. Stay tuned for updates!
## Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/DjrRRX)
## Render
[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/documenso/documenso)
For deploying Documenso with Docker, see the [Docker Deployment](https://docs.documenso.com/docs/self-hosting/deployment/docker) and [Docker Compose](https://docs.documenso.com/docs/self-hosting/deployment/docker-compose) guides.
+9 -38
View File
@@ -1,45 +1,16 @@
# docs
# @documenso/docs
This is a Next.js application generated with
[Create Fumadocs](https://github.com/fuma-nama/fumadocs).
The Documenso documentation site, built with [Next.js](https://nextjs.org/) and [Fumadocs](https://fumadocs.dev/). Published at [docs.documenso.com](https://docs.documenso.com).
Run development server:
Content lives under `content/docs/` as MDX. See [WRITING_STYLE.md](../../WRITING_STYLE.md) for the documentation writing conventions.
```bash
npm run dev
# or
pnpm dev
# or
yarn dev
# From the monorepo root
npm run dev --filter=@documenso/docs
```
Open http://localhost:3000 with your browser to see the result.
## Structure
## Explore
In the project, you can see:
- `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content.
- `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep.
| Route | Description |
| ------------------------- | ------------------------------------------------------ |
| `app/(home)` | The route group for your landing page and other pages. |
| `app/docs` | The documentation layout and pages. |
| `app/api/search/route.ts` | The Route Handler for search. |
### Fumadocs MDX
A `source.config.ts` config file has been included, you can customise different options like frontmatter schema.
Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details.
## Learn More
To learn more about Next.js and Fumadocs, take a look at the following
resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js
features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
- [Fumadocs](https://fumadocs.dev) - learn about Fumadocs
- `content/docs/`: Documentation pages (MDX).
- `lib/source.ts`: Content source adapter.
- `lib/layout.shared.tsx`: Shared layout options.
+1 -1
View File
@@ -10,4 +10,4 @@
"baseDir": "src",
"uiLibrary": "radix-ui",
"commands": {}
}
}
@@ -12,7 +12,7 @@ import { Callout } from 'fumadocs-ui/components/callout';
| 21 CFR Part 11 | Compliant (Enterprise) |
| SOC 2 | Compliant |
| ISO 27001 | Planned |
| HIPAA | Planned |
| HIPAA | Compliant (Enterprise) |
## 21 CFR Part 11
@@ -97,12 +97,12 @@ Documenso implements digital signatures with the following characteristics:
- **Timestamps**: RFC 3161 timestamps can be applied to signatures
- **Signature visualization**: Signed documents include visual signature representations
For specific implementation details and configuration options, refer to the [signing certificates](/signing-certificates/overview) documentation.
For specific implementation details and configuration options, refer to the [signing certificates](/docs/concepts/signing-certificates) documentation.
Self-hosted deployments can configure their own signing certificates and timestamp authorities to meet specific compliance requirements.
## Related
- [Legal Validity](/compliance/legal-validity) - Legal frameworks for electronic signatures
- [Signing Certificates Overview](/signing-certificates/overview) - Certificate configuration
- [Audit Log](/features/audit-log) - Document activity tracking
- [E-Sign Compliance](/docs/compliance/esign) - Legal frameworks for electronic signatures
- [Signing Certificates](/docs/concepts/signing-certificates) - Certificate configuration
- [Signing Workflow](/docs/concepts/signing-workflow) - Document activity and audit trail
+1 -7
View File
@@ -1,10 +1,4 @@
{
"title": "Concepts",
"pages": [
"document-lifecycle",
"recipient-roles",
"field-types",
"signing-workflow",
"signing-certificates"
]
"pages": ["document-lifecycle", "recipient-roles", "field-types", "signing-workflow", "signing-certificates"]
}
@@ -167,5 +167,5 @@ To enable sequential signing:
## Related
- [Add Recipients](/users/documents/add-recipients) - How to add recipients to a document
- [Field Types](/concepts/field-types) - Learn about the different field types you can assign to recipients
- [Add Recipients](/docs/users/documents/add-recipients) - How to add recipients to a document
- [Field Types](/docs/concepts/field-types) - Learn about the different field types you can assign to recipients
@@ -0,0 +1,45 @@
---
title: Common Errors
description: A comprehensive troubleshooting matrix for Documenso API and Webhook integration errors.
---
This guide provides a comprehensive troubleshooting matrix for the standard error codes returned by the Documenso API. Use this reference to diagnose and resolve integration issues related to envelopes, recipients, and webhooks.
## Application Error Codes
| Error Code | Description | Recommended Action |
| :--- | :--- | :--- |
| `ALREADY_EXISTS` | The resource you are attempting to create already exists. | Verify if the entity (e.g., user, envelope, webhook) has already been instantiated. Use a `PUT` or `PATCH` request to update the existing resource instead of `POST`. |
| `EXPIRED_CODE` | The provided access code or token has expired. | Generate a new access code or request a new invitation link before retrying the request. |
| `INVALID_BODY` | The request payload is malformed. | Inspect your JSON payload structure. Ensure it strictly adheres to the expected schema and that no required fields are missing. |
| `INVALID_REQUEST` | The overall request is malformed or invalid. | Review your API call parameters, including the URL, query parameters, and headers. Correct the request syntax. |
| `RECIPIENT_EXPIRED` | The signing link or recipient access has expired. | Generate and resend a new invitation to the affected recipient. |
| `LIMIT_EXCEEDED` | Your account usage quota has been exceeded. | Check your current plan limits. Upgrade your subscription or wait until your billing cycle renews. |
| `NOT_FOUND` | The requested resource could not be found (404). | Verify the resource ID (envelope, document, webhook) passed in the URL. Ensure the resource has not been deleted. |
| `NOT_IMPLEMENTED` | The requested feature is not currently supported by the server. | Consult the API documentation to verify available methods. Do not use this endpoint at this time. |
| `NOT_SETUP` | The required configuration for this action is incomplete. | Access your account or integration settings and complete the necessary configuration before retrying. |
| `INVALID_CAPTCHA` | Security token (Captcha) validation failed. | Ensure the Captcha token is correctly generated on the client side and transmitted without alteration in your request. |
| `UNAUTHORIZED` | Missing or invalid authentication (401). | Verify that your API key is correct, active, and properly formatted in the `Authorization` header (e.g., `Bearer <YOUR_API_KEY>`). |
| `FORBIDDEN` | Access to the resource is denied (403). | Ensure your API key or user account has the necessary permissions and roles to execute this specific action. |
| `UNKNOWN_ERROR` | An unexpected internal server error occurred (500). | Retry the request later. If the issue persists, contact technical support with your request payload and the timestamp of the incident. |
| `RETRY_EXCEPTION` | The operation failed temporarily but can be retried. | Implement an automatic retry logic in your integration, ideally using an exponential backoff strategy. |
| `SCHEMA_FAILED` | Strict data schema validation failed. | Verify that the data types sent (string, number, boolean) exactly match the OpenAPI specification. |
| `TOO_MANY_REQUESTS` | Rate limit exceeded (429). | Reduce the frequency of your API calls. Implement rate-limiting handling based on the response headers. |
| `TWO_FACTOR_AUTH_FAILED` | Two-factor authentication (2FA) failed. | Verify the provided 2FA code. Ensure it was entered correctly and has not expired. |
| `WEBHOOK_INVALID_REQUEST` | The webhook-related request is invalid. | Check your receiving endpoint configuration. Ensure the URL is correct and that your server accepts `POST` requests from Documenso. |
## Envelope State Errors
The following errors occur when attempting to perform actions on an envelope that are incompatible with its current state.
| Error Code | Description | Recommended Action |
| :--- | :--- | :--- |
| `ENVELOPE_DRAFT` | The action cannot be performed because the envelope is still in a draft state. | Finalize the envelope configuration and transition it to the `PENDING` (sent) state before attempting this operation. |
| `ENVELOPE_COMPLETED` | The action cannot be performed because the envelope is already completed. | No further modifications (e.g., adding signers, modifying documents) can be made to an envelope once the signing process is finished. |
| `ENVELOPE_REJECTED` | The action cannot be performed because the envelope was rejected by a recipient. | The signing flow is permanently halted. Create a new envelope if you wish to resubmit the document. |
| `ENVELOPE_LEGACY` | The action cannot be performed because the envelope uses an obsolete format. | This envelope was created with a legacy version of the system. Recreate the envelope using the current API version to interact with it. |
## See Also
- [Documents API](/docs/developers/api/documents)
- [Webhooks](/docs/developers/webhooks)
@@ -6,6 +6,8 @@ description: Create, manage, and send documents for signing via the API.
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<EnvelopeWarning />
<Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com).
@@ -5,6 +5,8 @@ description: Complete reference for the Documenso REST API.
import { Callout } from 'fumadocs-ui/components/callout';
<EnvelopeWarning />
<Callout type="warn">
The guides below cover common API patterns but may not reflect the latest endpoints or parameters.
For an always up-to-date reference, see the [OpenAPI Reference](https://openapi.documenso.com).
@@ -8,6 +8,8 @@
"teams",
"rate-limits",
"versioning",
"developer-mode"
"migrate-to-envelopes",
"developer-mode",
"common-errors"
]
}
@@ -0,0 +1,249 @@
---
title: Migrating to Envelopes
description: Why Documenso unified documents and templates into envelopes, and how to migrate from the deprecated document and template create endpoints.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Summary
The following items have been deprecated and will be removed on the <strong>1st of March 2027</strong>:
- <strong>API V1</strong>
- <strong>A subset of SDK/API V2 endpoints</strong>
- <strong>Legacy documents and templates</strong>
- <strong>EmbedCreateDocumentV1</strong>
- <strong>EmbedCreateTemplateV1</strong>
- <strong>EmbedUpdateDocumentV1</strong>
- <strong>EmbedUpdateTemplateV1</strong>
The beta endpoint `/api/v2-beta` will also be removed. Use `/api/v2` instead, which is a drop-in replacement.
Nothing breaks before 1st of March 2027, so you can migrate at your own pace.
## What are legacy documents and templates
These are documents and templates created by the following endpoints:
- `POST /api/v2/document/create`
- `POST /api/v2/document/create/beta`
- `POST /api/v2/template/create`
- `POST /api/v2/template/create/beta`
- `POST /api/v1/documents`
- `POST /api/v1/templates`
- `POST /api/v1/templates/create-document`
- `POST /api/v1/templates/generate-document`
## What replaces legacy documents and templates
At the end of 2025 we introduced a unified system for documents and templates, called <strong>envelopes</strong>.
We still reference documents and templates throughout the documentation and application to distinguish them, but internally they are envelopes.
Moving to the envelope system gives you:
- **Multiple PDFs in one envelope.** Send several documents to sign in a single request.
- **One API for documents and templates.** Learn one set of endpoints instead of two misaligned ones.
- **A better editor and signing experience** for you and your recipients.
## How to migrate
{/* prettier-ignore */}
<Steps>
<Step>
### Switch to the envelope endpoints
Replace each deprecated endpoint with its `/api/v2/envelope/*` equivalent from the [mapping tables](#endpoint-mapping-reference) below.
</Step>
<Step>
### Set the envelope `type` on create
A single endpoint, `POST /api/v2/envelope/create`, can create both documents and templates. Set `type` to `DOCUMENT` or `TEMPLATE`. You can now upload more than one PDF using the `files` field.
</Step>
<Step>
### Update how you store IDs
Envelope IDs are **strings** (for example `envelope_abc123`), not numbers. Update any code that stores, parses, or compares IDs.
</Step>
<Step>
### Test, then remove the old calls
Verify the new flow against your account, then delete the deprecated calls.
</Step>
</Steps>
The main data differences are as follows:
- ID format changed from number to string (e.g. `42` to `envelope_abc123`)
- pageNumber becomes page
- pageX becomes positionX
- pageY becomes positionY
See the [Documents API](/docs/developers/api/documents) and [Templates API](/docs/developers/api/templates) for the full envelope reference.
### Deprecated V1 API Endpoints
Full reference in the [V1 OpenAPI reference](https://openapi-v1.documenso.com).
| Deprecated endpoint | Replacement |
| -------------------------------------------------------- | ----------------------------------------------------- |
| `GET /api/v1/documents` | `GET /api/v2/envelope` |
| `GET /api/v1/documents/{id}` | `GET /api/v2/envelope/{envelopeId}` |
| `POST /api/v1/documents` | `POST /api/v2/envelope/create` |
| `POST /api/v1/documents/{id}/send` | `POST /api/v2/envelope/distribute` |
| `POST /api/v1/documents/{id}/resend` | `POST /api/v2/envelope/redistribute` |
| `DELETE /api/v1/documents/{id}` | `POST /api/v2/envelope/delete` |
| `GET /api/v1/documents/{id}/download` | `GET /api/v2/envelope/item/{envelopeItemId}/download` |
| `POST /api/v1/documents/{id}/recipients` | `POST /api/v2/envelope/recipient/create-many` |
| `PATCH /api/v1/documents/{id}/recipients/{recipientId}` | `POST /api/v2/envelope/recipient/update-many` |
| `DELETE /api/v1/documents/{id}/recipients/{recipientId}` | `POST /api/v2/envelope/recipient/delete` |
| `POST /api/v1/documents/{id}/fields` | `POST /api/v2/envelope/field/create-many` |
| `PATCH /api/v1/documents/{id}/fields/{fieldId}` | `POST /api/v2/envelope/field/update-many` |
| `DELETE /api/v1/documents/{id}/fields/{fieldId}` | `POST /api/v2/envelope/field/delete` |
| `GET /api/v1/templates` | `GET /api/v2/envelope` (with `type=TEMPLATE`) |
| `GET /api/v1/templates/{id}` | `GET /api/v2/envelope/{envelopeId}` |
| `POST /api/v1/templates` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) |
| `DELETE /api/v1/templates/{id}` | `POST /api/v2/envelope/delete` |
| `POST /api/v1/templates/{templateId}/create-document` | `POST /api/v2/envelope/use` |
| `POST /api/v1/templates/{templateId}/generate-document` | `POST /api/v2/envelope/use` |
### Deprecated V2 API Endpoints
Full reference in the [V2 OpenAPI reference](https://openapi.documenso.com).
#### Documents
| Deprecated endpoint | Replacement |
| ------------------------------------------------- | ----------------------------------------------------- |
| `GET /api/v2/document` | `GET /api/v2/envelope` |
| `GET /api/v2/document/{documentId}` | `GET /api/v2/envelope/{envelopeId}` |
| `POST /api/v2/document/get-many` | `POST /api/v2/envelope/get-many` |
| `POST /api/v2/document/create` | `POST /api/v2/envelope/create` |
| `POST /api/v2/document/create/beta` | `POST /api/v2/envelope/create` |
| `POST /api/v2/document/update` | `POST /api/v2/envelope/update` |
| `POST /api/v2/document/delete` | `POST /api/v2/envelope/delete` |
| `POST /api/v2/document/duplicate` | `POST /api/v2/envelope/duplicate` |
| `POST /api/v2/document/distribute` | `POST /api/v2/envelope/distribute` |
| `POST /api/v2/document/redistribute` | `POST /api/v2/envelope/redistribute` |
| `GET /api/v2/document/attachment` | `GET /api/v2/envelope/attachment` |
| `POST /api/v2/document/attachment/create` | `POST /api/v2/envelope/attachment/create` |
| `POST /api/v2/document/attachment/update` | `POST /api/v2/envelope/attachment/update` |
| `POST /api/v2/document/attachment/delete` | `POST /api/v2/envelope/attachment/delete` |
| `GET /api/v2/document/{documentId}/download` | `GET /api/v2/envelope/item/{envelopeItemId}/download` |
| `GET /api/v2/document/{documentId}/download-beta` | `GET /api/v2/envelope/item/{envelopeItemId}/download` |
#### Templates
| Deprecated endpoint | Replacement |
| ------------------------------------- | ------------------------------------------------ |
| `GET /api/v2/template` | `GET /api/v2/envelope` (with `type=TEMPLATE`) |
| `GET /api/v2/template/{templateId}` | `GET /api/v2/envelope/{envelopeId}` |
| `POST /api/v2/template/get-many` | `POST /api/v2/envelope/get-many` |
| `POST /api/v2/template/create` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) |
| `POST /api/v2/template/create/beta` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) |
| `POST /api/v2/template/update` | `POST /api/v2/envelope/update` |
| `POST /api/v2/template/duplicate` | `POST /api/v2/envelope/duplicate` |
| `POST /api/v2/template/delete` | `POST /api/v2/envelope/delete` |
| `POST /api/v2/template/use` | `POST /api/v2/envelope/use` |
| `POST /api/v2/template/direct/create` | **Pending replacement** |
| `POST /api/v2/template/direct/delete` | **Pending replacement** |
| `POST /api/v2/template/direct/toggle` | **Pending replacement** |
#### Document fields
| Deprecated endpoint | Replacement |
| ----------------------------------------- | ----------------------------------------- |
| `GET /api/v2/document/field/{fieldId}` | `GET /api/v2/envelope/field/{fieldId}` |
| `POST /api/v2/document/field/create` | `POST /api/v2/envelope/field/create-many` |
| `POST /api/v2/document/field/create-many` | `POST /api/v2/envelope/field/create-many` |
| `POST /api/v2/document/field/update` | `POST /api/v2/envelope/field/update-many` |
| `POST /api/v2/document/field/update-many` | `POST /api/v2/envelope/field/update-many` |
| `POST /api/v2/document/field/delete` | `POST /api/v2/envelope/field/delete` |
#### Template fields
| Deprecated endpoint | Replacement |
| ----------------------------------------- | ----------------------------------------- |
| `GET /api/v2/template/field/{fieldId}` | `GET /api/v2/envelope/field/{fieldId}` |
| `POST /api/v2/template/field/create` | `POST /api/v2/envelope/field/create-many` |
| `POST /api/v2/template/field/create-many` | `POST /api/v2/envelope/field/create-many` |
| `POST /api/v2/template/field/update` | `POST /api/v2/envelope/field/update-many` |
| `POST /api/v2/template/field/update-many` | `POST /api/v2/envelope/field/update-many` |
| `POST /api/v2/template/field/delete` | `POST /api/v2/envelope/field/delete` |
#### Document recipients
| Deprecated endpoint | Replacement |
| ---------------------------------------------- | ---------------------------------------------- |
| `GET /api/v2/document/recipient/{recipientId}` | `GET /api/v2/envelope/recipient/{recipientId}` |
| `POST /api/v2/document/recipient/create` | `POST /api/v2/envelope/recipient/create-many` |
| `POST /api/v2/document/recipient/create-many` | `POST /api/v2/envelope/recipient/create-many` |
| `POST /api/v2/document/recipient/update` | `POST /api/v2/envelope/recipient/update-many` |
| `POST /api/v2/document/recipient/update-many` | `POST /api/v2/envelope/recipient/update-many` |
| `POST /api/v2/document/recipient/delete` | `POST /api/v2/envelope/recipient/delete` |
#### Template recipients
| Deprecated endpoint | Replacement |
| ---------------------------------------------- | ---------------------------------------------- |
| `GET /api/v2/template/recipient/{recipientId}` | `GET /api/v2/envelope/recipient/{recipientId}` |
| `POST /api/v2/template/recipient/create` | `POST /api/v2/envelope/recipient/create-many` |
| `POST /api/v2/template/recipient/create-many` | `POST /api/v2/envelope/recipient/create-many` |
| `POST /api/v2/template/recipient/update` | `POST /api/v2/envelope/recipient/update-many` |
| `POST /api/v2/template/recipient/update-many` | `POST /api/v2/envelope/recipient/update-many` |
| `POST /api/v2/template/recipient/delete` | `POST /api/v2/envelope/recipient/delete` |
### Embedding components
| Deprecated component | Replacement |
| ----------------------- | --------------------- |
| `EmbedCreateDocumentV1` | `EmbedCreateEnvelope` |
| `EmbedCreateTemplateV1` | `EmbedCreateEnvelope` |
| `EmbedUpdateDocumentV1` | `EmbedUpdateEnvelope` |
| `EmbedUpdateTemplateV1` | `EmbedUpdateEnvelope` |
See the [embedding guide](/docs/developers/embedding) for the envelope components.
## FAQ
<Accordions>
<Accordion title="What happens on 1 March 2027?">
The deprecated V1 API, the V2 endpoints listed above, and the V1 embedding components are removed.
Requests to them will fail, so migrate to the envelope API before that date.
</Accordion>
<Accordion title="Will my existing documents and templates keep working?">
Yes. Documents and templates you already created remain in your account and continue to work. They will automatically be converted to envelopes. Only
the deprecated endpoints you call are going away. Your data is not deleted.
</Accordion>
<Accordion title="Do I need a new API token?">
No. Authentication is unchanged. The same API token works for the envelope endpoints under
`https://app.documenso.com/api/v2`.
</Accordion>
<Accordion title="What is the difference between a document and a template now?">
Both are envelopes, distinguished by a `type` field of `DOCUMENT` or `TEMPLATE`. They share the same
endpoints, recipients, fields, and attachments.
</Accordion>
<Accordion title="I use an official SDK, what should I do?">
The function calls to the legacy endpoints will break on the 1st of March 2027. Update to the latest SDK version and switch to its envelope methods.
The deprecated document and template methods map to the envelope endpoints in the tables above.
</Accordion>
<Accordion title="I need more time or help migrating">
Reach out to [support@documenso.com](mailto:support@documenso.com) with your use case and we will
help you plan the migration.
</Accordion>
</Accordions>
## Getting help
- [V2 OpenAPI reference](https://openapi.documenso.com): the up-to-date envelope API.
- [V1 OpenAPI reference](https://openapi-v1.documenso.com): the deprecated V1 API.
- [support@documenso.com](mailto:support@documenso.com): migration questions and extensions.
## See also
- [Documents API](/docs/developers/api/documents): create and manage envelopes
- [Templates API](/docs/developers/api/templates): work with templates and direct links
- [Fields API](/docs/developers/api/fields) and [Recipients API](/docs/developers/api/recipients)
- [API Versioning](/docs/developers/api/versioning): how Documenso versions the public API
@@ -11,9 +11,14 @@ Documenso enforces rate limits on all API endpoints to ensure service stability.
## HTTP Rate Limits
**Limit:** 100 requests per minute per IP address
**Limit:** 1000 requests per minute per IP address
**Response:** 429 Too Many Requests
<Callout type="info">
This is the global per-IP ceiling. Your organisation may have its own rate limits configured below
this value, in which case you can be rate-limited before reaching the global limit.
</Callout>
### Rate Limit Response
```json
@@ -65,3 +70,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)
@@ -6,6 +6,8 @@ description: Create documents from reusable templates via API.
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<EnvelopeWarning />
<Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com).
@@ -5,6 +5,8 @@ description: Versioning information for the Documenso public API.
import { Callout } from 'fumadocs-ui/components/callout';
<EnvelopeWarning />
## Overview
Documenso uses API versioning to manage changes to the public API. This allows us to introduce new features, fix bugs, and make other changes without breaking existing integrations.
@@ -19,7 +21,16 @@ Also, we may deprecate certain features or endpoints in the API. When we depreca
---
## Documents, Templates, and Envelopes
Documenso has unified documents and templates into a single resource called an **envelope**. New integrations should create documents and templates through the `/envelope/*` endpoints. The `POST /document/create` and `POST /template/create` endpoints (including their `/beta` variants) are deprecated in favor of `POST /envelope/create`.
See [Migrating to the Envelope API](/docs/developers/api/migrate-to-envelopes) for the rationale and step-by-step migration examples.
---
## See Also
- [Migrating to the Envelope API](/docs/developers/api/migrate-to-envelopes) - Move from the document and template create endpoints
- [Authentication](/docs/developers/getting-started/authentication) - API authentication guide
- [Rate Limits](/docs/developers/api/rate-limits) - API rate limit details
@@ -1,24 +1,24 @@
---
title: Authoring
title: Editor
description: Embed document, template, and envelope creation directly in your application.
---
import { Callout } from 'fumadocs-ui/components/callout';
In addition to embedding signing, Documenso supports embedded authoring. It allows your users to create and edit documents, templates, and envelopes without leaving your application.
In addition to embedding signing, Documenso supports embedded editor. It allows your users to create and edit documents, templates, and envelopes without leaving your application.
<Callout type="warn">
Embedded authoring is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
Embedded editor is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
also available as a paid add-on for the [Platform Plan](https://documen.so/platform-cta-pricing).
Contact sales for access.
</Callout>
## Versions
Embedded authoring is available in two versions:
Embedded editor is available in two versions:
- **[V1 Authoring](/docs/developers/embedding/authoring/v1)** — Works with V1 Documents and Templates.
- **[V2 Authoring](/docs/developers/embedding/authoring/v2)** — Works with Envelopes, which are the unified model for documents and templates.
- **[V1 Editor](/docs/developers/embedding/editor/v1)** — Works with V1 Documents and Templates.
- **[V2 Editor](/docs/developers/embedding/editor/v2)** — Works with Envelopes, which are the unified model for documents and templates.
### Comparison
@@ -32,7 +32,7 @@ Embedded authoring is available in two versions:
## Presign Tokens
Before using any authoring component, obtain a presign token from your backend:
Before using any editor component, obtain a presign token from your backend:
```
POST /api/v2/embedding/create-presign-token
@@ -50,7 +50,7 @@ See the [API documentation](https://openapi.documenso.com/reference#tag/embeddin
## Next Steps
- [V1 Authoring](/docs/developers/embedding/authoring/v1) — Create and edit documents and templates using V1 components
- [V2 Authoring](/docs/developers/embedding/authoring/v2) — Create and edit envelopes using V2 components
- [V1 Editor](/docs/developers/embedding/editor/v1) — Create and edit documents and templates using V1 components
- [V2 Editor](/docs/developers/embedding/editor/v2) — Create and edit envelopes using V2 components
- [CSS Variables](/docs/developers/embedding/css-variables) — Customize the appearance of embedded components
- [SDKs](/docs/developers/embedding/sdks) — Framework-specific SDK documentation
@@ -1,4 +1,4 @@
{
"title": "Authoring",
"title": "Editor",
"pages": ["v1", "v2"]
}
@@ -1,21 +1,21 @@
---
title: V1 Authoring
title: V1 Editor
description: Embed V1 document and template creation directly in your application.
---
import { Callout } from 'fumadocs-ui/components/callout';
V1 authoring components allow your users to create and edit documents and templates using the V1 Documents and Templates API without leaving your application.
V1 editor components allow your users to create and edit documents and templates using the V1 Documents and Templates API without leaving your application.
<Callout type="warn">
Embedded authoring is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
Embedded editor is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
also available as a paid add-on for the [Platform Plan](https://documen.so/platform-cta-pricing).
Contact sales for access.
</Callout>
## Components
The SDK provides four V1 authoring components:
The SDK provides four V1 editor components:
| Component | Purpose |
| ----------------------- | ----------------------- |
@@ -29,7 +29,7 @@ The SDK provides four V1 authoring components:
## Presign Tokens
All authoring components require a **presign token** for authentication. See the [Authoring overview](/docs/developers/embedding/authoring) for details on obtaining presign tokens.
All editor components require a **presign token** for authentication. See the [Editor overview](/docs/developers/embedding/editor) for details on obtaining presign tokens.
<Callout type="warn">
@@ -131,7 +131,7 @@ const TemplateEditor = ({ presignToken, templateId }) => {
## Props
### All Authoring Components
### All Editor Components
| Prop | Type | Required | Description |
| ------------------ | --------- | -------- | -------------------------------------------------------- |
@@ -143,7 +143,7 @@ const TemplateEditor = ({ presignToken, templateId }) => {
| `darkModeDisabled` | `boolean` | No | Disable dark mode (Platform Plan) |
| `language` | `string` | No | Set the UI language. See [Supported Languages](https://github.com/documenso/documenso/tree/main/packages/lib/constants/locales.ts) |
| `className` | `string` | No | CSS class for the iframe |
| `features` | `object` | No | Feature toggles for the authoring experience |
| `features` | `object` | No | Feature toggles for the editor experience |
### Update Components Only
@@ -157,7 +157,7 @@ const TemplateEditor = ({ presignToken, templateId }) => {
## Feature Toggles
Customize what options are available in the authoring experience:
Customize what options are available in the editor experience:
```jsx
<EmbedCreateDocumentV1
@@ -294,7 +294,7 @@ Pass extra props to the iframe for testing experimental features:
## See Also
- [Embedding Overview](/docs/developers/embedding) - Signing embed concepts and props
- [V2 Authoring](/docs/developers/embedding/authoring/v2) - V2 envelope authoring
- [V2 Editor](/docs/developers/embedding/editor/v2) - V2 envelope editor
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Documents API](/docs/developers/api/documents) - Create documents via API
- [Templates API](/docs/developers/api/templates) - Create templates via API
@@ -1,21 +1,21 @@
---
title: V2 Authoring
title: V2 Editor
description: Embed envelope creation and editing directly in your application.
---
import { Callout } from 'fumadocs-ui/components/callout';
V2 authoring components allow your users to create and edit envelopes without leaving your application. Envelopes are the unified model for documents and templates in the V2 API.
V2 editor components allow your users to create and edit envelopes without leaving your application. Envelopes are the unified model for documents and templates in the V2 API.
<Callout type="warn">
Embedded authoring is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
Embedded editor is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
also available as a paid add-on for the [Platform Plan](https://documen.so/platform-cta-pricing).
Contact sales for access.
</Callout>
## Components
The SDK provides two V2 authoring components:
The SDK provides two V2 editor components:
| Component | Purpose |
| ---------------------- | ------------------------ |
@@ -26,7 +26,7 @@ The SDK provides two V2 authoring components:
## Presign Tokens
All authoring components require a **presign token** for authentication. See the [Authoring overview](/docs/developers/embedding/authoring) for details on obtaining presign tokens.
All editor components require a **presign token** for authentication. See the [Editor overview](/docs/developers/embedding/editor) for details on obtaining presign tokens.
<Callout type="warn">
A presigned token is NOT an API token
@@ -100,7 +100,7 @@ const EnvelopeEditor = ({ presignToken, envelopeId }) => {
## Props
### All V2 Authoring Components
### All V2 Editor Components
| Prop | Type | Required | Description |
| ---------------- | --------- | -------- | -------------------------------------------------------- |
@@ -113,7 +113,7 @@ const EnvelopeEditor = ({ presignToken, envelopeId }) => {
| `language` | `string` | No | Set the UI language. See [Supported Languages](https://github.com/documenso/documenso/tree/main/packages/lib/constants/locales.ts) |
| `className` | `string` | No | CSS class for the iframe |
| `user` | `object` | No | Current user info. When provided, enables the "Add Myself" button in the recipients list. Object with optional `email` and `name` fields |
| `features` | `object` | No | Feature toggles for the authoring experience |
| `features` | `object` | No | Feature toggles for the editor experience |
### Create Component Only
@@ -132,7 +132,7 @@ const EnvelopeEditor = ({ presignToken, envelopeId }) => {
## Feature Toggles
V2 authoring provides rich, structured feature toggles organized into sections. Pass a partial configuration to customize the authoring experience — any omitted fields will use their defaults.
V2 editor provides rich, structured feature toggles organized into sections. Pass a partial configuration to customize the editor experience — any omitted fields will use their defaults.
```jsx
<EmbedCreateEnvelope
@@ -160,7 +160,7 @@ V2 authoring provides rich, structured feature toggles organized into sections.
### General
Controls the overall authoring flow and UI:
Controls the overall editor flow and UI:
| Property | Type | Default | Description |
| ------------------------------- | --------- | ------- | ------------------------------------------------ |
@@ -188,7 +188,7 @@ Controls envelope configuration options. Set to `null` to hide envelope settings
### Actions
Controls available actions during authoring:
Controls available actions during editing:
| Property | Type | Default | Description |
| ------------------ | --------- | ------- | ------------------------ |
@@ -221,7 +221,7 @@ Controls recipient configuration options. Set to `null` to prevent any recipient
### Disabling Steps
You can also disable entire steps of the authoring flow. This allows you to skip steps that are not relevant to your use case:
You can also disable entire steps of the editor flow. This allows you to skip steps that are not relevant to your use case:
```jsx
<EmbedCreateEnvelope
@@ -338,7 +338,7 @@ const EnvelopeManager = ({ presignToken }) => {
## See Also
- [Authoring Overview](/docs/developers/embedding/authoring) - V1 vs V2 comparison and presign tokens
- [V1 Authoring](/docs/developers/embedding/authoring/v1) - V1 document and template authoring
- [Editor Overview](/docs/developers/embedding/editor) - V1 vs V2 comparison and presign tokens
- [V1 Editor](/docs/developers/embedding/editor/v1) - V1 document and template editor
- [Embedding Overview](/docs/developers/embedding) - Signing embed concepts and props
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
@@ -0,0 +1,81 @@
---
title: iframe
description: Embed the signing experience directly in your application using an iframe.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<Callout type="warn" title="iframes are not recommended">
Embedding via iframe is not recommended. We strongly recommend using the [official SDKs](/docs/developers/embedding/sdks) instead.
</Callout>
### Basic iframe Embedding
```html
<iframe
src="https://app.documenso.com/embed/sign/abc123xyz"
width="100%"
height="800"
frameborder="0"
allow="clipboard-write"
></iframe>
```
<Callout title="Use the correct embed URL">
The URL you embed depends on the embed mode youre using (for example direct links vs sign-token embeds). Use the
embed URL provided by Documenso for your flow.
</Callout>
### iframe Customization
You can customize the embedded signing experience by passing **encoded options in the iframe URL fragment** (everything
after `#`).
Documenso expects the fragment to be **base64** of:
- `encodeURIComponent(JSON.stringify(options))`
#### Supported options
| Option | Type | Description |
| ------ | ---- | ----------- |
| `name` | `string` | Prefill signer name. |
| `email` | `string` | Prefill signer email. |
| `lockName` | `boolean` | Lock the name field (prevents editing). |
| `lockEmail` | `boolean` | Lock the email field (prevents editing). |
| `language` | `string` | Force the embed language (e.g. `en`). |
| `darkModeDisabled` | `boolean` | Disable dark mode behavior. |
| `allowDocumentRejection` | `boolean` | Allow or disallow document rejection. |
| `css` | `string` | Inject custom CSS into the embed. |
| `cssVars` | `object` | Override embed CSS variables (see the CSS Variables page). |
#### Example
```ts
const buildEmbedSrc = (host: string, token: string) => {
const options = {
name: 'Ada Lovelace',
email: 'ada@example.com',
lockName: true,
lockEmail: true,
language: 'en',
darkModeDisabled: false,
allowDocumentRejection: true,
css: ':root { --radius: 12px; }',
cssVars: {},
};
const encodedOptions = btoa(encodeURIComponent(JSON.stringify(options)));
return `${new URL(`/embed/sign/${token}`, host).toString()}#${encodedOptions}`;
};
```
A complete example can be found in the [Embeds repository](https://github.com/documenso/embeds/blob/main/packages/mitosis/src/sign-document.lite.tsx).
<Callout type="info" title="Why use the URL fragment?">
The fragment is **not sent to the server** as part of the HTTP request, but it is available to the embedded app in
the browser. This makes it a convenient way to pass client-side configuration without changing the base embed URL.
</Callout>
@@ -6,14 +6,14 @@ description: Embed document signing experiences directly in your application usi
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Embedded Signing vs Embedded Authoring
## Embedded Signing vs Embedded Editor
Documenso offers two types of embedding:
- **Embedded Signing** lets you embed the signing experience in your application. Your users sign documents without leaving your site. Available on Teams Plan and above.
- **Embedded Authoring** lets you embed document and template _creation and editing_ in your application. This is an [Enterprise](/docs/policies/enterprise-edition) feature (also available as a Platform Plan add-on). See the [Authoring](/docs/developers/embedding/authoring) guide.
- **Embedded Editor** lets you embed document and template _creation and editing_ in your application. This is an [Enterprise](/docs/policies/enterprise-edition) feature (also available as a Platform Plan add-on). See the [Editor](/docs/developers/embedding/editor) guide.
This page covers **embedded signing**. If you need your users to create or edit documents inside your app, see [Authoring](/docs/developers/embedding/authoring).
This page covers **embedded signing**. If you need your users to create or edit documents inside your app, see [Editor](/docs/developers/embedding/editor).
---
@@ -229,9 +229,9 @@ Receives an object with:
href="/docs/developers/embedding/css-variables"
/>
<Card
title="Authoring"
title="Editor"
description="Embed document and template creation."
href="/docs/developers/embedding/authoring"
href="/docs/developers/embedding/editor"
/>
</Cards>
@@ -1,4 +1,4 @@
{
"title": "Embedding",
"pages": ["sdks", "direct-links", "css-variables", "authoring"]
"pages": ["sdks", "direct-links", "css-variables", "editor", "iframe"]
}
@@ -89,4 +89,4 @@ export class SigningComponent {
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
- [Editor](/docs/developers/embedding/editor) - Embed document creation
@@ -93,4 +93,4 @@ See [CSS Variables](/docs/developers/embedding/css-variables) for all available
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
- [Editor](/docs/developers/embedding/editor) - Embed document creation
@@ -133,4 +133,4 @@ const DocumentSigning = ({ token }: { token: string }) => {
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
- [Editor](/docs/developers/embedding/editor) - Embed document creation
@@ -93,4 +93,4 @@ See [CSS Variables](/docs/developers/embedding/css-variables) for all available
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
- [Editor](/docs/developers/embedding/editor) - Embed document creation
@@ -101,4 +101,4 @@ See [CSS Variables](/docs/developers/embedding/css-variables) for all available
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
- [Editor](/docs/developers/embedding/editor) - Embed document creation
@@ -104,4 +104,4 @@ See [CSS Variables](/docs/developers/embedding/css-variables) for all available
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
- [Editor](/docs/developers/embedding/editor) - Embed document creation
@@ -8,6 +8,8 @@ import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<EnvelopeWarning />
## Workflow 1: Send a Document for Signature
The most common workflow: upload a PDF, add recipients with signature fields, and send for signing.
@@ -472,7 +474,7 @@ Send the same document to multiple recipients in parallel. Useful for policy ack
<code>distributeDocument: true</code>
</Step>
<Step>
Process in batches with a short delay to respect rate limits (e.g. 100 requests/minute)
Process in batches with a short delay to respect rate limits (e.g. 1000 requests/minute)
</Step>
</Steps>
@@ -638,8 +640,8 @@ done
</Tabs>
<Callout type="info">
The API allows 100 requests per minute. For large batches, implement rate limiting with delays
between requests to avoid hitting limits.
The API allows 1000 requests per minute (your organisation may have its own lower limit). For large
batches, implement rate limiting with delays between requests to avoid hitting limits.
</Callout>
---
@@ -3,6 +3,8 @@ title: Examples
description: Common integration patterns and end-to-end workflows.
---
<EnvelopeWarning />
<Cards>
<Card
title="Common Workflows"
@@ -7,6 +7,8 @@ import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
<EnvelopeWarning />
## Prerequisites
- A Documenso account (cloud or self-hosted)
@@ -73,14 +75,14 @@ Include the token in the `Authorization` header of your HTTP requests.
### cURL
```bash
curl https://app.documenso.com/api/v2/documents \
curl https://app.documenso.com/api/v2/document \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
```
### JavaScript / TypeScript
```typescript
const response = await fetch('https://app.documenso.com/api/v2/documents', {
const response = await fetch('https://app.documenso.com/api/v2/document', {
method: 'GET',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
@@ -7,6 +7,8 @@ import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<EnvelopeWarning />
## Prerequisites
Before starting, you need:
@@ -483,7 +485,7 @@ The API returns standard HTTP status codes and JSON error responses:
### Handling Rate Limits
The API allows 100 requests per minute per IP address. When rate limited, wait at least 60 seconds before retrying:
The API allows 1000 requests per minute per IP address. Your organisation may have its own lower rate limits. When rate limited, wait at least 60 seconds before retrying:
```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
@@ -3,6 +3,8 @@ title: Getting Started
description: Get your API key and make your first API call.
---
<EnvelopeWarning />
<Cards>
<Card
title="Authentication"
@@ -3,6 +3,8 @@ title: Developer Guide
description: Integrate Documenso into your applications using the REST API, webhooks, and embedding options.
---
<EnvelopeWarning />
## Getting Started
<Cards>
@@ -15,16 +15,17 @@ Pick the one that fits your needs the best.
## Tech Stack
- [Typescript](https://www.typescriptlang.org/) - Language
- [React Router](https://reactrouter.com/) - Framework
- [TypeScript](https://www.typescriptlang.org/) - Language
- [React Router v7](https://reactrouter.com/) - Framework
- [Hono](https://hono.dev/) - Server
- [Prisma](https://www.prisma.io/) - ORM
- [Tailwind](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
- [Tailwind CSS](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) + [Radix UI](https://www.radix-ui.com/) - Component Library
- [react-email](https://react.email/) - Email Templates
- [Lingui](https://lingui.dev/) - Internationalization
- [tRPC](https://trpc.io/) - API
- [@documenso/pdf-sign](https://www.npmjs.com/package/@documenso/pdf-sign) - PDF Signatures
- [React-PDF](https://github.com/wojtekmaj/react-pdf) - Viewing PDFs
- [PDF-Lib](https://github.com/Hopding/pdf-lib) - PDF manipulation
- [@libpdf/core](https://www.npmjs.com/package/@libpdf/core) - PDF Signing and Manipulation
- [pdf.js](https://mozilla.github.io/pdf.js/) - Viewing PDFs
- [Stripe](https://stripe.com/) - Payments
<div className="mt-16 flex items-center justify-center gap-4">
@@ -83,6 +83,15 @@ npm run prisma:seed -w @documenso/prisma
</Step>
<Step>
### Optional: configure job provider
The default local job provider does not support scheduled jobs required for document reminders.
See the [Background Jobs](/docs/self-hosting/configuration/background-jobs) page for more information.
</Step>
<Step>
### Start the application
@@ -105,6 +114,20 @@ Access the Documenso application by visiting `http://localhost:3000` in your web
certificate](/docs/developers/local-development/signing-certificate)**.
</Callout>
## Running Scripts with Environment Variables
If a package script does not automatically load your `.env` and `.env.local` files, wrap it with the `with:env` script:
```bash
npm run with:env -- npm run myscript
```
The same works for `npx` when running bin scripts:
```bash
npm run with:env -- npx myscript
```
## See Also
- [Developer Quickstart](/docs/developers/local-development/quickstart) - Quick Docker-based setup
@@ -33,13 +33,14 @@ All webhook events share a common structure:
| Field | Type | Description |
| ---------------- | --------- | ------------------------------------------------------ |
| `id` | number | Document or template ID |
| `id` | number | Legacy numeric v1 document or template ID |
| `envelopeId` | string | Canonical v2 identifier (`envelope_` + 16 characters) |
| `externalId` | string? | External identifier for integration |
| `userId` | number | Owner's user ID |
| `authOptions` | object? | Document-level authentication options |
| `formValues` | object? | PDF form values associated with the document |
| `title` | string | Document or template title |
| `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED` |
| `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED`, `CANCELLED` |
| `visibility` | string | Document visibility setting |
| `createdAt` | datetime | Document creation timestamp |
| `updatedAt` | datetime | Last modification timestamp |
@@ -47,8 +48,8 @@ All webhook events share a common structure:
| `deletedAt` | datetime? | Deletion timestamp |
| `teamId` | number? | Team ID if document belongs to a team |
| `templateId` | number? | Template ID if created from a template |
| `source` | string | Source: `DOCUMENT` or `TEMPLATE` |
| `documentMeta` | object | Document metadata (subject, message, signing options) |
| `source` | string | Source: `DOCUMENT`, `TEMPLATE`, or `TEMPLATE_DIRECT_LINK` |
| `documentMeta` | object? | Nullable document metadata (subject, message, signing options) |
| `recipients` | array | List of recipient objects |
| `Recipient` | array | List of recipient objects (legacy, same as recipients) |
@@ -60,7 +61,6 @@ All webhook events share a common structure:
| `subject` | string? | Email subject line |
| `message` | string? | Email message body |
| `timezone` | string | Timezone for date display |
| `password` | string? | Document access password (if set) |
| `dateFormat` | string | Date format string |
| `redirectUrl` | string? | URL to redirect after signing |
| `signingOrder` | string | `PARALLEL` or `SEQUENTIAL` |
@@ -77,8 +77,9 @@ All webhook events share a common structure:
| Field | Type | Description |
| ---------------------- | --------- | ------------------------------------------ |
| `id` | number | Recipient ID |
| `documentId` | number? | Parent document ID |
| `templateId` | number? | Template ID if created from a template |
| `envelopeId` | string | Canonical parent envelope ID |
| `documentId` | number? | Legacy parent document ID; null for templates |
| `templateId` | number? | Legacy parent template ID; null for documents |
| `email` | string | Recipient email address |
| `name` | string | Recipient name |
| `token` | string | Unique signing token |
@@ -94,6 +95,8 @@ All webhook events share a common structure:
| `sendStatus` | string | `NOT_SENT` or `SENT` |
| `rejectionReason` | string? | Reason if recipient rejected |
Use `recipient.envelopeId` as the reliable parent link. The legacy `documentId` and `templateId` fields depend on the parent envelope type, so one of them is always null.
---
## Document Lifecycle Events
@@ -111,6 +114,7 @@ Triggered when a new document is created.
"event": "DOCUMENT_CREATED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null,
"userId": 1,
"authOptions": null,
@@ -129,9 +133,8 @@ Triggered when a new document is created.
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"timezone": "Etc/UTC",
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"allowDictateNextSigner": false,
@@ -145,6 +148,7 @@ Triggered when a new document is created.
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
@@ -166,6 +170,7 @@ Triggered when a new document is created.
"Recipient": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
@@ -203,6 +208,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"event": "DOCUMENT_SENT",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null,
"userId": 1,
"authOptions": null,
@@ -221,9 +227,8 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"timezone": "Etc/UTC",
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"allowDictateNextSigner": false,
@@ -237,6 +242,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
@@ -258,6 +264,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"Recipient": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
@@ -295,12 +302,14 @@ The recipient's `readStatus` changes to `OPENED`.
"event": "DOCUMENT_OPENED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING",
"title": "contract.pdf",
"source": "DOCUMENT",
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -328,6 +337,7 @@ The recipient's `signingStatus` changes to `SIGNED` and `signedAt` is populated.
"event": "DOCUMENT_SIGNED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "COMPLETED",
"title": "contract.pdf",
"source": "DOCUMENT",
@@ -335,6 +345,7 @@ The recipient's `signingStatus` changes to `SIGNED` and `signedAt` is populated.
"recipients": [
{
"id": 51,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -361,12 +372,14 @@ Triggered when an individual recipient completes their required action (signing,
"event": "DOCUMENT_RECIPIENT_COMPLETED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING",
"title": "contract.pdf",
"source": "DOCUMENT",
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -395,6 +408,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null,
"userId": 1,
"authOptions": null,
@@ -413,9 +427,8 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"timezone": "Etc/UTC",
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"allowDictateNextSigner": false,
@@ -429,6 +442,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"recipients": [
{
"id": 50,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "reviewer@example.com",
@@ -451,6 +465,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
},
{
"id": 51,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
@@ -475,6 +490,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"Recipient": [
{
"id": 50,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "reviewer@example.com",
@@ -497,6 +513,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
},
{
"id": 51,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
@@ -537,12 +554,14 @@ The recipient's `signingStatus` changes to `REJECTED` and `rejectionReason` cont
"event": "DOCUMENT_REJECTED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING",
"title": "contract.pdf",
"source": "DOCUMENT",
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -561,7 +580,7 @@ The recipient's `signingStatus` changes to `REJECTED` and `rejectionReason` cont
### `document.cancelled`
Triggered when the document owner or a team member deletes a document. Draft and pending documents are hard-deleted, while completed documents are soft-deleted.
Triggered when a pending document is explicitly cancelled with `POST /envelope/cancel`, or when a document owner or team member deletes a document. Deleting a draft or pending document hard-deletes it, while deleting a completed document soft-deletes it.
This event is **not** triggered when a recipient hides a document from their inbox.
@@ -572,6 +591,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"event": "DOCUMENT_CANCELLED",
"payload": {
"id": 7,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null,
"userId": 3,
"authOptions": null,
@@ -591,7 +611,6 @@ This event is **not** triggered when a recipient hides a document from their inb
"subject": "",
"message": "",
"timezone": "Etc/UTC",
"password": null,
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "",
"signingOrder": "PARALLEL",
@@ -606,6 +625,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"recipients": [
{
"id": 7,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 7,
"templateId": null,
"email": "signer@example.com",
@@ -627,6 +647,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"Recipient": [
{
"id": 7,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 7,
"templateId": null,
"email": "signer@example.com",
@@ -651,6 +672,45 @@ This event is **not** triggered when a recipient hides a document from their inb
}
```
### `recipient.expired`
Triggered when a recipient's signing deadline passes on a pending document before they sign or reject it.
**Event name:** `RECIPIENT_EXPIRED`
The recipient's `expiresAt` contains the signing deadline, and `expirationNotifiedAt` is set when the expiration is processed.
```json
{
"event": "RECIPIENT_EXPIRED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING",
"title": "contract.pdf",
"source": "DOCUMENT",
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
"expiresAt": "2024-04-22T11:51:00.000Z",
"expirationNotifiedAt": "2024-04-22T11:52:00.000Z",
"readStatus": "OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:52:00.000Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.reminder.sent`
Triggered when a reminder email is sent to a recipient who has not yet completed their action.
@@ -662,12 +722,14 @@ Triggered when a reminder email is sent to a recipient who has not yet completed
"event": "DOCUMENT_REMINDER_SENT",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING",
"title": "contract.pdf",
"source": "DOCUMENT",
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -686,7 +748,7 @@ Triggered when a reminder email is sent to a recipient who has not yet completed
## Template Events
Template events track changes to reusable document templates. Template payloads use the same structure as document payloads, with `source` set to `TEMPLATE` and `templateId` populated.
Template events track changes to reusable document templates. Template payloads use the same structure as document payloads. For `TEMPLATE_CREATED`, `TEMPLATE_UPDATED`, and `TEMPLATE_DELETED` the template's own legacy numeric ID is in `id` and `templateId` is `null`. Only `TEMPLATE_USED` — whose payload describes the new document envelope created from the template — carries the originating template's legacy ID in `templateId`, with `source` set to `TEMPLATE`.
### `template.created`
@@ -699,9 +761,10 @@ Triggered when a new template is created.
"event": "TEMPLATE_CREATED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "My Template",
"status": "DRAFT",
"templateId": 10,
"templateId": null,
"source": "TEMPLATE",
"recipients": []
},
@@ -721,9 +784,10 @@ Triggered when a template's settings, recipients, or fields are modified.
"event": "TEMPLATE_UPDATED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "My Updated Template",
"status": "DRAFT",
"templateId": 10,
"templateId": null,
"source": "TEMPLATE",
"recipients": []
},
@@ -743,9 +807,10 @@ Triggered when a template is deleted.
"event": "TEMPLATE_DELETED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "Deleted Template",
"status": "DRAFT",
"templateId": 10,
"templateId": null,
"source": "TEMPLATE",
"recipients": []
},
@@ -765,6 +830,7 @@ Triggered when a document is created from a template. This event fires alongside
"event": "TEMPLATE_USED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "Document from Template",
"status": "DRAFT",
"templateId": 10,
@@ -791,7 +857,8 @@ Triggered when a document is created from a template. This event fires alongside
| `DOCUMENT_RECIPIENT_COMPLETED` | Recipient completes their action | Recipient `signingStatus: "SIGNED"`, `signedAt` set |
| `DOCUMENT_COMPLETED` | All recipients complete actions | `status: "COMPLETED"`, `completedAt` set |
| `DOCUMENT_REJECTED` | Recipient rejects document | Recipient `signingStatus: "REJECTED"`, `rejectionReason` set |
| `DOCUMENT_CANCELLED` | Owner or team member deletes document | Document cancelled or deleted |
| `DOCUMENT_CANCELLED` | Pending document explicitly cancelled, or document deleted | `status: "CANCELLED"` after explicit cancellation; deletion may remove or soft-delete the document |
| `RECIPIENT_EXPIRED` | Recipient signing deadline passes | Recipient `expiresAt` passed, `expirationNotifiedAt` set |
| `DOCUMENT_REMINDER_SENT` | Reminder email sent to recipient | No status changes |
### Template Events
@@ -821,7 +888,7 @@ When processing webhook events:
**Process idempotently** — Webhooks may be retried, so handle duplicate events
</Step>
<Step>
**Respond quickly** — Return a 200 status code within 30 seconds
**Respond quickly** — Return a `2xx` status code within 10 seconds
</Step>
</Steps>
@@ -9,7 +9,7 @@ description: Receive real-time notifications for document and template events.
2. When an event occurs, Documenso sends an HTTP POST to your URL
3. Your application processes the event and responds with 200 OK
Documenso supports webhook events for the full document lifecycle (created, sent, opened, signed, completed, rejected, cancelled) as well as template events (created, updated, deleted, used).
Documenso supports webhook events for the full document lifecycle (created, sent, opened, signed, completed, rejected, cancelled), recipient-level events (recipient completed, reminder sent, recipient expired), and template events (created, updated, deleted, used).
---
@@ -42,12 +42,14 @@ Documenso supports webhook events for the full document lifecycle (created, sent
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 123,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "Contract",
"status": "COMPLETED",
"completedAt": "2024-01-15T10:30:00.000Z",
"recipients": [
{
"id": 1,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"signingStatus": "SIGNED"
}
@@ -58,6 +60,8 @@ Documenso supports webhook events for the full document lifecycle (created, sent
}
```
`payload.id` is the legacy numeric v1 ID. Use `payload.envelopeId` as the canonical v2 identifier. Each recipient repeats `envelopeId` as the reliable parent link because the legacy `documentId` and `templateId` fields depend on the parent envelope type, leaving one of them null.
---
## See Also
@@ -148,7 +148,7 @@ func main() {
</Tabs>
<Callout type="warn">
Always respond with a `200 OK` status within 30 seconds. Documenso will retry failed deliveries.
Always respond with a `2xx` status within 10 seconds. Documenso will retry failed deliveries according to the configured background-job provider.
</Callout>
## Configuring Webhooks in Documenso via the Dashboard
@@ -184,7 +184,7 @@ Fill in the following fields:
| Field | Description |
| ----- | ----------- |
| **Webhook URL** | The HTTPS endpoint that will receive webhook events |
| **Webhook URL** | The HTTP or HTTPS endpoint that will receive webhook events |
| **Events** | Select which events should trigger this webhook |
| **Secret** (optional) | A secret key used to sign the payload for verification |
</Step>
@@ -202,12 +202,21 @@ Your webhook endpoint must meet these requirements:
| Requirement | Details |
| ----------- | ------- |
| **Protocol** | HTTPS required (HTTP not allowed in production) |
| **Response** | Must return `2xx` status code within 30 seconds |
| **Protocol** | HTTP and HTTPS are accepted; use HTTPS in production |
| **Response** | Must return a `2xx` status code within 10 seconds |
| **Method** | Must accept HTTP POST requests |
| **Content-Type** | Must accept `application/json` payloads |
| **Availability** | Must be publicly accessible from the internet |
<Callout type="warn">
Documenso performs a best-effort check that rejects webhook URLs which use or resolve to private
or loopback addresses. This is not a complete SSRF mitigation — it does not cover DNS rebinding
and fails open on DNS lookup errors or timeouts — so self-hosted deployments should still enforce
network-level egress rules. Self-hosters that need to deliver to a hostname resolving to a
private address can add that hostname to the comma-separated
`NEXT_PRIVATE_WEBHOOK_SSRF_BYPASS_HOSTS` environment variable.
</Callout>
<Callout type="info">
For local development, use a tunneling service like [ngrok](https://ngrok.com) or [localtunnel](https://localtunnel.me) to expose your local server.
</Callout>
@@ -225,7 +234,8 @@ When creating a webhook, you can subscribe to one or more events:
| `DOCUMENT_RECIPIENT_COMPLETED` | A recipient completes their required action |
| `DOCUMENT_COMPLETED` | All recipients have completed their actions |
| `DOCUMENT_REJECTED` | A recipient rejects the document |
| `DOCUMENT_CANCELLED` | The document owner deletes the document |
| `DOCUMENT_CANCELLED` | A pending document is explicitly cancelled or a document owner deletes it |
| `RECIPIENT_EXPIRED` | A recipient's signing deadline passes before they sign or reject |
| `DOCUMENT_REMINDER_SENT` | A reminder email is sent to a recipient |
| `TEMPLATE_CREATED` | A new template is created |
| `TEMPLATE_UPDATED` | A template is modified |
@@ -294,6 +304,7 @@ Each webhook call shows the following details:
- Timestamp
- Response code
- Request and response bodies
- Response headers
Click any call to see full details including headers and response data.
</Step>
@@ -318,17 +329,17 @@ Documenso will attempt to deliver the same payload again
## Retry Policy
When a webhook delivery fails (non-2xx response or timeout), Documenso automatically retries with exponential backoff:
A delivery fails when the endpoint returns a non-`2xx` response, the 10-second timeout expires, or the request fails. Redirects are not followed, so `3xx` responses also fail. Network and SSRF-blocked requests are recorded with response code `0`.
| Attempt | Delay |
| ------- | ----- |
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
For self-hosted deployments, retries are handled by the background-job provider selected with `NEXT_PRIVATE_JOBS_PROVIDER`:
After 5 failed attempts, the webhook is marked as failed and no further automatic retries occur. You can manually resend failed webhooks from the dashboard.
| Provider | Total attempts | Retry timing |
| -------- | -------------- | ------------ |
| Local (default) | 4 | Back-to-back, with no backoff |
| BullMQ | 3 | Exponential backoff starting at 1 second |
| Inngest | 5 | Inngest platform backoff |
Only the individual delivery (`WebhookCall`) record is marked as failed. Documenso does not automatically disable the webhook or apply a circuit breaker, so future matching events continue to be delivered. After automatic attempts are exhausted, you can manually resend a failed delivery from the dashboard.
<Callout type="warn">
If your endpoint consistently fails, consider reviewing your server logs and ensuring your endpoint meets all [URL requirements](#webhook-url-requirements).
@@ -255,6 +255,7 @@ const validEvents = [
'DOCUMENT_REJECTED',
'DOCUMENT_CANCELLED',
'DOCUMENT_REMINDER_SENT',
'RECIPIENT_EXPIRED',
'TEMPLATE_CREATED',
'TEMPLATE_UPDATED',
'TEMPLATE_DELETED',
@@ -53,8 +53,8 @@ The Enterprise Edition is required when you:
- Document Action Reauthentication (Passkeys and 2FA)
- 21 CFR Part 11 Compliance
- Email Domains (custom sender addresses)
- Embed Authoring
- Embed Authoring White Label
- Embed Editor
- Embed Editor White Label
- Custom signing certificates
- Priority feature requests
@@ -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.
</Accordion>
</Accordions>
@@ -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
</Step>
<Step>
@@ -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
+17 -9
View File
@@ -19,16 +19,19 @@ Use the limitless plans as much as you like. They are meant to offer a lot. Plea
### Do
- Sign as many documents as you need with the individual plan for your single business or organisation
- Use the API and automation tools to automate your signing workflows
- Experiment with plans and integrations while testing what you want to build
- Use team or platform plans to run your workflows, even with significant volume, as long as it aligns with the plans intended purpose.
- Experiment and automate freely within the plan features.
- If volume grows beyond whats sustainable on your plan, well reach out to discuss an upgrade.
- Assume that extreme usage will lead to us contacting you. You can scale up—or scale back. Its about finding the right fit.
### Don't
- Use an individual account API to power a platform or product
- Run a large company signing thousands of documents per day on a small team plan
- Expect enterprise-level support on a fair support plan
- Overthink this policy — if you are a paying customer, we want you to win
- Use an individual account's API to power a platform or product.
- Run a large company signing thousands of documents per day on a small team plan.
- Expect enterprise-level support on a fair support plan (i.e. business edition).
- Use a team plan to power an external platform or commercial product or platform beyond moderate testing.
- Expect a platform plan to support enterprise-level volumes indefinitely without a conversation.
- Dont expect the platform plan to cover enterprise-scale volume or support. If you reach that point, well reach out to guide you to the right fit.
- Dont overthink this if youre building something valuable, we want to see you succeed. If we need to talk, we will.
## Rate Limits
@@ -38,12 +41,17 @@ When a limit is reached, requests return a `429 Too Many Requests` response with
| Action | Limit | Window |
| --- | --- | --- |
| API requests (v1 and v2) | 100 requests | 1 minute |
| API requests (v1 and v2) | 1000 requests | 1 minute |
| File uploads | 20 requests | 1 minute |
| AI features | 3 requests | 1 minute |
Authentication endpoints (login, signup, password reset, etc.) are also rate-limited to protect against abuse.
<Callout type="info">
The API request limit above is the global per-IP ceiling. Individual organisations also have their
own rate limits, which may be configured below this value.
</Callout>
<Callout type="info">
Rate limits may vary by plan. Enterprise plans can include higher or custom limits. Contact
[sales](https://documen.so/sales) for details.
@@ -8,6 +8,7 @@
"privacy",
"terms",
"security",
"verify-email",
"support"
]
}
@@ -0,0 +1,68 @@
---
title: Verifying Emails from Documenso
description: How to confirm that an email is genuinely from Documenso, and what to do if you receive a suspicious message.
---
import { Callout } from 'fumadocs-ui/components/callout';
## Check the Sender Domain
All email sent by Documenso originates from one of the following domains. If you receive an email claiming to be from Documenso and the sender address does not end in one of these domains, treat it as suspicious.
| Domain | Used for |
| ------------------------ | -------------------------------------------------------------- |
| `app.documenso.com` | Transactional email |
| `documensomail.com` | Transactional email |
| `documensoemail.com` | Transactional email |
| Custom domain | [Enterprise organisations](/docs/users/organisations/email-domains) using a custom email domain |
Typical sender addresses include:
- `noreply@app.documenso.com`
- `noreply@free.documensomail.com`
- `noreply@send.documensoemail.com`
<Callout type="warn">
A misspelling such as `documenso-email.com`, `documensoemaiI.com` (capital i instead of l), or any other variation is not a Documenso domain.
</Callout>
## Types of Email Documenso Sends
Documenso sends email only for the following purposes:
- **Account verification** — confirming your email address when you sign up or change it
- **Password reset** — a link to reset your password that you requested
- **Document invitations** — notifying you that a document has been shared with you to sign, approve, or view
- **Signing reminders** — follow-up reminders for pending document actions
- **Completed document notifications** — confirmation that all parties have signed a document
- **Team invitations** — inviting you to join an organisation or team
## What Documenso Will Never Do
- Ask for your password via email
- Send you an attachment and ask you to open it to verify your identity
- Ask you to confirm payment details or billing information over email
- Send unsolicited marketing emails if you have not opted in
## How to Tell If an Email Is Legitimate
1. **Check the sender address** — the domain must be `documenso.com` or `documensomail.com`
2. **Look at the link destination** — hover over any link before clicking; it should point to `app.documenso.com`
3. **Watch for urgency or threats** — legitimate Documenso emails do not threaten account suspension to pressure you into clicking a link immediately
4. **Verify the action yourself** — if in doubt, log in to [app.documenso.com](https://app.documenso.com) directly (not via the email link) and check whether the document or notification exists there
## Report a Suspicious Email
If you receive an email that appears to impersonate Documenso:
1. Do not click any links or download any attachments
2. Forward the email as an attachment to **support@documenso.com**
3. Delete the email from your inbox
You can also report phishing emails directly to your email provider using their built-in reporting tools.
## Related
- [Security Policy](/docs/policies/security) — Documenso's security practices and vulnerability disclosure process
- [Create an Account](/docs/users/getting-started/create-account) — What to expect during sign-up
- [Security Settings](/docs/users/settings/security) — Enable two-factor authentication and manage sessions
@@ -1,5 +1,5 @@
---
title: AI Recipient & Field Detection (Self-hosting)
title: AI Recipient & Field Detection
description: Configure Google Vertex AI so Documenso can detect recipients and fields automatically.
---
@@ -0,0 +1,408 @@
---
title: Document Conversion
description: Enable DOCX uploads on a self-hosted Documenso instance by running a Gotenberg sidecar that converts Word documents to PDF.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Overview
Documenso can accept `.docx` uploads in addition to PDFs. When a user uploads a Word document, the Documenso server sends it to a [Gotenberg](https://gotenberg.dev) service which uses LibreOffice to convert it to PDF. The converted PDF is what gets stored, signed, and downloaded. The original DOCX is discarded.
This feature is **opt-in for self-hosted instances**. When the conversion service is not configured, DOCX uploads are rejected in the UI and only PDFs are accepted.
| Property | Value |
| ----------------------- | -------------------------------------------------------------------- |
| Conversion engine | [Gotenberg](https://gotenberg.dev) + LibreOffice |
| Input format | `.docx` (Office Open XML Word documents) |
| Output format | PDF |
| Network requirement | Documenso must reach the Gotenberg HTTP API |
| Default request timeout | 30 seconds per file |
| Failure handling | An internal circuit breaker opens for 30 seconds after a failure |
<Callout type="info">
Only `.docx` is accepted. Legacy `.doc`, `.odt`, `.rtf`, and other LibreOffice-supported formats
are rejected at the upload step even when Gotenberg is configured.
</Callout>
---
## Requirements
- A running Gotenberg 8 instance with the LibreOffice module (`gotenberg/gotenberg:8-libreoffice` or newer).
- Network reachability from the Documenso container to the Gotenberg HTTP API.
- A version of Documenso that includes the document conversion feature.
## Build the Gotenberg Image
The upstream `gotenberg/gotenberg:8-libreoffice` image works out of the box, but it ships only **metric-compatible font substitutes** (Carlito for Calibri, Liberation for Arial/Times/Courier). Layout widths are preserved but documents will look noticeably different from Word.
For better fidelity, especially for non-Latin scripts, build a derived image that adds Microsoft Core Fonts and additional language fonts. The Documenso repository ships a reference Dockerfile at [`docker/development/Dockerfile.gotenberg`](https://github.com/documenso/documenso/blob/main/docker/development/Dockerfile.gotenberg) that you can use as a starting point:
```dockerfile
FROM gotenberg/gotenberg:8-libreoffice
USER root
RUN echo "deb http://deb.debian.org/debian trixie contrib non-free" \
> /etc/apt/sources.list.d/contrib.list \
&& echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" \
| debconf-set-selections \
&& apt-get update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
ca-certificates \
ttf-mscorefonts-installer \
fonts-symbola \
fonts-noto-extra \
fonts-hosny-amiri \
fonts-thai-tlwg \
fonts-sil-padauk \
fonts-sarai \
fonts-samyak-taml \
culmus \
libfribidi0 \
libharfbuzz0b \
&& fc-cache -f \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
USER gotenberg
```
<Callout type="warn">
`ttf-mscorefonts-installer` accepts the Microsoft Core Fonts EULA on your behalf via debconf. By
installing this image you are agreeing to those licence terms. Review them before publishing the
image.
</Callout>
Build and publish the image to a registry you control:
```bash
docker build -t registry.example.com/documenso/gotenberg:8 \
-f Dockerfile.gotenberg .
docker push registry.example.com/documenso/gotenberg:8
```
If you do not need extra fonts, skip the build step entirely and reference `gotenberg/gotenberg:8-libreoffice` directly in the next section.
## Deploy the Service
The Gotenberg service should run **alongside** your Documenso container, not exposed to the public internet. The conversion service has no built-in authorisation beyond HTTP Basic auth, so it should sit on a private network or behind your existing reverse proxy.
<Tabs items={['Docker Compose', 'Kubernetes', 'External Instance']}>
<Tab value="Docker Compose">
Add a `gotenberg` service to the `compose.yml` you use for Documenso:
```yaml
services:
gotenberg:
image: registry.example.com/documenso/gotenberg:8
# Or use upstream directly:
# image: gotenberg/gotenberg:8-libreoffice
restart: unless-stopped
environment:
GOTENBERG_API_BASIC_AUTH_USERNAME: ${GOTENBERG_USERNAME}
GOTENBERG_API_BASIC_AUTH_PASSWORD: ${GOTENBERG_PASSWORD}
command:
- gotenberg
- --api-enable-basic-auth
- --libreoffice-deny-private-ips
- --api-timeout=500s
- --libreoffice-auto-start
- --libreoffice-start-timeout=300s
- --pdfengines-disable-routes
- --webhook-disable
healthcheck:
test: ['CMD', 'curl', '-fsS', 'http://localhost:3000/health']
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
documenso:
# existing config
environment:
NEXT_PRIVATE_DOCUMENT_CONVERSION_URL: http://gotenberg:3000
NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME: ${GOTENBERG_USERNAME}
NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD: ${GOTENBERG_PASSWORD}
depends_on:
gotenberg:
condition: service_healthy
```
Do **not** publish Gotenberg's port (`3000`) to the host. Documenso reaches it over the internal Docker network using the service name (`http://gotenberg:3000`).
</Tab>
<Tab value="Kubernetes">
Create a Deployment, Service, and Secret. Example manifests:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: gotenberg-auth
namespace: documenso
stringData:
username: documenso
password: replace-me-with-a-strong-password
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gotenberg
namespace: documenso
spec:
replicas: 1
selector:
matchLabels: { app: gotenberg }
template:
metadata:
labels: { app: gotenberg }
spec:
containers:
- name: gotenberg
image: registry.example.com/documenso/gotenberg:8
args:
- gotenberg
- --api-enable-basic-auth
- --libreoffice-deny-private-ips
- --api-timeout=500s
- --libreoffice-auto-start
- --libreoffice-start-timeout=300s
- --pdfengines-disable-routes
- --webhook-disable
env:
- name: GOTENBERG_API_BASIC_AUTH_USERNAME
valueFrom: { secretKeyRef: { name: gotenberg-auth, key: username } }
- name: GOTENBERG_API_BASIC_AUTH_PASSWORD
valueFrom: { secretKeyRef: { name: gotenberg-auth, key: password } }
ports:
- containerPort: 3000
readinessProbe:
httpGet: { path: /health, port: 3000 }
livenessProbe:
httpGet: { path: /health, port: 3000 }
initialDelaySeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: gotenberg
namespace: documenso
spec:
selector: { app: gotenberg }
ports:
- port: 3000
targetPort: 3000
```
Then reference the in-cluster URL from Documenso's environment:
```
NEXT_PRIVATE_DOCUMENT_CONVERSION_URL=http://gotenberg.documenso.svc.cluster.local:3000
NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME=documenso
NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD=replace-me-with-a-strong-password
```
</Tab>
<Tab value="External Instance">
Documenso does not have to colocate with Gotenberg. You can point it at any reachable Gotenberg deployment: a managed instance, a shared internal service, or a Gotenberg-compatible API.
```bash
NEXT_PRIVATE_DOCUMENT_CONVERSION_URL=https://gotenberg.internal.example.com
NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME=documenso
NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD=replace-me-with-a-strong-password
```
The remote instance must:
- Expose the LibreOffice route `/forms/libreoffice/convert`.
- Be reachable from the Documenso container with low enough latency that the 30 second per-request timeout is comfortable.
- Be on a private network or require authentication. Uploaded documents are sent to it as multipart form data and may contain sensitive content.
</Tab>
</Tabs>
## Recommended Gotenberg Flags
The flags in the examples above are not arbitrary. Each one matters for a production deployment.
| Flag | Why it matters |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--api-enable-basic-auth` | Requires HTTP Basic credentials on every API route. Without this, anyone with network access to the container can convert arbitrary documents. |
| `--libreoffice-deny-private-ips` | Rejects any outbound fetch LibreOffice tries to make to private, loopback, link-local, or cloud-metadata addresses while processing a document. Mitigates SSRF via malicious `.docx` files that embed `TargetMode="External"` references. Requires Gotenberg 8.32.0. |
| `--api-timeout=500s` | Server-side request ceiling. Documenso aborts at 30 s by default, so this is a safety net for very large documents. |
| `--libreoffice-auto-start` | Starts LibreOffice at container boot so the first request is not slow. |
| `--libreoffice-start-timeout=300s`| Allows LibreOffice up to 5 minutes to come up under load. |
| `--pdfengines-disable-routes` | Disables the PDF engines routes Documenso does not use. Shrinks the attack surface. |
| `--webhook-disable` | Disables webhook callbacks. Documenso uses synchronous requests only. |
## Configure Documenso
Set the following environment variables on the Documenso container and restart it.
### Required
| Variable | Description |
| ------------------------------------- | ---------------------------------------------------------------------- |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL`| Base URL of the Gotenberg service (e.g., `http://gotenberg:3000`). Leave unset to disable the feature. |
### Optional
| Variable | Default | Description |
| ------------------------------------------- | ------- | -------------------------------------------------------------------------------------------- |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME` | | HTTP Basic auth username. Set when Gotenberg runs with `--api-enable-basic-auth`. |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD` | | HTTP Basic auth password. Set together with the username. |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_TIMEOUT_MS`| `30000` | Per-request timeout in milliseconds. Increase for very large documents. |
<Callout type="info">
When `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL` is set, the public flag
`NEXT_PUBLIC_DOCUMENT_CONVERSION_ENABLED` is derived automatically on server start. You do not
need to set it yourself, and setting it manually has no effect.
</Callout>
### Example `.env` Snippet
```bash
# Document conversion (DOCX -> PDF)
NEXT_PRIVATE_DOCUMENT_CONVERSION_URL=http://gotenberg:3000
NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME=documenso
NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD=replace-me-with-a-strong-password
# NEXT_PRIVATE_DOCUMENT_CONVERSION_TIMEOUT_MS=60000
```
## Verify the Setup
{/* prettier-ignore */}
<Steps>
<Step>
### Restart the Documenso container
Restart so the new environment variables are picked up.
</Step>
<Step>
### Confirm Gotenberg is healthy
From a shell inside the Documenso container or another container on the same network:
```bash
curl -fsS http://gotenberg:3000/health
```
The endpoint is exempt from basic auth and should return `200 OK`.
</Step>
<Step>
### Upload a test DOCX
In the Documenso web UI, open **Documents** and try uploading a small `.docx` file. The upload dropzone should accept it, and after a few seconds the editor should open with the converted PDF.
</Step>
<Step>
### Check the server logs
Successful conversions log a `document_conversion_attempt` event with `result: "success"`, the duration, and the file size. Failures log the same event with `result: "error"` and an error code (`CONVERSION_SERVICE_UNAVAILABLE`, `CONVERSION_FAILED`, or `UNSUPPORTED_FILE_TYPE`).
</Step>
</Steps>
## Security Considerations
- **Treat the conversion service as untrusted internal infrastructure.** Documents pass through Gotenberg in plain form. Run it on a private network and require HTTP Basic auth.
- **Run with `--libreoffice-deny-private-ips`.** Without this flag, a malicious `.docx` can trigger LibreOffice to fetch URLs from your internal network (SSRF).
- **Disable unused routes.** `--pdfengines-disable-routes` and `--webhook-disable` reduce attack surface. Documenso only uses the LibreOffice convert route.
- **Do not expose Gotenberg to the public internet.** Even with basic auth, this is a document-processing service with a non-trivial CPU and memory footprint; exposing it invites abuse.
- **Rotate credentials.** Rotating the basic auth secret is a config change in both Gotenberg and Documenso, followed by a restart of each.
## Resource Sizing
Conversion is CPU- and memory-bound on LibreOffice. As a starting point:
| Workload | Suggested resources |
| ----------------------------- | ------------------------------------ |
| Light (a few DOCX per minute) | 1 vCPU, 1 GB RAM |
| Moderate (sustained uploads) | 2 vCPU, 2 GB RAM |
| Heavy / multi-tenant | Horizontally scale Gotenberg replicas behind a load balancer |
Gotenberg is stateless. Each container handles one or more concurrent requests independently. Scale horizontally rather than vertically once a single replica is saturated.
## Troubleshooting
<Accordions type="multiple">
<Accordion title="DOCX uploads are rejected with 'Only PDF and DOCX files are allowed'">
The Documenso server does not see `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL`. Check the value is set
on the running container (`docker exec documenso printenv | grep DOCUMENT_CONVERSION`) and
restart after changing it.
</Accordion>
<Accordion title="Uploads fail with 'Document conversion service is currently unavailable'">
Documenso could not reach Gotenberg. Verify:
- The URL in `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL` is resolvable from the Documenso container
(use the Docker service name or in-cluster DNS, not `localhost`).
- Gotenberg's `/health` endpoint returns `200`.
- Basic auth credentials match between the two services.
After repeated failures, an internal circuit breaker opens for 30 seconds. Subsequent uploads
will fail fast during that window; this is intentional and self-recovers.
</Accordion>
<Accordion title="Uploads fail with 'Failed to convert document to PDF'">
Gotenberg was reachable but returned a non-2xx response. Check the Gotenberg container logs:
```bash
docker compose logs -f gotenberg
```
Common causes: corrupted `.docx` file, exotic embedded objects LibreOffice cannot render, or a
file that genuinely exceeded the conversion timeout. Increase
`NEXT_PRIVATE_DOCUMENT_CONVERSION_TIMEOUT_MS` for very large documents.
</Accordion>
<Accordion title="Converted PDFs look different from the Word document">
LibreOffice is not byte-identical to Microsoft Word. Layout, font metrics, and complex elements
(Charts, SmartArt, ActiveX controls) may differ. To improve fidelity:
- Use the custom Dockerfile in this guide to install Microsoft Core Fonts and additional
language fonts.
- Make sure any custom fonts referenced by your documents are installed in the Gotenberg image.
- For pixel-perfect output, ask users to export to PDF from Word before uploading.
</Accordion>
<Accordion title="Form controls in the DOCX appear blank or missing">
Documenso disables Gotenberg's `exportFormFields` flag during conversion. Word content controls
(`<w:sdt>`) become static graphics in the output PDF, which prevents Documenso's later
flattening step from making them invisible. This is intentional. Use Documenso fields
(signature, text, date, etc.) for anything that needs to be filled in by signers.
</Accordion>
<Accordion title="Conversion is slow on the first request">
LibreOffice starts lazily by default. Pass `--libreoffice-auto-start` to Gotenberg so it warms
up at container boot. Allow up to a minute on first start before considering the service
unhealthy.
</Accordion>
<Accordion title="The circuit breaker keeps opening">
Repeated failures open an in-process circuit breaker for 30 seconds. If you see this in
production, the underlying problem is the Gotenberg service. Check its logs, resource usage,
and connectivity. The breaker is per-process and resets on restart.
</Accordion>
</Accordions>
---
## See Also
- [Upload Documents (User Guide)](/docs/users/documents/upload) - End-user view of DOCX uploads
- [Environment Variables](/docs/self-hosting/configuration/environment) - Full configuration reference
- [Docker Compose Deployment](/docs/self-hosting/deployment/docker-compose) - Compose-based deployment patterns
- [Gotenberg Documentation](https://gotenberg.dev/docs/getting-started/introduction) - Upstream Gotenberg docs
@@ -1,6 +1,6 @@
---
title: Advanced
description: Optional configuration for OAuth providers, AI features, and other advanced settings.
description: Optional configuration for OAuth providers, AI features, document conversion, and other advanced settings.
---
<Cards>
@@ -14,4 +14,9 @@ description: Optional configuration for OAuth providers, AI features, and other
description="Enable AI-powered recipient and field detection."
href="/docs/self-hosting/configuration/advanced/ai-features"
/>
<Card
title="Document Conversion"
description="Accept DOCX uploads by running a Gotenberg sidecar that converts Word documents to PDF."
href="/docs/self-hosting/configuration/advanced/document-conversion"
/>
</Cards>
@@ -1,4 +1,4 @@
{
"title": "Advanced",
"pages": ["oauth-providers", "ai-features"]
"pages": ["oauth-providers", "document-conversion", "ai-features"]
}
@@ -278,7 +278,9 @@ Test your email configuration by creating an account or resetting a password. Th
### Using a Test SMTP Server
For development or testing, use a local SMTP server like [Mailhog](https://github.com/mailhog/MailHog) or [Mailpit](https://github.com/axllent/mailpit):
For development or testing, use a local SMTP server like [Inbucket](https://www.inbucket.org/), [Mailpit](https://github.com/axllent/mailpit), or [Mailhog](https://github.com/mailhog/MailHog). The default development setup (`docker/development/compose.yml`) already runs Inbucket, with its web UI on port 9000 and SMTP on port 2500.
To run one standalone instead:
```bash
# Using Docker
@@ -86,6 +86,21 @@ Callback URL: `https://<your-domain>/api/auth/callback/microsoft`
| `NEXT_PRIVATE_OIDC_SKIP_VERIFY` | `false` | Skip email verification for OIDC accounts |
| `NEXT_PRIVATE_OIDC_PROMPT` | `login` | OIDC prompt parameter. Set to empty string to omit |
### Webhooks
| Variable | Default | Description |
| --------------------------------------- | ------- | ------------------------------------------------------------------------ |
| `NEXT_PRIVATE_WEBHOOK_SSRF_BYPASS_HOSTS` | - | Comma-separated hostnames or IPs allowed to resolve to private addresses |
Before delivering a webhook, Documenso checks whether the target resolves to a
private or loopback address and blocks it if so. This check is best-effort and
fails open. Use `NEXT_PRIVATE_WEBHOOK_SSRF_BYPASS_HOSTS` to allow specific
internal hosts, for example when delivering to a service on your own network:
```bash
NEXT_PRIVATE_WEBHOOK_SSRF_BYPASS_HOSTS="hooks.internal.example,10.0.0.5"
```
---
## Email Configuration
@@ -186,9 +201,9 @@ Documenso requires a certificate to digitally sign documents.
### Transport Selection
| Variable | Description | Default |
| -------------------------------- | ---------------------------------------- | ------- |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | Signing backend: `local` or `gcloud-hsm` | `local` |
| Variable | Description | Default |
| -------------------------------- | ------------------------------------------------- | ------- |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | Signing backend: `local`, `gcloud-hsm`, or `csc` | `local` |
### Local Signing
@@ -210,11 +225,36 @@ Documenso requires a certificate to digitally sign documents.
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_CERT_CHAIN_CONTENTS` | Base64-encoded certificate chain |
| `NEXT_PRIVATE_SIGNING_GCLOUD_HSM_SECRET_MANAGER_CERT_PATH` | Google Secret Manager path for certificate retrieval |
### Cloud Signature Consortium (CSC)
Routes signing through a third-party Trust Service Provider for Advanced and Qualified Electronic Signatures (AES/QES). Instance-wide; set `NEXT_PRIVATE_SIGNING_TRANSPORT=csc` to enable. See [CSC (AES / QES)](/docs/self-hosting/configuration/signing-certificate/csc-qes) for the full setup walkthrough.
CSC mode requires an active [Enterprise Edition](/docs/policies/enterprise-edition) license. Without a valid license, the instance will refuse to start in `csc` mode.
| Variable | Description | Default |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------- |
| `NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL` | Base URL of the CSC provider's API | |
| `NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_ID` | OAuth client ID registered with the CSC provider | |
| `NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_SECRET` | OAuth client secret registered with the CSC provider | |
| `NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL` | Default legal tier for new envelopes when the caller doesn't specify one. `AES` or `QES`. Explicit requests pass through. | `AES` |
The OAuth callback URL registered with the CSC provider is fixed at `${NEXT_PUBLIC_WEBAPP_URL}/api/csc/oauth/callback` — register this exact URL with the TSP.
#### Derived Public Variables
The following client-visible variable is **derived automatically** from the private transport at server startup. Do not set it manually — any value set in the environment is overwritten on boot.
| Variable | Derived from | Value |
| ------------------------------------- | -------------------------------------------------- | ------------------------------------------------- |
| `NEXT_PUBLIC_SIGNING_TRANSPORT_IS_CSC` | `NEXT_PRIVATE_SIGNING_TRANSPORT === 'csc'` | `'true'` when CSC mode is active, else `'false'` |
The authoring UI uses this flag to gate features that AES/QES envelopes cannot support (parallel signing, assistant role, dictate next signer). Deriving it from the private transport prevents the client-side flag from drifting from the real server-side configuration.
### Signature Options
| Variable | Description | Default |
| ------------------------------------------- | ----------------------------------------------------------- | ---------- |
| `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` | Comma-separated timestamp authority URLs for LTV signatures | |
| `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` | Comma-separated timestamp authority URLs for LTV signatures. Optional for `local` / `gcloud-hsm` (signatures omit the timestamp when unset). **Required** when `NEXT_PRIVATE_SIGNING_TRANSPORT=csc` — the instance refuses to start without it. See [CSC (AES / QES)](/docs/self-hosting/configuration/signing-certificate/csc-qes#timestamp-authority-resolution). | |
| `NEXT_PUBLIC_SIGNING_CONTACT_INFO` | Contact info embedded in PDF signatures | Webapp URL |
| `NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER` | Use `adbe.pkcs7.detached` instead of `ETSI.CAdES.detached` | `false` |
@@ -224,32 +264,89 @@ For detailed certificate setup, see [Signing Certificate](/docs/self-hosting/con
## Feature Flags
| Variable | Description | Default |
| ------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public user registration entirely | `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of email domains allowed to sign up (e.g., `example.com,acme.org`) | |
| Variable | Description | Default |
| -------------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch. Disable all signup methods application-wide | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Disable email/password signup only. SSO signup is unaffected | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Block new accounts via Google. Existing Google-linked users can still sign in | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Block new accounts via Microsoft. Existing linked users can still sign in | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC, including the organisation portal | `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of email domains allowed to sign up (e.g., `example.com,acme.org`) | |
| `NEXT_PUBLIC_DISABLE_SIGNIN` | Master switch. Disable all signin methods application-wide | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN` | Disable email/password signin. Also closes `/forgot-password` and `/reset-password` | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN` | Hide the Google signin button | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN` | Hide the Microsoft signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNIN` | Hide the OIDC signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT` | Disable the automatic `/signin` redirect when OIDC is the only enabled transport | `false` |
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog API key for analytics and feature flags | |
| `NEXT_PUBLIC_FEATURE_BILLING_ENABLED` | Enable billing features | `false` |
### Signup Restrictions
You can control who is allowed to create accounts on your instance using two environment variables:
You can control who is allowed to create accounts on your instance with the following environment variables:
- **`NEXT_PUBLIC_DISABLE_SIGNUP`**: Set to `true` to block all new signups. Existing users can still sign in. This applies to both email/password and OAuth signups.
- **`NEXT_PUBLIC_DISABLE_SIGNUP`** (master switch): Set to `true` to block all new signups across every method (email/password, Google, Microsoft, OIDC). When set, this also blocks new-account creation through the organisation OIDC authentication portal.
- **`NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP`**: Set to `true` to disable email/password signup only. SSO signup is still allowed.
- **`NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP`**, **`NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`**, **`NEXT_PUBLIC_DISABLE_OIDC_SIGNUP`**: Set to `true` to block brand-new account creation through the matching SSO provider. Existing users with the provider already linked can still sign in, and existing users can still link the provider to their account. `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` also blocks new-account creation through the organisation authentication portal.
- **`NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS`**: Restrict signups to specific email domains. When set, only users whose email address matches one of the listed domains can create an account. Leave empty to allow all domains.
Both restrictions apply to email/password registration and OAuth (Google, Microsoft, OIDC). If a user attempts to sign up via OAuth with a disallowed domain, they are redirected to the sign-in page with an error.
Sign-in for existing users is never affected, only the creation of brand-new accounts.
When both variables are set, `NEXT_PUBLIC_DISABLE_SIGNUP` takes precedence. Signups are blocked regardless of the domain list.
Both the master switch and the domain allowlist apply to email/password registration and OAuth (Google, Microsoft, OIDC). If a user attempts to sign up via OAuth with a disallowed domain, they are redirected to the sign-in page with an error.
When both the master switch and the domain allowlist are set, the master switch takes precedence. Signups are blocked regardless of the domain list.
```bash
# Allow signups only from specific domains
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS="example.com,acme.org"
# Allow OIDC signup only; block email/password, Google, Microsoft
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP="true"
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP="true"
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP="true"
# Or disable signups entirely
NEXT_PUBLIC_DISABLE_SIGNUP="true"
```
### Sign-in Restrictions
You can control which methods are available for users to sign in with the following environment variables:
- **`NEXT_PUBLIC_DISABLE_SIGNIN`** (master switch): Set to `true` to block all signin methods (email/password, Google, Microsoft, OIDC). Hides every signin entry point on `/signin` and rejects email/password signin server-side with a `SIGNIN_DISABLED` error.
- **`NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN`**: Set to `true` to disable email/password signin only. The email/password form is hidden, the `/forgot-password` and `/reset-password` pages redirect to `/signin`, and the corresponding server endpoints reject requests. SSO signin is unaffected.
- **`NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN`**, **`NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN`**, **`NEXT_PUBLIC_DISABLE_OIDC_SIGNIN`**: Set to `true` to hide the matching SSO button on the signin page. Useful when an SSO provider is kept configured for account linking but not advertised as a signin entry point.
These flags are opt-in: when none are set, signin behaviour is unchanged from a stock Documenso instance.
```bash
# Allow only OIDC signin (e.g. enterprise SSO-only)
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN="true"
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN="true"
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN="true"
# Or disable signin entirely
NEXT_PUBLIC_DISABLE_SIGNIN="true"
```
### OIDC Auto-redirect
When OIDC is the only enabled signin transport on your instance, `/signin` automatically redirects users straight to the OIDC provider instead of showing the signin form. The page renders a spinner while the redirect happens. No extra configuration is required — disabling every other signin method is enough to trigger it.
- **`NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT`**: Set to `true` to opt out of the automatic redirect and keep rendering the signin page even when OIDC is the only enabled transport.
The redirect only triggers when OIDC is configured and email/password, Google, and Microsoft signin are all disabled. If any other transport remains enabled, the signin form is shown as normal.
```bash
# OIDC-only signin: disabling all other methods auto-redirects to the provider
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN="true"
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN="true"
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN="true"
# Opt out of the auto-redirect while still OIDC-only
# NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT="true"
```
---
## AI Features
@@ -266,6 +363,23 @@ AI features must also be enabled in organisation/team settings after configurati
---
## Document Conversion
Documenso can accept `.docx` uploads by sending them to a [Gotenberg](https://gotenberg.dev) service that converts them to PDF. When `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL` is unset, DOCX uploads are rejected and only PDFs are accepted.
| Variable | Description | Default |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------- |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL` | Base URL of the Gotenberg service (e.g., `http://gotenberg:3000`). Unset disables the feature. | |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME` | HTTP Basic auth username. Required when Gotenberg runs with `--api-enable-basic-auth`. | |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD` | HTTP Basic auth password. Set together with the username. | |
| `NEXT_PRIVATE_DOCUMENT_CONVERSION_TIMEOUT_MS` | Per-request timeout in milliseconds. Increase for very large documents. | `30000` |
The public flag `NEXT_PUBLIC_DOCUMENT_CONVERSION_ENABLED` is derived automatically from `NEXT_PRIVATE_DOCUMENT_CONVERSION_URL` on server start. Do not set it manually.
For setup, image-build instructions, and security recommendations, see [Document Conversion](/docs/self-hosting/configuration/advanced/document-conversion).
---
## Background Jobs
Documenso supports multiple background job providers for processing emails, documents, webhooks, and scheduled tasks.
@@ -329,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 authoring, 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 |
@@ -371,7 +485,21 @@ NEXT_PRIVATE_SIGNING_PASSPHRASE="your-certificate-password"
# Signup restrictions (optional)
# NEXT_PUBLIC_DISABLE_SIGNUP="true"
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP="true"
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP="true"
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP="true"
# NEXT_PUBLIC_DISABLE_OIDC_SIGNUP="true"
# NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS="example.com,acme.org"
# Sign-in restrictions (optional)
# NEXT_PUBLIC_DISABLE_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_OIDC_SIGNIN="true"
# Opt out of the automatic OIDC redirect when OIDC is the only enabled transport (optional)
# NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT="true"
```
---
@@ -382,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
@@ -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"
/>
<Card
title="Organisation Limits"
description="Set per-organisation document, email, and API limits via the admin panel."
href="/docs/self-hosting/configuration/organisation-limits"
/>
</Cards>
## Required Configuration
@@ -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.
<Callout type="info">
The license key applies to your **whole instance**, not an individual user account. There's one
key per deployment.
</Callout>
## 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.
<Tabs items={['Docker Compose', 'docker run', '.env']}>
<Tab value="Docker Compose">
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
```
</Tab>
<Tab value="docker run">
```bash
docker run -d \
--name documenso \
-e NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY=your-license-key-here \
documenso/documenso:latest
```
</Tab>
<Tab value=".env">
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
```
</Tab>
</Tabs>
## 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
<Accordions type="multiple">
<Accordion title="Enterprise features are still unavailable after applying the key">
- 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.
</Accordion>
<Accordion title="A specific feature still isn't working">
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.
</Accordion>
</Accordions>
## 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
@@ -2,12 +2,14 @@
"title": "Configuration",
"pages": [
"environment",
"license",
"database",
"email",
"storage",
"background-jobs",
"signing-certificate",
"telemetry",
"organisation-limits",
"advanced"
]
}
@@ -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 (1000/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**.
<Callout type="warn">
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.
</Callout>
## 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.
<Callout type="warn">
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.
</Callout>
### 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 **1000 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
@@ -0,0 +1,213 @@
---
title: CSC (AES / QES)
description: Configure Cloud Signature Consortium signing for Advanced and Qualified Electronic Signatures via a third-party Trust Service Provider.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
The `csc` signing transport routes signatures through a third-party Trust Service Provider (TSP) using the [Cloud Signature Consortium API v1.0.4.0](https://cloudsignatureconsortium.org/). Each recipient authenticates directly with the TSP (Strong Customer Authentication) and the TSP returns a per-recipient signature bound to the document hash. Documenso assembles the resulting PAdES signature inside the PDF.
This transport enables **Advanced Electronic Signatures (AES)** and **Qualified Electronic Signatures (QES)** under eIDAS. See [Signature Levels](/docs/compliance/signature-levels) for the legal framework.
<Callout type="warn">
CSC mode is **instance-wide**: one CSC provider per Documenso install. All envelopes created
while the instance runs in `csc` mode use AES or QES. Switching `NEXT_PRIVATE_SIGNING_TRANSPORT`
is a one-way operational migration — see [Switching Transports](#switching-transports).
</Callout>
<Callout type="warn">
CSC mode requires an active [Enterprise Edition](/docs/policies/enterprise-edition) license. The
instance refuses to start in `csc` mode without it.
</Callout>
## Prerequisites
{/* prettier-ignore */}
<Steps>
<Step>
### A TSP account
Establish a relationship with a CSC-compatible Trust Service Provider. The TSP issues qualified or advanced certificates to your signers, holds the private keys in its HSM, and exposes a CSC v1.0.4.0-compliant API.
</Step>
<Step>
### OAuth client credentials
Register Documenso as an OAuth client with the TSP. You will receive a client ID and client secret, and must supply Documenso's callback URL when registering:
```
${NEXT_PUBLIC_WEBAPP_URL}/api/csc/oauth/callback
```
The callback URL is fixed — Documenso derives it from `NEXT_PUBLIC_WEBAPP_URL` and the route mount path. There is no env var to override it; ensuring the registered URL matches your instance's webapp URL exactly is the operator's responsibility.
</Step>
<Step>
### Enterprise Edition license
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.
</Step>
<Step>
### S3 storage (strongly recommended)
CSC produces multiple `DocumentData` rows per envelope item (one per recipient signature, plus the materialised and source rows). Database-backed storage base64-inflates each row by ~33% and is impractical at meaningful PDF sizes. Configure [S3 storage](/docs/self-hosting/configuration/storage) before enabling CSC.
</Step>
</Steps>
## Environment Variables
| Variable | Description | Default |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | Set to `csc` | |
| `NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL` | Base URL of the CSC provider's API | |
| `NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_ID` | OAuth client ID registered with the CSC provider | |
| `NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_SECRET` | OAuth client secret registered with the CSC provider | |
| `NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL` | Default legal tier for new envelopes when the caller does not specify one. `AES` or `QES`. Explicit requests always pass through. | `AES` |
| `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` | **Required.** Comma-separated RFC 3161 TSA URLs. Always used for B-LTA archival timestamps at seal time, and also serves as the B-T sign-time fallback when the TSP does not expose `signatures/timestamp`. The instance refuses to start in CSC mode without it. See [Timestamp Authority Resolution](#timestamp-authority-resolution). | |
<Callout type="info">
`NEXT_PUBLIC_SIGNING_TRANSPORT_IS_CSC` is set automatically from
`NEXT_PRIVATE_SIGNING_TRANSPORT` at server startup. Do not set it manually — see
[Environment Variables](/docs/self-hosting/configuration/environment#derived-public-variables).
</Callout>
## Configuration Example
```bash
NEXT_PRIVATE_SIGNING_TRANSPORT=csc
NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL=https://api.example-tsp.com/csc/v1
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_ID=documenso-prod
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_SECRET=...
NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL=QES
NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY=http://timestamp.example.com
```
Register `${NEXT_PUBLIC_WEBAPP_URL}/api/csc/oauth/callback` (e.g. `https://sign.example.com/api/csc/oauth/callback`) as the OAuth callback URL with the TSP.
## Default Signature Level
`NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL` selects the legal tier applied to envelopes that do not specify one explicitly. It is a default, not a capability gate: callers may still create AES or QES envelopes explicitly regardless of this setting.
| Configured value | Caller passes nothing | Caller passes `AES` | Caller passes `QES` |
| ---------------- | --------------------- | ------------------- | ------------------- |
| `AES` (default) | Envelope is `AES` | Envelope is `AES` | Envelope is `QES` |
| `QES` | Envelope is `QES` | Envelope is `AES` | Envelope is `QES` |
Any value other than `AES` or `QES` causes the instance to refuse to start. This prevents silent qualified-to-advanced downgrades from a typo.
## Timestamp Authority Resolution
AES/QES envelopes use TSA-attested timestamps in two distinct phases. Resolution differs per phase.
### Sign time — PAdES B-T per recipient
Each recipient's CMS embeds a signature timestamp (CMS unsigned attribute) so proven time is bound to the recipient's signature itself. Resolution order:
1. If the TSP advertises `signatures/timestamp` in its `info` response (CSC §11.10), the TSP endpoint is used. The call is authorised with **this recipient's** service-scope bearer token — the same one authorising the `signatures/signHash` call alongside it.
2. Otherwise, the first URL from `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` is used (RFC 3161 over HTTP).
Selection is made at boot from the discovered transport, not at runtime; there is no try-then-fall-through. If the chosen source fails, the recipient's sign attempt fails.
### Seal time — PAdES B-LTA archival
The seal-document job emits a single archival `/DocTimeStamp` over the fully-signed envelope (plus DSS for the existing signatures and the timestamp's own chain). This phase is **env-only**: the first URL from `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` is always used.
The archival anchor is the operator's long-term trust anchor and SHOULD point at a dedicated qualified archival TSA (e.g. DigiCert) independent of the per-recipient TSP. We deliberately do not fall back to the TSP at seal time: archive longevity should not be coupled to a TSP that may rotate or revoke, and the seal-document job has no recipient context to carry a service-scope bearer.
### Boot-time guard
The instance refuses to start in CSC mode unless `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` is set (`CSC_PROVIDER_NO_TSA` at transport construction). The env var is required unconditionally — even when the TSP advertises its own `signatures/timestamp`, seal-time B-LTA archival uses the env TSA. Catching this at boot prevents the failure mode where an envelope signs successfully at B-T and then hangs in `WAITING_FOR_SIGNATURE_COMPLETION` when the seal job throws.
## Switching Transports
`NEXT_PRIVATE_SIGNING_TRANSPORT` is a one-way operational migration. Existing envelopes route per the `signatureLevel` column they were created with — the runtime branching looks at the envelope, not the env var. After a switch:
- Envelopes already at `SES` continue to use the new transport for sealing, but the new transport's signer must produce SES-compatible signatures (only `local` and `gcloud-hsm` qualify).
- Envelopes already at `AES` / `QES` will fail at sign or seal time if the new transport is not `csc`.
Plan migrations during a quiet window with no in-flight envelopes.
## Behavioural Notes
CSC mode changes a number of envelope-authoring behaviours that operators should communicate to users.
### Mutation lock at distribution
For AES/QES envelopes, all authoring routes refuse mutations once the envelope leaves DRAFT. This locks the PDF before any recipient begins Strong Customer Authentication, closing the PDF-swap window that would otherwise allow an owner to replace the PDF between view and sign and break the legal "what you see is what you sign" guarantee.
In practice: edit envelope, recipients, fields, and items freely while DRAFT; once sent, no changes are accepted (including from the API).
### Sequential signing only
Parallel signing produces conflicting incremental updates over the same base PDF, breaking the per-recipient `/ByteRange` invariant. The signing order is forced to `SEQUENTIAL` on AES/QES envelopes — at the schema layer, at send time, and in the UI (the parallel-signing toggle is hidden).
### Assistant role and Dictate Next Signer disabled
Both features modify the recipient set after the envelope is sent, which is incompatible with the AES/QES mutation lock. They are hidden in the UI and rejected at the server schema layer.
### Sidecar PDFs at download
The signed PDF must remain byte-identical to what each recipient's TSP signature authorised — Documenso cannot decorate it after signing. Audit logs and the Certificate of Completion are generated on demand and delivered as separate PDFs:
- `GET /sign/{token}/download` returns the signed PDF only (or a ZIP for multi-item envelopes).
- `GET /sign/{token}/download?version=bundle` returns a ZIP containing the signed PDFs, audit log PDF, and Certificate of Completion.
- The completion email attaches all three.
## Recipient Flow
For context when supporting end users, here is what a recipient experiences on an AES/QES envelope:
1. Opens the email link, lands on the signing page.
2. Documenso redirects to the TSP for Strong Customer Authentication (first visit only; cached for the session lifetime).
3. Fills fields as normal.
4. Clicks Sign → redirected to the TSP for a second authentication round (issues a per-document Signature Activation Data token).
5. Returns to Documenso; the signing call completes within ~15 seconds.
6. Sees the standard completion screen.
If the TSP returns no eligible credentials for the recipient (e.g. they have not enrolled), they see a blocking page directing them to enrol with the TSP and retry.
## Error Codes
CSC-specific error codes surfaced through the standard error channels:
| Code | Meaning | Recovery |
| -------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------- |
| `CSC_UNLICENSED` | License flag absent at transport-create | Operator: enable Enterprise Edition, restart |
| `CSC_PROVIDER_INFO_FAILED` | `info` discovery failed at startup | Operator: check TSP availability and `NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL` |
| `CSC_PROVIDER_NO_TSA` | `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` is unset | Operator: configure `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` |
| `CSC_CREDENTIAL_LIST_EMPTY`| TSP returned no credentials for the user | Recipient: enrol with the TSP |
| `CSC_CERT_INVALID` | Certificate refused at credential validation | Recipient: contact the TSP |
| `CSC_ALGORITHM_REFUSED` | Signature algorithm fails policy | Operator/recipient: TSP does not meet policy (see below) |
| `CSC_SAD_EXPIRED_PRE_SIGN` | Signature Activation Data expired before signing | Recipient: retry from Sign |
| `CSC_TSP_TIMEOUT` | 15-second synchronous timeout reached | Recipient: retry (idempotent — the TSP enforces single-use SAD binding) |
| `CSC_EMBED_FAILED` | Sign-time digest diverged from prep capture | Recipient: retry from Sign |
| `CSC_BASE_DOCUMENT_MUTATED`| Document data changed between prep and sign | Operator: investigate (structural guard violation) |
| `CSC_INSTANCE_MODE_MISMATCH`| Envelope created with wrong level for transport | Caller: use a level matching the instance transport |
| `CSC_REQUEST_FAILED` | TSP HTTP transport failure — network error, non-2xx, or malformed response | Operator: check TSP availability; carries the TSP HTTP status and error in the message |
## Algorithm Policy
Documenso refuses TSP credentials that do not meet the following minimums, at the OAuth callback boundary and again at sign time:
| Class | Allowed | Refused |
| ----- | ---------------------------------- | ------------------------------------------------------ |
| RSA | `key.len >= 2048` | Missing `key.len`, `key.len < 2048` |
| ECDSA | P-256, P-384, P-521 | Missing `key.curve`, P-192, P-224, other curves |
| Hash | SHA-256, SHA-384, SHA-512 | SHA-1, MD5 |
| Other | — | DSA |
This is the union of CSC v1.0.4.0 §11.5 requirements and current cryptographic guidance.
## Related
- [Signature Levels](/docs/compliance/signature-levels) — AES / QES legal framework
- [Signing Certificate](/docs/self-hosting/configuration/signing-certificate) — overview of all signing transports
- [Environment Variables](/docs/self-hosting/configuration/environment) — full env reference
- [Enterprise Edition](/docs/policies/enterprise-edition) — license requirements
@@ -24,6 +24,11 @@ Self-hosted Documenso instances require a signing certificate. You can generate
description="Hardware-based key protection with Google Cloud KMS."
href="/docs/self-hosting/configuration/signing-certificate/google-cloud-hsm"
/>
<Card
title="CSC (AES / QES)"
description="Route signing through a third-party Trust Service Provider for Advanced and Qualified Electronic Signatures."
href="/docs/self-hosting/configuration/signing-certificate/csc-qes"
/>
<Card
title="Timestamp Server"
description="Add trusted timestamps and customise signature appearance."
@@ -38,7 +43,7 @@ Self-hosted Documenso instances require a signing certificate. You can generate
## Certificate Options
<Tabs items={['Self-Signed', 'CA-Issued', 'Google Cloud HSM']}>
<Tabs items={['Self-Signed', 'CA-Issued', 'Google Cloud HSM', 'CSC (AES / QES)']}>
<Tab value="Self-Signed">
A self-signed certificate is sufficient for most use cases where your industry has no special signing regulations.
@@ -79,6 +84,18 @@ For organisations requiring hardware-based key protection, Documenso supports Go
See [Google Cloud HSM](/docs/self-hosting/configuration/signing-certificate/google-cloud-hsm) for setup instructions.
</Tab>
<Tab value="CSC (AES / QES)">
For Advanced and Qualified Electronic Signatures under eIDAS, Documenso integrates with third-party Trust Service Providers via the Cloud Signature Consortium API. Each recipient authenticates directly with the TSP, which holds the private key and issues the signature.
- Per-recipient identity verification by an accredited TSP
- Legally equivalent to a handwritten signature within the EU (QES)
- Requires an [Enterprise Edition](/docs/policies/enterprise-edition) license
- Instance-wide setting; one CSC provider per Documenso install
See [CSC (AES / QES)](/docs/self-hosting/configuration/signing-certificate/csc-qes) for setup instructions.
</Tab>
</Tabs>
@@ -1,4 +1,4 @@
{
"title": "Signing Certificate",
"pages": ["...index", "local", "google-cloud-hsm", "timestamp-server", "troubleshooting"]
"pages": ["...index", "local", "google-cloud-hsm", "csc-qes", "timestamp-server", "troubleshooting"]
}
@@ -1,6 +1,6 @@
---
title: Storage Configuration
description: Configure file storage for uploaded documents and signed PDFs using database storage (default) or S3-compatible object storage.
description: Configure file storage for uploaded documents and signed PDFs using database storage (default), S3-compatible object storage, or Azure Blob Storage.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
@@ -10,10 +10,11 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Storage Options
| Backend | Best For | Scalability | Configuration |
| ---------- | -------------------------------- | ----------- | ------------- |
| `database` | Small deployments, simplicity | Limited | None required |
| `s3` | Production, large files, backups | High | Required |
| Backend | Best For | Scalability | Configuration |
| ------------ | --------------------------------------- | ----------- | ------------- |
| `database` | Small deployments, simplicity | Limited | None required |
| `s3` | Production, large files, backups | High | Required |
| `azure-blob` | Production on Azure, native Blob access | High | Required |
Select the storage backend with the `NEXT_PUBLIC_UPLOAD_TRANSPORT` environment variable:
@@ -23,6 +24,9 @@ NEXT_PUBLIC_UPLOAD_TRANSPORT=database
# S3-compatible storage
NEXT_PUBLIC_UPLOAD_TRANSPORT=s3
# Azure Blob Storage (native)
NEXT_PUBLIC_UPLOAD_TRANSPORT=azure-blob
```
---
@@ -283,6 +287,111 @@ NEXT_PRIVATE_UPLOAD_REGION=us-east-1
---
## Azure Blob Storage
Azure Blob Storage is supported as a native transport (not S3-compatible). Documenso uses the official `@azure/storage-blob` SDK and signs SAS URLs with the Storage Account key for browser uploads and downloads.
### Required Variables
| Variable | Description |
| --------------------------------------- | ------------------------------------------------- |
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | Set to `azure-blob` |
| `NEXT_PRIVATE_UPLOAD_AZURE_ACCOUNT_NAME` | Azure Storage Account name |
| `NEXT_PRIVATE_UPLOAD_AZURE_ACCOUNT_KEY` | Azure Storage Account access key |
| `NEXT_PRIVATE_UPLOAD_AZURE_CONTAINER` | Container name where uploads are stored |
### Optional Variables
| Variable | Description | Default |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `NEXT_PRIVATE_UPLOAD_AZURE_ENDPOINT` | Custom Blob endpoint URL. Useful for local development against Azurite (for example `http://127.0.0.1:10000`). | `https://<account>.blob.core.windows.net` |
### Azure Setup
{/* prettier-ignore */}
<Steps>
<Step>
### Create a Storage Account and Container
Create a Storage Account in the Azure Portal or via the Azure CLI, then create a container inside it:
```bash
az storage account create \
--name yourstorageaccount \
--resource-group your-rg \
--location eastus \
--sku Standard_LRS
az storage container create \
--name documenso-documents \
--account-name yourstorageaccount
```
</Step>
<Step>
### Configure CORS on the container
The browser uploads documents directly to Azure Blob using a SAS URL, and downloads them the same way, so the Storage Account needs CORS rules that allow your application origin:
```bash
az storage cors add \
--services b \
--methods GET PUT \
--origins https://your-documenso-domain.com \
--allowed-headers "Content-Type" "x-ms-blob-type" "Authorization" \
--exposed-headers "*" \
--max-age 3600 \
--account-name yourstorageaccount
```
</Step>
<Step>
### Configure Environment Variables
```bash
NEXT_PUBLIC_UPLOAD_TRANSPORT=azure-blob
NEXT_PRIVATE_UPLOAD_AZURE_ACCOUNT_NAME=yourstorageaccount
NEXT_PRIVATE_UPLOAD_AZURE_ACCOUNT_KEY=your-account-key
NEXT_PRIVATE_UPLOAD_AZURE_CONTAINER=documenso-documents
```
</Step>
</Steps>
### Local Development with Azurite
Azurite is the official Azure Storage emulator. It supports the Blob REST API with account-key authentication.
```bash
docker run -d --name azurite \
-p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite
```
Create the container against the well-known development account:
```bash
az storage container create \
--name documenso-documents \
--connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
```
Configure environment variables to point at the emulator:
```bash
NEXT_PUBLIC_UPLOAD_TRANSPORT=azure-blob
NEXT_PRIVATE_UPLOAD_AZURE_ACCOUNT_NAME=devstoreaccount1
NEXT_PRIVATE_UPLOAD_AZURE_ACCOUNT_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
NEXT_PRIVATE_UPLOAD_AZURE_CONTAINER=documenso-documents
NEXT_PRIVATE_UPLOAD_AZURE_ENDPOINT=http://127.0.0.1:10000
```
<Callout type="info">
The Azurite key shown above is the public well-known development key, published by Microsoft for emulator use. Never reuse it in production.
</Callout>
---
## CloudFront CDN (Optional)
Use Amazon CloudFront to serve documents with lower latency and reduced S3 costs. CloudFront integration uses signed URLs for secure access.
@@ -81,7 +81,7 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?err}
- POSTGRES_DB=${POSTGRES_DB:?err}
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 10s
timeout: 5s
retries: 5
@@ -155,8 +155,27 @@ PORT=3000
NEXT_PRIVATE_SIGNING_PASSPHRASE=your-certificate-password
# Signup restrictions (optional)
# Master switch — disables every signup method
NEXT_PUBLIC_DISABLE_SIGNUP=false
# Per-method switches (optional). Each disables brand-new account creation through that method.
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP=true
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP=true
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=true
# NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=true
# NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=example.com,acme.org
# Signin restrictions (optional)
# Master switch — disables every signin method
# NEXT_PUBLIC_DISABLE_SIGNIN=true
# Per-method switches (optional). Each disables that signin path.
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN=true
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN=true
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN=true
# NEXT_PUBLIC_DISABLE_OIDC_SIGNIN=true
# When OIDC is the only enabled transport, /signin auto-redirects to the provider.
# Set this to opt out and keep showing the signin page (optional).
# NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT=true
```
<Callout type="info">Generate secure secrets using: `openssl rand -base64 32`</Callout>
@@ -252,7 +271,10 @@ Navigate to the signup page and create your account. Verify your email address
<Callout type="info">
All accounts created through signup are regular user accounts. Admin access must be granted
directly in the database. Once your accounts are set up, consider disabling public signups by
setting `NEXT_PUBLIC_DISABLE_SIGNUP=true`, or restrict signups to specific email domains with
setting `NEXT_PUBLIC_DISABLE_SIGNUP=true`. For finer control, use the per-method switches
`NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP`, `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP`,
`NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`, `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP`, or restrict
signups to specific email domains with
`NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS`.
</Callout>
@@ -26,8 +26,14 @@ docker --version
## Pulling the Docker Image
The Documenso image is available on both DockerHub and GitHub Container Registry:
```bash
# DockerHub
docker pull documenso/documenso:latest
# GitHub Container Registry
docker pull ghcr.io/documenso/documenso:latest
```
### Available Tags
@@ -100,8 +106,18 @@ See [Email Configuration](/docs/self-hosting/configuration/email) for other tran
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | Passphrase for the signing certificate | - |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | Base64-encoded `.p12` certificate (alternative to file path) | - |
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | Document storage: `database` or `s3` | `database` |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public signups | `false` |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch — disable all signup methods | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Disable email/password signup only | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Block new accounts via Google OAuth | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Block new accounts via Microsoft OAuth | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC (incl. organisation portal) | `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of allowed signup email domains | |
| `NEXT_PUBLIC_DISABLE_SIGNIN` | Master switch — disable all signin methods | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN` | Disable email/password signin only | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN` | Hide the Google signin button | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN` | Hide the Microsoft signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNIN` | Hide the OIDC signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT` | Disable auto-redirect to OIDC when it is the only transport | `false` |
For the complete list, see [Environment Variables](/docs/self-hosting/configuration/environment).
@@ -192,6 +208,14 @@ Documenso provides health check endpoints for monitoring:
| `/api/health` | Checks database connectivity and certificate status |
| `/api/certificate-status` | Returns whether a signing certificate is configured and usable |
Both endpoints return a JSON response with a `status` field:
| Status | Meaning |
| ----------- | -------------------------------------------------------------------- |
| `"ok"` | Everything is working properly |
| `"warning"` | Application is running but there are certificate issues |
| `"error"` | Critical issues (database unreachable, missing configuration, etc.) |
### Docker Health Check
Add a health check to your container:
@@ -235,7 +235,7 @@ spec:
```
<Callout type="info">
Pin to a specific image tag (e.g., `documenso/documenso:1.5.0`) in production instead of `latest`
Pin to a specific image tag (e.g., `documenso/documenso:<version>`) in production instead of `latest`
to ensure predictable deployments.
</Callout>
@@ -14,8 +14,8 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
## Prerequisites
- Node.js 20 or later
- npm
- Node.js 22 or later
- npm 11 or later
- PostgreSQL 14 or later
- A Linux server (for systemd service setup)
@@ -24,7 +24,7 @@ Before deploying, you need:
The fastest way to deploy Documenso on Railway is using the official template:
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/bG6D4p)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/DjrRRX?referralCode=EZR3s0&utm_medium=integration&utm_source=template&utm_campaign=generic)
This template automatically provisions:
@@ -153,8 +153,18 @@ NEXT_PRIVATE_SMTP_FROM_ADDRESS=noreply@yourdomain.com
| Variable | Description | Default |
| --------------------------------- | ---------------------------------- | ------- |
| `PORT` | Application port | `3000` |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public signups | `false` |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch — disable all signup methods | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Disable email/password signup only | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Block new accounts via Google OAuth | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`| Block new accounts via Microsoft OAuth | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC (incl. organisation portal)| `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of allowed signup email domains | |
| `NEXT_PUBLIC_DISABLE_SIGNIN` | Master switch — disable all signin methods | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN` | Disable email/password signin only | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN` | Hide the Google signin button | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN`| Hide the Microsoft signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNIN` | Hide the OIDC signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT` | Disable auto-redirect to OIDC when it is the only transport | `false` |
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | Passphrase for signing certificate | - |
| `DOCUMENSO_DISABLE_TELEMETRY` | Disable anonymous telemetry | `false` |
@@ -3,6 +3,8 @@ title: Getting Started
description: Requirements and quick start guide for self-hosting Documenso.
---
import { Callout } from 'fumadocs-ui/components/callout';
<Cards>
<Card
title="Requirements"
@@ -15,3 +17,11 @@ description: Requirements and quick start guide for self-hosting Documenso.
href="/docs/self-hosting/getting-started/quick-start"
/>
</Cards>
<Callout type="error">
**You must generate a signing certificate.** Documenso does not ship with one. Without a
certificate, the application starts normally but document signing will fail on completion with
errors.
Please see all the [requirements](/docs/self-hosting/getting-started/requirements) before proceeding.
</Callout>
@@ -124,12 +124,16 @@ docker compose -f docker/development/compose.yml exec database \
The quick start setup runs the following containers:
| Container | Purpose | Port |
| ----------- | -------------------------------- | ----- |
| `documenso` | Main application | 3000 |
| `database` | PostgreSQL database | 54320 |
| `maildev` | Local email testing server | 2500 |
| `minio` | S3-compatible storage (optional) | 9000 |
| Container | Purpose | Port |
| ----------- | ------------------------------------ | ----------------------------- |
| `documenso` | Main application | 3000 |
| `database` | PostgreSQL database | 54320 |
| `inbucket` | Local email testing server | 9000 (web UI), 2500 (SMTP) |
| `redis` | Cache and background job queue | 63790 |
| `minio` | S3-compatible storage | 9002 (API), 9001 (console) |
| `gotenberg` | Document conversion (optional) | 3005 |
The local email server is [Inbucket](https://www.inbucket.org/). Open its web UI at [http://localhost:9000](http://localhost:9000) to view emails Documenso sends during development. For your own deployment you can use any SMTP-compatible mailserver, such as Inbucket, [Mailpit](https://github.com/axllent/mailpit), or [Mailhog](https://github.com/mailhog/MailHog).
## Useful Commands
@@ -7,14 +7,29 @@ import { Callout } from 'fumadocs-ui/components/callout';
## What You Need
Documenso requires the following external services:
Documenso requires the following items and external services:
| Service | Purpose | Minimum Version |
| ------------- | ---------------------------- | --------------- |
| Signing certificate | Digital signature for documents | N/A |
| PostgreSQL | Primary database | 14+ |
| SMTP server | Sending emails to recipients | Any |
| Reverse proxy | SSL termination, routing | Any |
### Signing Certificate
<Callout type="error">
Documenso does not ship with a signing certificate. Without one, the application starts normally
but all document signing will fail. You must generate or provide a `.p12` certificate before going
to production.
</Callout>
Every completed document is digitally signed using an X.509 certificate. You can generate a self-signed certificate for free or use one from a Certificate Authority (CA).
- [Generate a local certificate](/docs/self-hosting/configuration/signing-certificate/local) — step-by-step instructions to create a `.p12` certificate
- [All certificate options](/docs/self-hosting/configuration/signing-certificate) — self-signed, CA-issued, and Google Cloud HSM
### PostgreSQL Database
Documenso uses PostgreSQL for all data storage including documents, users, and audit logs. You cannot use MySQL, SQLite, or other databases.
@@ -126,8 +141,8 @@ If building from source (not using Docker images):
| Requirement | Version |
| ----------- | ------- |
| Node.js | 18+ |
| npm | 8+ |
| Node.js | 22+ |
| npm | 11+ |
---
@@ -154,7 +169,7 @@ Documenso runs on:
| MySQL/MariaDB | PostgreSQL-specific features required |
| SQLite | Not suitable for production workloads |
| MongoDB | Relational database required |
| Node.js < 18 | Modern JavaScript features required |
| Node.js < 22 | Modern JavaScript features required |
---
@@ -92,7 +92,7 @@ Use a specific version tag in production:
```bash
# Good — predictable, reproducible
docker pull documenso/documenso:1.8.0
docker pull documenso/documenso:<version>
# Risky — may pull breaking changes
docker pull documenso/documenso:latest
@@ -154,6 +154,34 @@ See [Background Jobs Configuration](/docs/self-hosting/configuration/background-
---
## IPv6-Only Deployments
If you are deploying to an environment that uses only IPv6, set the `HOST` environment variable to `::` so the application binds to all IPv6 addresses:
**Docker:**
```bash
docker run -it -e HOST=:: documenso/documenso:latest npm run start
```
**Kubernetes or Docker Compose:**
```yaml
containers:
- name: documenso
image: documenso/documenso:latest
command:
- npm
args:
- run
- start
env:
- name: HOST
value: '::'
```
---
## Docker File Permissions
The Documenso container runs as a non-root user (UID 1001). If you mount files into the container (certificates, configuration), ensure they're readable:
+19 -2
View File
@@ -3,6 +3,8 @@ title: Self-Hosting
description: Deploy and manage your own Documenso instance for complete control over your data, compliance, and customization.
---
import { Callout } from 'fumadocs-ui/components/callout';
## Getting Started
<Cards>
@@ -18,6 +20,21 @@ description: Deploy and manage your own Documenso instance for complete control
/>
</Cards>
<Callout type="error">
**You must generate a signing certificate.** Documenso does not ship with one. Without a
certificate, the application starts normally but document signing will fail.
Please see all the [requirements](/docs/self-hosting/getting-started/requirements) before proceeding.
</Callout>
<Callout type="warn">
**You are responsible for your own network security.** Documenso applies best-effort, non-exhaustive
checks to outbound requests such as webhooks, but these are not a complete SSRF mitigation and they
fail open. A self-hosted instance can reach internal addresses on your network. Restricting outbound
traffic, egress filtering, and blocking access to internal services and cloud metadata endpoints is
your responsibility through your firewall and network configuration.
</Callout>
---
## Deployment Options
@@ -122,9 +139,9 @@ See the [Quick Start guide](/docs/self-hosting/getting-started/quick-start) for
## Enterprise Edition
Self-hosted Documenso includes full core functionality under the AGPL-3.0 license. If you need enterprise features such as SSO, embed authoring white label, or 21 CFR Part 11 compliance, you can activate them with a license key.
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).
---
@@ -165,10 +165,10 @@ See [Backups](/docs/self-hosting/maintenance/backups) for automated backup strat
### Pull the new image
```bash
docker pull documenso/documenso:1.6.0
docker pull documenso/documenso:<version>
```
Replace `1.6.0` with your target version.
Replace `<version>` with your target version.
</Step>
<Step>
@@ -189,7 +189,7 @@ docker run -d \
-p 3000:3000 \
--env-file .env \
-v /path/to/cert.p12:/opt/documenso/cert.p12:ro \
documenso/documenso:1.6.0
documenso/documenso:<version>
```
</Step>
@@ -223,14 +223,14 @@ Edit `compose.yml` or your `.env` file to specify the new version:
```yaml
services:
documenso:
image: documenso/documenso:1.6.0
image: documenso/documenso:<version>
```
Or if using environment variable substitution:
```bash
# In .env
DOCUMENSO_VERSION=1.6.0
DOCUMENSO_VERSION=<version>
```
```yaml
@@ -283,7 +283,7 @@ Edit the deployment directly:
```bash
kubectl set image deployment/documenso \
documenso=documenso/documenso:1.6.0 \
documenso=documenso/documenso:<version> \
-n documenso
```
@@ -295,7 +295,7 @@ spec:
spec:
containers:
- name: documenso
image: documenso/documenso:1.6.0
image: documenso/documenso:<version>
```
Then apply:
@@ -421,12 +421,12 @@ To run migrations manually before upgrading:
```bash
# Pull the new image
docker pull documenso/documenso:1.6.0
docker pull documenso/documenso:<version>
# Run migrations only
docker run --rm \
-e NEXT_PRIVATE_DATABASE_URL="postgresql://user:password@host:5432/documenso" \
documenso/documenso:1.6.0 \
documenso/documenso:<version> \
npx prisma migrate deploy
```
@@ -516,7 +516,7 @@ docker run -d \
-p 3000:3000 \
--env-file .env \
-v /path/to/cert.p12:/opt/documenso/cert.p12:ro \
documenso/documenso:1.5.0
documenso/documenso:<previous-version>
```
</Tab>

Some files were not shown because too many files have changed in this diff Show More