Compare commits

..

121 Commits

Author SHA1 Message Date
797553bcde chore: add translations 2025-11-13 14:04:16 +00:00
47466a2db9 chore: Update README.md (#2188)
We are launching Documenso 2.0 on Product Hunt, come say hi!
2025-11-13 20:52:34 +11:00
e4e04cdddc feat: simplify billing ux (#2117) 2025-11-13 15:58:16 +11:00
74a03077b7 fix: placeholders translations (#2020) 2025-11-13 14:26:19 +11:00
58bff33275 fix: add context to signature pad translations (#2051) 2025-11-13 13:35:23 +11:00
0cb23be27a chore: add question mark to message (#2176) 2025-11-13 12:08:39 +11:00
4304fc1d35 chore: add envelope docs (#2186) 2025-11-13 11:50:06 +11:00
ce53bcea8c v2.0.11 2025-11-13 10:56:58 +11:00
5a3d5b8b4a fix: persist missed meta values for envelopes (#2185) 2025-11-13 10:44:59 +11:00
3d1fe85d62 fix: add foreign key indexes (#2184)
Can't believe we missed some of these ☠️
2025-11-13 10:43:11 +11:00
1772c3ee36 v2.0.10 2025-11-12 18:57:24 +11:00
f55b902a01 fix: openapi path for envelope item download 2025-11-12 18:57:03 +11:00
50db4e39be v2.0.9 2025-11-12 18:22:46 +11:00
2802813c76 fix: add default values for envelope field meta (#2181)
🙏
2025-11-12 18:22:30 +11:00
29d40f1cca v2.0.8 2025-11-12 17:19:53 +11:00
d67f32eae2 chore: add translations (#2166)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-11-12 17:17:44 +11:00
a33233443b chore: extract translations (#2179) 2025-11-12 17:09:50 +11:00
68a3608aee fix: handle bracket notation arrays (#2178)
Co-authored-by: David Nguyen <davidngu28@gmail.com>
2025-11-12 16:38:06 +11:00
378dd605b9 v2.0.7 2025-11-12 13:09:18 +11:00
211ae6c9e9 fix: prefill arcoforms with formdata endpoints (#2169) 2025-11-12 12:41:10 +11:00
f931885a95 fix: remove redundant endpoint (#2170)
Remove duplicate redundant endpoint
2025-11-12 11:01:15 +11:00
4ade408001 fix: include cmaps for pdf viewer (#2177)
We were previously omitting cmaps meaning that
when signing documents with certain UTF-8 characters or CJK characters
they would appear as outlined squares in the pdf viewer despite the
actual pdf looking as expected with the characters displaying correctly.
2025-11-12 11:00:44 +11:00
3d0e3c6e8e fix: update dropzone to create envelopes (#2168) 2025-11-11 20:49:42 +11:00
936d8d90b3 fix: envelope auth not being passed (#2167) 2025-11-11 13:05:05 +11:00
c6b08d8594 feat: org insights (#1937) 2025-11-11 12:09:58 +11:00
575634e326 fix: add pluralization to document details (#2130) 2025-11-11 10:54:12 +11:00
c66eda4aae chore: add message context in user organisation table (#2138) 2025-11-11 10:44:20 +11:00
ef52b35f79 chore: change message in dropzone wrappers (#2137) 2025-11-11 10:43:22 +11:00
95a647034a chore: add message context in envelope download dialog (#2136) 2025-11-11 10:42:30 +11:00
34dba0b6ff chore: fix message formatting (#2135) 2025-11-11 10:41:54 +11:00
fccd97e124 fix: add pluralization to 2fa code template (#2131) 2025-11-11 10:39:05 +11:00
3dbbcefddf fix: add pluralization to passkey page (#2129) 2025-11-11 10:37:54 +11:00
2aea3c4de0 fix: rename envelope buttons (#2161) 2025-11-10 22:21:34 +11:00
ff44ffbc03 v2.0.6 2025-11-10 19:08:43 +11:00
441842d2bd fix: use correct token for embeded template files (#2160) 2025-11-10 19:08:08 +11:00
ca0b83579f fix: auto insert prefilled text and number fields (#2157) 2025-11-10 18:04:21 +11:00
6c0d1da91e fix(input): prevent mobile zoom on input focus (#2079) 2025-11-10 12:43:06 +11:00
805982f3e8 fix: envelope cc issues (#2158) 2025-11-10 11:42:57 +11:00
e2f5e570cf fix: envelope direct template (#2156) 2025-11-09 22:23:13 +11:00
9fd9613076 feat: add additional field options (#2154) 2025-11-08 23:40:03 +11:00
0977c16e33 v2.0.5 2025-11-08 16:03:59 +11:00
88d5a636c3 fix: show legacy ids on template and document view page (#2153)
<img width="557" height="455" alt="image"
src="https://github.com/user-attachments/assets/7b669f4a-c6c5-4fdc-bf10-da0def7b0b3f"
/>
2025-11-08 16:03:26 +11:00
1e6292b1d9 v2.0.4 2025-11-08 13:58:11 +11:00
d65866156d fix: remove parallel steps (#2152) 2025-11-08 13:57:26 +11:00
fe8915162f v2.0.3 2025-11-08 12:53:50 +11:00
37a2634aca feat: support optimizeParallelism for inngest jobs (#2151) 2025-11-08 12:53:13 +11:00
eff7d90f43 v2.0.2 2025-11-08 00:48:31 +11:00
db5524f8ce fix: resolve issue with sealing task on inngest (#2146)
Currently on inngest the sealing task fails during decoration stating
that it can not find the step "xxx"

My running theory is that this was due to it being a
Promise.all(map(...)) even though that isn't explicitly disallowed.

This change turns it into a for loop collecting promises to be awaited
after the fact.

Local inngest testing looks promising.
2025-11-08 00:48:13 +11:00
3d539b20ad v2.0.1 2025-11-07 23:42:03 +11:00
48626b9169 fix: support utf8 filenames download (#2145) 2025-11-07 23:41:31 +11:00
88371b665a fix: set correct envelope item cache url (#2144) 2025-11-07 16:50:58 +11:00
1650c55b19 v2.0.0 2025-11-07 15:40:24 +11:00
60d73e0921 chore: add translations (#2143)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-11-07 14:45:21 +11:00
4a779ec81e fix: handle custom org limits with member invite 2025-11-07 14:24:05 +11:00
7f19ec1265 fix: embedded direct template recipient auth 2025-11-07 14:23:46 +11:00
d6a2f5a4c9 chore: extract translations (#2070) 2025-11-07 14:20:53 +11:00
d05bfa9fed feat: add envelopes api (#2105) 2025-11-07 14:17:52 +11:00
d2a009d52e fix: allow direct template recipient dictation (#2108) 2025-11-01 12:44:34 +11:00
9350c53c7d chore: add code styleguide (#2089)
Co-authored-by: Ephraim Atta-Duncan <ephraimduncan68@gmail.com>
2025-10-28 22:25:27 +11:00
ffce7a2c81 fix: filter document stats by folder (#2083)
This pull request refactors the filtering logic in the `getTeamCounts`
function within `get-stats.ts` to improve consistency and
maintainability. The main change is the consolidation of multiple filter
conditions into a single `AND` clause, which now includes search
filters, folder filters, and visibility filters. This ensures that all
relevant filters are applied in a unified way for document count
queries.
2025-10-28 21:16:12 +11:00
353bdce86b feat: admin member role updates (#2093) 2025-10-28 21:09:38 +11:00
e13b9f7c84 fix: hide banner for envelope editor (#2109) 2025-10-28 16:55:44 +11:00
9908580bf1 fix: add envelopes flag (#2104)
## Description

Add a global flag override for envelopes
2025-10-28 11:42:03 +11:00
b0b07106b4 fix: envelope autosave (#2103) 2025-10-27 19:53:35 +11:00
35250fa308 feat: server port configurable via PORT env (#2097) 2025-10-27 17:24:24 +11:00
5cdd7f8623 fix: envelope styling (#2102) 2025-10-27 16:11:10 +11:00
47bdcd833f chore: extract translations (#2094) 2025-10-24 16:37:10 +11:00
03eb6af69a feat: polish envelopes (#2090)
## Description

The rest of the owl
2025-10-24 16:22:06 +11:00
88836404d1 v1.13.1 2025-10-24 10:50:25 +11:00
2eebc0e439 feat: add attachments (#2091) 2025-10-23 23:07:10 +11:00
4a3859ec60 feat: signin with microsoft (#1998) 2025-10-22 12:05:11 +11:00
49b792503f fix: query envelope table for openpage stats (#2086) 2025-10-21 12:43:57 +00:00
c3dc76b1b4 feat: add API support for folders (#1967) 2025-10-21 18:22:19 +11:00
daab8461c7 fix: email attachment names (#2085) 2025-10-21 12:59:40 +11:00
1ffc4bd703 v1.13.0 2025-10-21 11:21:04 +11:00
f15c0778b5 fix: authoring token arg and null email settings 2025-10-21 10:42:44 +11:00
06cb8b1f23 fix: email attachment formats (#2077) 2025-10-16 14:16:00 +11:00
7f09ba72f4 feat: add envelopes (#2025)
This PR is handles the changes required to support envelopes. The new
envelope editor/signing page will be hidden during release.

The core changes here is to migrate the documents and templates model to
a centralized envelopes model.

Even though Documents and Templates are removed, from the user
perspective they will still exist as we remap envelopes to documents and
templates.
2025-10-14 21:56:36 +11:00
7b17156e56 v1.12.10 2025-10-09 15:32:35 +11:00
86e89e137e fix: bump search limit and path formatting (#2069) 2025-10-09 15:11:43 +11:00
26f65dbdd7 v1.12.9 2025-10-07 17:07:11 +11:00
a902bec96d fix: use select account prompt for sso oidc (#2065)
Use the `select_account` prompt for SSO OIDC to avoid constantly asking
for credentials to be entered with a client has an existing session with
the SSO provider.
2025-10-07 17:06:28 +11:00
399f91de73 feat: improve invite email (#2030)
Improve the email sent to invite a user to approve/sign/assist/view a
document.
The current links "Reject Document" / "Sign Document" confuse some users
as they think the document will be rejected/signed just by clicking on
these buttons.
This change makes it more clear that they will have a chance to view the
document before they can reject/sign.
2025-10-06 16:19:16 +11:00
995bc9c362 feat: support 2fa for document completion (#2063)
Adds support for 2FA when completing a document, also adds support for
using email for 2FA when no authenticator has been associated with the
account.
2025-10-06 16:17:54 +11:00
3467317271 chore: extract translations (#2056)
## Description

Extract translations to be translated
2025-10-02 13:20:30 +10:00
a5eaa8ad47 v1.12.8 2025-09-29 23:22:59 +10:00
577691214b fix: add viewed call for embedded signing (#2055)
Adds the missing `viewedDocument` method call for embedded signing.

I believe this had previously been added but was lost as the result of a
merge conflict being resolved.
2025-09-29 23:22:36 +10:00
c7d21c6587 fix: update personal organisation email settings (#2048) 2025-09-29 10:11:00 +03:00
2aa391f917 v1.12.7 2025-09-26 09:57:34 +10:00
681540b501 fix: add removed date formats (#2049)
Add date formats that were removed in a prior pull request causing
issues with certain API requests.
2025-09-26 09:56:46 +10:00
f3305ac306 feat: show branding logo on signing page (#2031)
If the team has the branding enabled & a logo uploaded, it'll show on
the document signing page view.
2025-09-25 22:41:17 +10:00
68b4305b6a feat: add max file size for uploaded documents (#2044) 2025-09-25 22:40:00 +10:00
3de1ea0a02 feat: resend dialog improvements (#2034)
The checkboxes were difficult to see and the "Send reminder" button
wasn't disabled when no recipients were selected. This PR disables the
sending button when there's no selected recipient and improves the
checkboxes visibility.
2025-09-25 22:23:07 +10:00
b8fc47b719 v1.12.6 2025-09-25 22:10:20 +10:00
cfceebd78f feat: change organisation owner in admin panel (#2047)
Allows changing the owner of an organisation within the admin panel,
useful for support requests to change ownership from a testing account
to the main admin account.

<img width="890" height="431" alt="image"
src="https://github.com/user-attachments/assets/475bbbdd-0f26-4f74-aacf-3e793366551d"
/>
2025-09-25 17:13:47 +10:00
b9b3ddfb98 chore: update tests to use new date formats (#2045)
## Description

Update the tests to use the new date formats form this PR
https://github.com/documenso/documenso/pull/2038.
2025-09-25 16:55:31 +10:00
8590502338 fix: file upload error messages (#2041) 2025-09-24 16:06:41 +03:00
53f29daf50 fix: allow dates with and without time (#2038) 2025-09-24 14:46:04 +03:00
197d17ed7b v1.12.5 2025-09-23 21:00:48 +10:00
3c646d9475 feat: remove email requirement for recipients (#2040) 2025-09-23 17:13:52 +10:00
ed4dfc9b55 v1.12.4 2025-09-13 18:08:55 +10:00
32ce573de4 fix: incorrect certificate health logic (#2028) 2025-09-13 18:07:39 +10:00
2ecfdbdde5 v1.12.3 2025-09-12 23:02:59 +10:00
a3005f8616 fix: Include NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS in docker compose file. (#2022) 2025-09-10 22:55:56 +10:00
2c0d4f8789 chore: self hosting docs update and certificate issues (#1847) 2025-09-09 21:26:42 +10:00
7c8e93b53e feat: implement recipients autosuggestions (#1923) 2025-09-09 20:57:26 +10:00
93a3809f6a fix: add maxLength limits to document input fields (#1988) 2025-09-09 17:52:03 +10:00
4550bca3d3 fix: signature pad translation (#2007) 2025-09-09 17:14:44 +10:00
9ac7b94d9a feat: add organisation sso portal (#1946)
Allow organisations to manage an SSO OIDC compliant portal. This method
is intended to streamline the onboarding process and paves the way to
allow organisations to manage their members in a more strict way.
2025-09-09 17:14:07 +10:00
374f2c45b4 chore: add soc2 compliance (#2019)
added soc2 compliance to docs
2025-09-08 17:56:53 +02:00
bb5c2edefd feat: implement auto-save functionality for signers in document edit form (#1792) 2025-09-02 21:01:16 +10:00
19565c1821 fix: access audit logs for documents in folder (#1989) 2025-08-31 12:17:31 +10:00
2603ae8b90 fix: send signing request email after the document status is updated (#1944)
When sending a document for signing, emails for recipients are sent
before the document status is updated.
In this case, the job "send.signing.requested.email" fails because it
cannot find the document with a PENDING status.
2025-08-31 11:37:49 +10:00
7d257236a6 fix: default pagination on documents list API (#1929) 2025-08-28 16:20:27 +10:00
31c1a9a783 fix: preserve existing recipient properties when adding new recipient (#1987) 2025-08-28 16:19:14 +10:00
657db3bc84 fix: improve mobile signing ux (#2003)
Improves the mobile signing UX making actions available via the floating
navbar more obvious.

Also adds an automatic switch to the complete button once all fields
have been signed.
2025-08-28 16:15:52 +10:00
184ebdedf1 v1.12.2-rc.6 2025-08-26 11:17:43 +10:00
4012022f55 fix: element visible race condition (#1996)
On larger documents we could accidentally start trying to render fields
while not all pages of the PDF have loaded due to us checking for a
single page existing. This would cause an error to be thrown, hard
locking those documents.

This change resolves this by grabbing the highest page number from the
given fields and using it for the visibility check instead.
2025-08-26 11:08:43 +10:00
44f5da95b3 chore: refactor routes (#1992) 2025-08-25 21:00:35 +10:00
7eb882aea8 fix: email domain sender logic (#1993) 2025-08-25 20:59:37 +10:00
dbf10e5b7b chore: add agents file (#1991) 2025-08-25 11:32:15 +10:00
908 changed files with 72136 additions and 17244 deletions

View File

@ -13,6 +13,10 @@ NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="DEADBEEF"
# https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#google-oauth-gmail # https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#google-oauth-gmail
NEXT_PRIVATE_GOOGLE_CLIENT_ID="" NEXT_PRIVATE_GOOGLE_CLIENT_ID=""
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET="" NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=""
# Find documentation on setting up Microsoft OAuth here:
# https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#microsoft-oauth-azure-ad
NEXT_PRIVATE_MICROSOFT_CLIENT_ID=""
NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET=""
NEXT_PRIVATE_OIDC_WELL_KNOWN="" NEXT_PRIVATE_OIDC_WELL_KNOWN=""
NEXT_PRIVATE_OIDC_CLIENT_ID="" NEXT_PRIVATE_OIDC_CLIENT_ID=""
@ -25,6 +29,10 @@ NEXT_PUBLIC_WEBAPP_URL="http://localhost:3000"
# URL used by the web app to request itself (e.g. local background jobs) # URL used by the web app to request itself (e.g. local background jobs)
NEXT_PRIVATE_INTERNAL_WEBAPP_URL="http://localhost:3000" NEXT_PRIVATE_INTERNAL_WEBAPP_URL="http://localhost:3000"
# [[SERVER]]
# OPTIONAL: The port the server will listen on. Defaults to 3000.
PORT=3000
# [[DATABASE]] # [[DATABASE]]
NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso" NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
# Defines the URL to use for the database when running migrations and other commands that won't work with a connection pool. # Defines the URL to use for the database when running migrations and other commands that won't work with a connection pool.

3
.gitignore vendored
View File

@ -57,3 +57,6 @@ logs.json
# claude # claude
.claude .claude
CLAUDE.md CLAUDE.md
# agents
.specs

57
AGENTS.md Normal file
View File

@ -0,0 +1,57 @@
# Agent Guidelines for Documenso
## Build/Test/Lint Commands
- `npm run build` - Build all packages
- `npm run lint` - Lint all packages
- `npm run lint:fix` - Auto-fix linting issues
- `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 dev` - Start development server for Remix app
## Code Style Guidelines
- Use TypeScript for all code; prefer `type` over `interface`
- Use functional components with `const Component = () => {}`
- Never use classes; prefer functional/declarative patterns
- Use descriptive variable names with auxiliary verbs (isLoading, hasError)
- Directory names: lowercase with dashes (auth-wizard)
- Use named exports for components
- Never use 'use client' directive
- Never use 1-line if statements
- Structure files: exported component, subcomponents, helpers, static content, types
## Error Handling & Validation
- Use custom AppError class when throwing errors
- When catching errors on the frontend use `const error = AppError.parse(error)` to get the error code
- Use early returns and guard clauses
- Use Zod for form validation and react-hook-form for forms
- Use error boundaries for unexpected errors
## UI & Styling
- Use Shadcn UI, Radix, and Tailwind CSS with mobile-first approach
- Use `<Form>` `<FormItem>` elements with fieldset having `:disabled` attribute when loading
- Use Lucide icons with longhand names (HomeIcon vs Home)
## TRPC Routes
- Each route in own file: `routers/teams/create-team.ts`
- Associated types file: `routers/teams/create-team.types.ts`
- Request/response schemas: `Z[RouteName]RequestSchema`, `Z[RouteName]ResponseSchema`
- Only use GET and POST methods in OpenAPI meta
- Deconstruct input argument on its own line
- Prefer route names such as get/getMany/find/create/update/delete
- "create" routes request schema should have the ID and data in the top level
- "update" routes request schema should have the ID in the top level and the data in a nested "data" object
## Translations & Remix
- Use `<Trans>string</Trans>` for JSX translations from `@lingui/react/macro`
- Use `t\`string\`` macro for TypeScript translations
- Use `(params: Route.Params)` and `(loaderData: Route.LoaderData)` for routes
- Directly return data from loaders, don't use `json()`
- Use `superLoaderJson` when sending complex data through loaders such as dates or prisma decimals

692
CODE_STYLE.md Normal file
View File

@ -0,0 +1,692 @@
# Documenso Code Style Guide
This document captures the code style, patterns, and conventions used in the Documenso codebase. It covers both enforceable rules and subjective "taste" elements that make our code consistent and maintainable.
## Table of Contents
1. [General Principles](#general-principles)
2. [TypeScript Conventions](#typescript-conventions)
3. [Imports & Dependencies](#imports--dependencies)
4. [Functions & Methods](#functions--methods)
5. [React & Components](#react--components)
6. [Error Handling](#error-handling)
7. [Async/Await Patterns](#asyncawait-patterns)
8. [Whitespace & Formatting](#whitespace--formatting)
9. [Naming Conventions](#naming-conventions)
10. [Pattern Matching](#pattern-matching)
11. [Database & Prisma](#database--prisma)
12. [TRPC Patterns](#trpc-patterns)
---
## General Principles
- **Functional over Object-Oriented**: Prefer functional programming patterns over classes
- **Explicit over Implicit**: Be explicit about types, return values, and error cases
- **Early Returns**: Use guard clauses and early returns to reduce nesting
- **Immutability**: Favor `const` over `let`; avoid mutation where possible
---
## TypeScript Conventions
### Type Definitions
```typescript
// ✅ Prefer `type` over `interface`
type CreateDocumentOptions = {
templateId: number;
userId: number;
recipients: Recipient[];
};
// ❌ Avoid interfaces unless absolutely necessary
interface CreateDocumentOptions {
templateId: number;
}
```
### Type Imports
```typescript
// ✅ Use `type` keyword for type-only imports
import type { Document, Recipient } from '@prisma/client';
import { DocumentStatus } from '@prisma/client';
// Types in function signatures
export const findDocuments = async ({ userId, teamId }: FindDocumentsOptions) => {
// ...
};
```
### Inline Types for Function Parameters
```typescript
// ✅ Extract inline types to named types
type FinalRecipient = Pick<Recipient, 'name' | 'email' | 'role' | 'authOptions'> & {
templateRecipientId: number;
fields: Field[];
};
const finalRecipients: FinalRecipient[] = [];
```
---
## Imports & Dependencies
### Import Organization
Imports should be organized in the following order with blank lines between groups:
```typescript
// 1. React imports
import { useCallback, useEffect, useMemo } from 'react';
// 2. Third-party library imports (alphabetically)
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/react/macro';
import type { Document, Recipient } from '@prisma/client';
import { DocumentStatus, RecipientRole } from '@prisma/client';
import { match } from 'ts-pattern';
// 3. Internal package imports (from @documenso/*)
import { AppError } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
import { Button } from '@documenso/ui/primitives/button';
// 4. Relative imports
import { getTeamById } from '../team/get-team';
import type { FindResultResponse } from './types';
```
### Destructuring Imports
```typescript
// ✅ Destructure specific exports
// ✅ Use type imports for types
import type { Document } from '@prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
```
---
## Functions & Methods
### Arrow Functions
```typescript
// ✅ Always use arrow functions for functions
export const createDocument = async ({
userId,
title,
}: CreateDocumentOptions) => {
// ...
};
// ✅ Callbacks and handlers
const onSubmit = useCallback(async () => {
// ...
}, [dependencies]);
// ❌ Avoid regular function declarations
function createDocument() {
// ...
}
```
### Function Parameters
```typescript
// ✅ Use destructured object parameters for multiple params
export const findDocuments = async ({
userId,
teamId,
status = ExtendedDocumentStatus.ALL,
page = 1,
perPage = 10,
}: FindDocumentsOptions) => {
// ...
};
// ✅ Destructure on separate line when needed
const onFormSubmit = form.handleSubmit(onSubmit);
// ✅ Deconstruct nested properties explicitly
const { user } = ctx;
const { templateId } = input;
```
---
## React & Components
### Component Definition
```typescript
// ✅ Use const with arrow function
export const AddSignersFormPartial = ({
documentFlow,
recipients,
fields,
onSubmit,
}: AddSignersFormProps) => {
// ...
};
// ❌ Never use classes
class MyComponent extends React.Component {
// ...
}
```
### Hooks
```typescript
// ✅ Group related hooks together with blank line separation
const { _ } = useLingui();
const { toast } = useToast();
const { currentStep, totalSteps, previousStep } = useStep();
const form = useForm<TFormSchema>({
resolver: zodResolver(ZFormSchema),
defaultValues: {
// ...
},
});
```
### Event Handlers
```typescript
// ✅ Use arrow functions with descriptive names
const onFormSubmit = async () => {
await form.trigger();
// ...
};
const onFieldCopy = useCallback(
(event?: KeyboardEvent | null) => {
event?.preventDefault();
// ...
},
[dependencies],
);
// ✅ Inline handlers for simple operations
<Button onClick={() => setOpen(false)}>Close</Button>
```
### State Management
```typescript
// ✅ Descriptive state names with auxiliary verbs
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
// ✅ Complex state in single useState when related
const [coords, setCoords] = useState({
x: 0,
y: 0,
});
```
---
## Error Handling
### Try-Catch Blocks
```typescript
// ✅ Use try-catch for operations that might fail
try {
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
});
return {
status: 200,
body: document,
};
} catch (err) {
return {
status: 404,
body: {
message: 'Document not found',
},
};
}
```
### Throwing Errors
```typescript
// ✅ Use AppError for application errors
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Template not found',
});
// ✅ Use descriptive error messages
if (!template) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: `Template with ID ${templateId} not found`,
});
}
```
### Error Parsing on Frontend
```typescript
// ✅ Parse errors on the frontend
try {
await updateOrganisation({ organisationId, data });
} catch (err) {
const error = AppError.parseError(err);
console.error(error);
toast({
title: t`An error occurred`,
description: error.message,
variant: 'destructive',
});
}
```
---
## Async/Await Patterns
### Async Function Definitions
```typescript
// ✅ Mark async functions clearly
export const createDocument = async ({
userId,
title,
}: Options): Promise<Document> => {
// ...
};
// ✅ Use await for promises
const document = await prisma.document.create({ data });
// ✅ Use Promise.all for parallel operations
const [document, recipients] = await Promise.all([
getDocumentById({ documentId }),
getRecipientsForDocument({ documentId }),
]);
```
### Void for Fire-and-Forget
```typescript
// ✅ Use void for intentionally unwaited promises
void handleAutoSave();
// ✅ Or in event handlers
onClick={() => void onFormSubmit()}
```
---
## Whitespace & Formatting
### Blank Lines Between Concepts
```typescript
// ✅ Blank line after imports
import { prisma } from '@documenso/prisma';
export const findDocuments = async () => {
// ...
};
// ✅ Blank line between logical sections
const user = await prisma.user.findFirst({ where: { id: userId } });
let team = null;
if (teamId !== undefined) {
team = await getTeamById({ userId, teamId });
}
// ✅ Blank line before return statements
const result = await someOperation();
return result;
```
### Function/Method Spacing
```typescript
// ✅ No blank lines between chained methods in same operation
const documents = await prisma.document
.findMany({ where: { userId } })
.then((docs) => docs.map(maskTokens));
// ✅ Blank line between different operations
const document = await createDocument({ userId });
await sendDocument({ documentId: document.id });
return document;
```
### Object and Array Formatting
```typescript
// ✅ Multi-line when complex
const options = {
userId,
teamId,
status: ExtendedDocumentStatus.ALL,
page: 1,
};
// ✅ Single line when simple
const coords = { x: 0, y: 0 };
// ✅ Array items on separate lines when objects
const recipients = [
{
name: 'John',
email: 'john@example.com',
},
{
name: 'Jane',
email: 'jane@example.com',
},
];
```
---
## Naming Conventions
### Variables
```typescript
// ✅ camelCase for variables and functions
const documentId = 123;
const onSubmit = () => {};
// ✅ Descriptive names with auxiliary verbs for booleans
const isLoading = false;
const hasError = false;
const canEdit = true;
const shouldRender = true;
// ✅ Prefix with $ for DOM elements
const $page = document.querySelector('.page');
const $inputRef = useRef<HTMLInputElement>(null);
```
### Types and Schemas
```typescript
// ✅ PascalCase for types
type CreateDocumentOptions = {
userId: number;
};
// ✅ Prefix Zod schemas with Z
const ZCreateDocumentSchema = z.object({
title: z.string(),
});
// ✅ Prefix type from Zod schema with T
type TCreateDocumentSchema = z.infer<typeof ZCreateDocumentSchema>;
```
### Constants
```typescript
// ✅ UPPER_SNAKE_CASE for true constants
const DEFAULT_DOCUMENT_DATE_FORMAT = 'dd/MM/yyyy';
const MAX_FILE_SIZE = 1024 * 1024 * 5;
// ✅ camelCase for const variables that aren't "constants"
const userId = await getUserId();
```
### Functions
```typescript
// ✅ Verb-based names for functions
const createDocument = async () => {};
const findDocuments = async () => {};
const updateDocument = async () => {};
const deleteDocument = async () => {};
// ✅ On prefix for event handlers
const onSubmit = () => {};
const onClick = () => {};
const onFieldCopy = () => {}; // 'on' is also acceptable
```
### Clarity Over Brevity
```typescript
// ✅ Prefer descriptive names over abbreviations
const superLongMethodThatIsCorrect = () => {};
const recipientAuthenticationOptions = {};
const documentMetadata = {};
// ❌ Avoid abbreviations that sacrifice clarity
const supLongMethThatIsCorrect = () => {};
const recipAuthOpts = {};
const docMeta = {};
// ✅ Common abbreviations that are widely understood are acceptable
const userId = 123;
const htmlElement = document.querySelector('div');
const apiResponse = await fetch('/api');
```
---
## Pattern Matching
### Using ts-pattern
```typescript
import { match } from 'ts-pattern';
// ✅ Use match for complex conditionals
const result = match(status)
.with(ExtendedDocumentStatus.DRAFT, () => ({
status: 'draft',
}))
.with(ExtendedDocumentStatus.PENDING, () => ({
status: 'pending',
}))
.with(ExtendedDocumentStatus.COMPLETED, () => ({
status: 'completed',
}))
.exhaustive();
// ✅ Use .otherwise() for default case when not exhaustive
const value = match(type)
.with('text', () => 'Text field')
.with('number', () => 'Number field')
.otherwise(() => 'Unknown field');
```
---
## Database & Prisma
### Query Structure
```typescript
// ✅ Destructure commonly used fields
const { id, email, name } = user;
// ✅ Use select to limit returned fields
const user = await prisma.user.findFirst({
where: { id: userId },
select: {
id: true,
email: true,
name: true,
},
});
// ✅ Use include for relations
const document = await prisma.document.findFirst({
where: { id: documentId },
include: {
recipients: true,
fields: true,
},
});
```
### Transactions
```typescript
// ✅ Use transactions for related operations
return await prisma.$transaction(async (tx) => {
const document = await tx.document.create({ data });
await tx.field.createMany({ data: fieldsData });
await tx.documentAuditLog.create({ data: auditData });
return document;
});
```
### Where Clauses
```typescript
// ✅ Build complex where clauses separately
const whereClause: Prisma.DocumentWhereInput = {
AND: [
{ userId: user.id },
{ deletedAt: null },
{ status: { in: [DocumentStatus.DRAFT, DocumentStatus.PENDING] } },
],
};
const documents = await prisma.document.findMany({
where: whereClause,
});
```
---
## TRPC Patterns
### Router Structure
```typescript
// ✅ Destructure context and input at start
.query(async ({ input, ctx }) => {
const { teamId } = ctx;
const { templateId } = input;
ctx.logger.info({
input: { templateId },
});
return await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
});
});
```
### Request/Response Schemas
```typescript
// ✅ Name schemas clearly
const ZCreateDocumentRequestSchema = z.object({
title: z.string(),
recipients: z.array(ZRecipientSchema),
});
const ZCreateDocumentResponseSchema = z.object({
documentId: z.number(),
status: z.string(),
});
```
### Error Handling in TRPC
```typescript
// ✅ Catch and transform errors appropriately
try {
const result = await createDocument({ userId, data });
return result;
} catch (err) {
return AppError.toRestAPIError(err);
}
// ✅ Or throw AppError directly
if (!template) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Template not found',
});
}
```
---
## Additional Patterns
### Optional Chaining
```typescript
// ✅ Use optional chaining for potentially undefined values
const email = user?.email;
const recipientToken = recipient?.token ?? '';
// ✅ Use nullish coalescing for defaults
const pageSize = perPage ?? 10;
const status = documentStatus ?? DocumentStatus.DRAFT;
```
### Array Operations
```typescript
// ✅ Use functional array methods
const activeRecipients = recipients.filter((r) => r.signingStatus === 'SIGNED');
const recipientEmails = recipients.map((r) => r.email);
const hasSignedRecipients = recipients.some((r) => r.signingStatus === 'SIGNED');
// ✅ Use find instead of filter + [0]
const recipient = recipients.find((r) => r.id === recipientId);
```
### Conditional Rendering
```typescript
// ✅ Use && for conditional rendering
{isLoading && <Loader />}
// ✅ Use ternary for either/or
{isLoading ? <Loader /> : <Content />}
// ✅ Extract complex conditions to variables
const shouldShowAdvanced = isAdmin && hasPermission && !isDisabled;
{shouldShowAdvanced && <AdvancedSettings />}
```
---
## When in Doubt
- **Consistency**: Follow the patterns you see in similar files
- **Readability**: Favor code that's easy to read over clever one-liners
- **Explicitness**: Be explicit rather than implicit
- **Whitespace**: Use blank lines to separate logical sections
- **Early Returns**: Use guard clauses to reduce nesting
- **Functional**: Prefer functional patterns over imperative ones

View File

@ -1,3 +1,6 @@
> 🚨 🚨 🚨
> Documenso 2.0.0 is live on Product Hunt 🎉 <a href="https://documen.so/launch" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">Join us to celebrate the best Documenso yet 🪩</a>
<img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo"> <img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo">
<p align="center" style="margin-top: 20px"> <p align="center" style="margin-top: 20px">

View File

@ -10,13 +10,24 @@ For the digital signature of your documents you need a signing certificate in .p
`openssl req -new -x509 -key private.key -out certificate.crt -days 365` `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. 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 command to do this: 3. Combine the private key and the self-signed certificate to create the p12 certificate. You can run the following commands to do this:
`openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt` ```bash
# Set certificate password securely (won't appear in command history)
read -s -p "Enter certificate password: " CERT_PASS
echo
4. You will be prompted to enter a password for the p12 file. Choose a strong password and remember it, as you will need it to use the certificate (**can be empty for dev certificates**) # 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) 5. Place the certificate `/apps/remix/resources/certificate.p12` (If the path does not exist, it needs to be created)

View File

@ -44,7 +44,7 @@ Before using the `EmbedCreateDocument` component, you'll need to obtain a presig
You can create a presign token by making a request to: You can create a presign token by making a request to:
``` ```
POST /api/v2-beta/embedding/create-presign-token POST /api/v2/embedding/create-presign-token
``` ```
This API endpoint requires authentication with your Documenso API key. The token has a default expiration of 1 hour, but you can customize this duration based on your security requirements. This API endpoint requires authentication with your Documenso API key. The token has a default expiration of 1 hour, but you can customize this duration based on your security requirements.
@ -134,7 +134,7 @@ import { unstable_EmbedCreateDocument as EmbedCreateDocument } from '@documenso/
function DocumentCreator() { function DocumentCreator() {
// In a real application, you would fetch this token from your backend // In a real application, you would fetch this token from your backend
// using your API key at /api/v2-beta/embedding/create-presign-token // using your API key at /api/v2/embedding/create-presign-token
const presignToken = 'YOUR_PRESIGN_TOKEN'; const presignToken = 'YOUR_PRESIGN_TOKEN';
const [documentId, setDocumentId] = useState<number | null>(null); const [documentId, setDocumentId] = useState<number | null>(null);

View File

@ -9,11 +9,11 @@ Documenso uses API keys for authentication. An API key is a unique token that is
## Creating an API Key ## Creating an API Key
To create an API key, navigate to the user settings page. Click on your avatar in the top right corner of the dashboard and select "**[User settings](https://app.documenso.com/settings)**" from the dropdown menu. To create an API key, navigate to the user settings page. Click on your avatar in the top right corner of the dashboard and select "Team Settings" from the dropdown menu.
![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/public-api-images/documenso-user-dropdown-menu.webp) ![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/public-api-images/documenso-user-dropdown-menu.webp)
Once you're on the user settings page, navigate to the "**[API Tokens](https://app.documenso.com/settings/tokens)**" tab. The "API Token" page lists your existing keys and enables you to create new ones. Once you're on the settings page, navigate to the **API Tokens** tab. This page lists your existing keys and enables you to create new ones.
![A screenshot of the Documenso's user settings page that shows the API Tokens page](/public-api-images/api-tokens-page-documenso.webp) ![A screenshot of the Documenso's user settings page that shows the API Tokens page](/public-api-images/api-tokens-page-documenso.webp)
@ -37,31 +37,17 @@ You must include the API key in the `Authorization` request header to authentica
Here's a sample API request using cURL: Here's a sample API request using cURL:
```bash ```bash
curl --location 'https://app.documenso.com/api/v1/documents?page=1&perPage=1' \ curl --location 'https://app.documenso.com/api/v2/envelope/create' \
--header 'Authorization: api_xxxxxxxxxxxxxxxx' --header 'Authorization: api_xxxxxxxxxxxxxxxx' \
--form 'payload={ "title": "Some Title", "type": "DOCUMENT" }'
``` ```
Here's a sample response from the API based on the above cURL request: Here's a sample response from the API based on the above cURL request:
```json ```json
{ {
"documents": [ "id": "envelope_xxxxxxxxxxxxx"
{
"id": 11,
"userId": 2,
"teamId": null,
"title": "documenso",
"status": "PENDING",
"documentDataId": "ab2ecm1npk11rt5sp398waf7h",
"createdAt": "2024-04-25T11:05:18.420Z",
"updatedAt": "2024-04-25T11:05:36.328Z",
"completedAt": null
}
],
"totalPages": 1
} }
``` ```
![A screenshot of a cURL request to the Documenso public API with the API key in the Authorization header](/public-api-images/documenso-api-authorization.webp) The API key has access to your account and all its resources. Please keep it secure and do not share it with others. If you suspect your key has been compromised, you can revoke it from the same page you created it from.
The API key has access to your account and all its resources. Please keep it secure and do not share it with others. If you suspect your key has been compromised, you can revoke it from the "API Tokens" page in your user settings.

View File

@ -9,19 +9,13 @@ import { Callout, Steps } from 'nextra/components';
Documenso provides a public REST API enabling you to interact with your documents programmatically. The API exposes various HTTP endpoints that allow you to perform operations such as: Documenso provides a public REST API enabling you to interact with your documents programmatically. The API exposes various HTTP endpoints that allow you to perform operations such as:
- retrieving, uploading, deleting, and sending documents for signing - Retrieving, uploading, deleting, and sending documents for signing
- creating, updating, and deleting recipients - Creating, updating, and deleting recipients
- creating, updating, and deleting document fields - Creating, updating, and deleting document fields
The documentation walks you through creating API keys and using them to authenticate your API requests. You'll also learn about the available endpoints, request and response formats, and how to use the API. The documentation walks you through creating API keys and using them to authenticate your API requests. You'll also learn about the available endpoints, request and response formats, and how to use the API.
## API V1 - Stable ## API V2 - Stable
Check out the [API V1 documentation](https://app.documenso.com/api/v1/openapi) for details about the API endpoints, request parameters, response formats, and authentication methods.
## API V2 - Beta
<Callout type="warning">API V2 is currently beta, and will be subject to breaking changes</Callout>
Check out the [API V2 documentation](https://documen.so/api-v2-docs) for details about the API endpoints, request parameters, response formats, and authentication methods. Check out the [API V2 documentation](https://documen.so/api-v2-docs) for details about the API endpoints, request parameters, response formats, and authentication methods.
@ -32,18 +26,15 @@ Our new API V2 supports the following typed SDKs:
- [Go](https://github.com/documenso/sdk-go) - [Go](https://github.com/documenso/sdk-go)
<Callout type="info"> <Callout type="info">
For the staging API, please use the following base URL: For the staging API, please use the following base URL: `https://stg-app.documenso.com/api/v2/`
`https://stg-app.documenso.com/api/v2-beta/`
</Callout> </Callout>
🚀 [V2 Announcement](https://documen.so/sdk-blog) ## API V1 - Deprecated
Check out the [API V1 documentation](https://app.documenso.com/api/v1/openapi) for details about the API endpoints, request parameters, response formats, and authentication methods.
📖 [Documentation](https://documen.so/api-v2-docs) 📖 [Documentation](https://documen.so/api-v2-docs)
💬 [Leave Feedback](https://documen.so/sdk-feedback)
🔔 [Breaking Changes](https://documen.so/sdk-breaking)
## Availability ## Availability
The API is available to individual users, teams and higher plans. [Fair Use](https://documen.so/fair) applies. The API is available to individual users, teams and higher plans. [Fair Use](https://documen.so/fair) applies.

View File

@ -7,550 +7,246 @@ import { Callout, Steps } from 'nextra/components';
# API Reference # API Reference
The Swagger UI for the API is available at [/api/v1/openapi](https://app.documenso.com/api/v1/openapi). This page provides detailed information about the API endpoints, request and response formats, and authentication requirements. On this page we will refer to both Documents and Templates as Envelopes for convenience, unless otherwise specified.
## Upload a Document
Uploading a document to your Documenso account requires a two-step process.
<Steps>
### Create Document
First, you need to make a `POST` request to the `/api/v1/documents` endpoint, which takes a JSON payload with the following fields:
```json
{
"title": "string",
"externalId": "string",
"recipients": [
{
"name": "string",
"email": "user@example.com",
"role": "SIGNER",
"signingOrder": 0
}
],
"meta": {
"subject": "string",
"message": "string",
"timezone": "Etc/UTC",
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "ACCOUNT"
},
"formValues": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
```
- `title` _(required)_ - This represents the document's title.
- `externalId` - This is an optional field that you can use to store an external identifier for the document. This can be useful for tracking the document in your system.
- `recipients` _(required)_ - This is an array of recipient objects. Each recipient object has the following fields:
- `name` - The name of the recipient.
- `email` - The email address of the recipient.
- `role` - The role of the recipient. See the [available roles](/users/signing-documents#roles).
- `signingOrder` - The order in which the recipient should sign the document. This is an integer value starting from 0.
- `meta` - This object contains additional metadata for the document. It has the following fields:
- `subject` - The subject of the email that will be sent to the recipients.
- `message` - The message of the email that will be sent to the recipients.
- `timezone` - The timezone in which the document should be signed.
- `dateFormat` - The date format that should be used in the document.
- `redirectUrl` - The URL to which the user should be redirected after signing the document.
- `signingOrder` - The signing order for the document. This can be either `SEQUENTIAL` or `PARALLEL`.
- `authOptions` - This object contains authentication options for the document. It has the following fields:
- `globalAccessAuth` - The authentication level required to access the document. This can be either `ACCOUNT` or `null`.
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to access it.
- The document can be accessed without a Documenso account if it's set to `null`.
- `globalActionAuth` - The authentication level required to perform actions on the document. This can be `ACCOUNT`, `PASSKEY`, `TWO_FACTOR_AUTH`, or `null`.
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to perform actions on the document.
- If it's set to `PASSKEY`, all recipients must have the passkey active to perform actions on the document.
- If it's set to `TWO_FACTOR_AUTH`, all recipients must have the two-factor authentication active to perform actions on the document.
- If it's set to `null`, all the recipients can perform actions on the document without any authentication.
- `formValues` - This object contains additional form values for the document. This property only works with native PDF fields and accepts three types: number, text and boolean.
<Callout type="info"> <Callout type="info">
The `globalActionAuth` property is only available for Enterprise accounts. This page is used to demonstrate some of the endpoints and how to use them. For the full API
reference, please see the [API Reference](https://openapi.documenso.com/).
</Callout> </Callout>
Here's an example of the JSON payload for uploading a document: ## Get Envelope
```json For the vast majority of use cases, you will need to retrieve the envelope to get details for further operations.
{
"title": "my-document.pdf", The main details you generally want to extract are the following:
"externalId": "12345",
"recipients": [ - **Recipients** - The individuals who will be signing the document
{ - **Fields** - The fields the user will sign
"name": "Alex Blake", - **Items** - The PDF files the user will see and sign
"email": "alexblake@email.com",
"role": "SIGNER", This is done by doing a the following GET [request](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/create)
"signingOrder": 1
}, ```sh
{ curl -X GET "https://app.documenso.com/api/v2/envelope/envelope_xxxxxxxxxxxxx" \
"name": "Ash Drew", -H "Authorization: api_xxxxxxxxxxxxxx"
"email": "ashdrew@email.com",
"role": "SIGNER",
"signingOrder": 0
}
],
"meta": {
"subject": "Sign the document",
"message": "Hey there, please sign this document.",
"timezone": "Europe/London",
"dateFormat": "Day, Month Year",
"redirectUrl": "https://mysite.com/welcome",
"signingOrder": "SEQUENTIAL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "PASSKEY"
}
}
``` ```
### Upload to S3
A successful API call to the `/api/v1/documents` endpoint returns a JSON response containing the upload URL, document ID, and recipient information.
The upload URL is a pre-signed S3 URL that you can use to upload the document to the Documenso (or your) S3 bucket. You need to make a `PUT` request to this URL to upload the document.
```json
{
"uploadUrl": "https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject",
"documentId": 51,
"recipients": [
{
"recipientId": 11,
"name": "Alex Blake",
"email": "alexblake@email.com",
"token": "<unique-signer-token>",
"role": "SIGNER",
"signingOrder": 1,
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
},
{
"recipientId": 12,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"token": "<unique-signer-token>",
"role": "SIGNER",
"signingOrder": 0,
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
}
]
}
```
When you make the `PUT` request to the pre-signed URL, you need to include the document file you want to upload. The image below shows how to upload a document to the S3 bucket via Postman.
![Upload document to S3](/api-reference/upload-document-to-s3.webp)
Here's an example of how to upload a document using cURL:
```bash
curl --location --request PUT 'https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject' \
--form '=@"/Users/my-user/Documents/documenso.pdf"'
```
Once the document is successfully uploaded, you can access it in your Documenso account dashboard. The screenshot below shows the document that was uploaded via the API.
![Uploaded Document](/api-reference/document-uploaded-to-documenso-via-api.webp)
</Steps>
## Generate Document From Template
Documenso allows you to generate documents from templates. This is useful when you have a standard document format you want to reuse.
The API endpoint for generating a document from a template is `/api/v1/templates/{templateId}/generate-document`, and it takes a JSON payload with the following fields:
```json
{
"title": "string",
"externalId": "string",
"recipients": [
{
"id": 0,
"name": "string",
"email": "user@example.com",
"signingOrder": 0
}
],
"meta": {
"subject": "string",
"message": "string",
"timezone": "string",
"dateFormat": "string",
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "ACCOUNT"
},
"formValues": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
```
The JSON payload is identical to the payload for uploading a document, so you can read more about the fields in the [Create Document](/developers/public-api/reference#create-document) step. For this API endpoint, the `recipients` property is required.
<Steps>
### Grab the Template ID
The first step is to retrieve the template ID from the Documenso dashboard. You can find the template ID in the URL by navigating to the template details page.
![Template ID](/api-reference/documenso-template-id.webp)
In this case, the template ID is "99999".
### Retrieve the Recipient(s) ID(s)
Once you have the template ID, the next step involves retrieving the ID(s) of the recipient(s) from the template. You can do this by making a GET request to `/api/v1/templates/{template-id}`.
A successful response looks as follows: A successful response looks as follows:
```json ```json
{ {
"id": 0, "internalVersion": 2,
"externalId": "string", "type": "TEMPLATE",
"type": "PUBLIC",
"title": "string",
"userId": 0,
"teamId": 0,
"templateDocumentDataId": "string",
"createdAt": "2024-10-11T08:46:58.247Z",
"updatedAt": "2024-10-11T08:46:58.247Z",
"templateMeta": {
"id": "string",
"subject": "string",
"message": "string",
"timezone": "string",
"dateFormat": "string",
"templateId": 0,
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"directLink": {
"token": "string",
"enabled": true
},
"templateDocumentData": {
"id": "string",
"type": "S3_PATH",
"data": "string"
},
"Field": [
{
"id": 0,
"recipientId": 0,
"type": "SIGNATURE",
"page": 0,
"positionX": "string",
"positionY": "string",
"width": "string",
"height": "string"
}
],
"Recipient": [
{
"id": 0,
"email": "user@example.com",
"name": "string",
"signingOrder": 0,
"authOptions": "string",
"role": "CC"
}
]
}
```
You'll need the recipient(s) ID(s) for the next step.
### Generate the Document
To generate a document from the template, you need to make a POST request to the `/api/v1/templates/{template-id}/generate-document` endpoint.
At the minimum, you must provide the `recipients` array in the JSON payload. Here's an example of the JSON payload:
```json
{
"recipients": [
{
"id": 0,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"signingOrder": 0
}
]
}
```
Filling the `recipients` array with the corresponding recipient for each template placeholder recipient is recommended. For example, if the template has two placeholders, you should provide at least two recipients in the `recipients` array. Otherwise, the document will be sent to inexistent recipients such as `<recipient.1@documenso.com>`. However, the recipients can always be edited via the API or the web app.
A successful response will contain the document ID and recipient(s) information.
```json
{
"documentId": 999,
"recipients": [
{
"recipientId": 0,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"token": "<signing-token>",
"role": "SIGNER",
"signingOrder": null,
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
}
]
}
```
You can now access the document in your Documenso account dashboard. The screenshot below shows the document that was generated from the template.
![Generated Document](/api-reference/document-generated-from-template.webp)
</Steps>
## Add Fields to Document
The API allows you to add fields to a document via the `/api/v1/documents/{documentId}/fields` endpoint. This is useful when you want to add fields to a document before sending it to recipients.
To add fields to a document, you need to make a `POST` request with a JSON payload containing the field(s) information.
```json
{
"recipientId": 0,
"type": "SIGNATURE",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0,
"fieldMeta": {
"label": "string",
"placeholder": "string",
"required": true,
"readOnly": true,
"type": "text",
"text": "string",
"characterLimit": 0
}
}
// or
[
{
"recipientId": 0,
"type": "SIGNATURE",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0
},
{
"recipientId": 0,
"type": "TEXT",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0,
"fieldMeta": {
"label": "string",
"placeholder": "string",
"required": true,
"readOnly": true,
"type": "text",
"text": "string",
"characterLimit": 0
}
}
]
```
<Callout type="info">This endpoint accepts either one field or an array of fields.</Callout>
Before adding fields to a document, you need each recipient's ID. If the document already has recipients, you can query the document to retrieve the recipient's details. If the document has no recipients, you need to add a recipient via the UI or API before adding a field.
<Steps>
### Retrieve the Recipient(s) ID(s)
Perform a `GET` request to the `/api/v1/documents/{id}` to retrieve the details of a specific document, including the recipient's information.
An example response would look like this:
```json
{
"id": 137,
"externalId": null,
"userId": 3,
"teamId": null,
"title": "documenso.pdf",
"status": "DRAFT", "status": "DRAFT",
"documentDataId": "<document-data-id>", "source": "TEMPLATE",
"createdAt": "2024-10-11T12:29:12.725Z", "visibility": "EVERYONE",
"updatedAt": "2024-10-11T12:29:12.725Z", "templateType": "PRIVATE",
"id": "envelope_xxxxxxxxxxxxxxxx",
"secondaryId": "template_9",
"externalId": null,
"createdAt": "2025-11-12T11:36:38.391Z",
"updatedAt": "2025-11-12T11:36:55.648Z",
"completedAt": null, "completedAt": null,
"deletedAt": null,
"title": "Title",
"recipients": [ "recipients": [
{ {
"id": 55, "envelopeId": "envelope_xxxxxxxxxxxxxxxx",
"documentId": 137,
"email": "ashdrew@email.com",
"name": "Ash Drew",
"role": "SIGNER", "role": "SIGNER",
"signingOrder": null,
"token": "<signing-token>",
"signedAt": null,
"readStatus": "NOT_OPENED", "readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED", "signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT", "sendStatus": "NOT_SENT",
"signingUrl": "https://app.documenso.com/sign/<signing-token>" "id": 0,
"email": "recipient+1@documenso.com",
"name": "",
"token": "cxH2Ss79Hj94M1U3PxRAG",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": [],
"actionAuth": []
},
"signingOrder": 1,
"rejectionReason": null
} }
] ]
} "envelopeItems": [
```
From this response, you'll only need the recipient ID, which is `55` in this case.
### (OR) Add a Recipient
If the document doesn't already have recipient(s), you can add recipient(s) via the API. Make a `POST` request to the `/api/v1/documents/{documentId}/recipients` endpoint with the recipient information. This endpoint takes the following JSON payload:
```json
{
"name": "string",
"email": "user@example.com",
"role": "SIGNER",
"signingOrder": 0,
"authOptions": {
"actionAuth": "ACCOUNT"
}
}
```
<Callout type="info">The `authOptions` property is only available for Enterprise accounts.</Callout>
Here's an example of the JSON payload for adding a recipient:
```json
{
"name": "Ash Drew",
"email": "ashdrew@email.com",
"role": "SIGNER",
"signingOrder": 0
}
```
A successful request will return a JSON response with the newly added recipient. You can now use the recipient ID to add fields to the document.
### Add Field(s)
Now you can make a `POST` request to the `/api/v1/documents/{documentId}/fields` endpoint with the field(s) information. Here's an example:
```json
[
{ {
"recipientId": 55, "envelopeId": "envelope_xxxxxxxxxxxxxxxx",
"id": "envelope_item_xxxxxxxxxxxxxxxxx",
"title": "FileOne",
"order": 1
}
],
// Other attributes
}
```
## Create Envelope
This example request will create a document with the following:
- Two PDF files
- Two recipients with one field each
You will need to:
- Replace api_xxxxxxxxxxxxxx with your API key
- Replace the file paths with the actual paths to your files
Note that the identifier on a field is linking to the files you upload. It can either be the index of the file or the filename. In this example it uses the index of the file.
```sh
curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
-H "Authorization: api_xxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"type": "DOCUMENT",
"title": "Envelope Full Field Test",
"recipients": [
{
"email": "recipient+1@example.com",
"name": "Recipient One",
"role": "SIGNER",
"fields": [
{
"identifier": 0,
"type": "SIGNATURE", "type": "SIGNATURE",
"pageNumber": 1, "page": 1,
"pageX": 50, "positionX": 0,
"pageY": 20, "positionY": 0,
"pageWidth": 25, "width": 50,
"pageHeight": 5 "height": 50
}
]
}, },
{ {
"recipientId": 55, "email": "recipient-2@example.com",
"type": "TEXT", "name": "Recipient Two",
"pageNumber": 1, "role": "SIGNER",
"pageX": 20, "fields": [
"pageY": 50, {
"pageWidth": 30, "identifier": 1,
"pageHeight": 7.5, "type": "SIGNATURE",
"fieldMeta": { "page": 1,
"label": "Address", "positionX": 0,
"placeholder": "32 New York Street, 41241", "positionY": 0,
"required": true, "width": 50,
"readOnly": false, "height": 50
"type": "text",
"text": "32 New York Street, 41241",
"characterLimit": 40
} }
]
} }
] ]
}' \
-F "files=@./field-font-alignment.pdf;type=application/pdf" \
-F "files=@./field-meta.pdf;type=application/pdf"
``` ```
<Callout type="info"> <Callout type="info">
The `text` field represents the default value of the field. If the user doesn't provide any other See the full request and response formats for the create endpoint
value, this is the value that will be used to sign the field. [here](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/create)
</Callout> </Callout>
<Callout type="warning"> ## Use Template
It's important to pass the `type` in the `fieldMeta` property for the advanced fields. [Read more
here](#a-note-on-advanced-fields) Documenso allows you to generate documents from templates. This is useful when you have a standard document format you want to reuse.
The API endpoint for generating a document from a template is `/api/v2/envelopes/use`, and it takes a JSON payload with the following fields:
<Callout type="info">
See the full request and response formats for the use endpoint
[here](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/use)
</Callout> </Callout>
A successful request will return a JSON response with the newly added fields. The image below illustrates the fields added to the document via the API. <Steps>
![A screenshot of the document in the Documenso editor](/api-reference/fields-added-via-api.webp) ### Grab the Template ID
</Steps> The first step is to retrieve the ID of the template you want to use. This can be done via the API or from the Documenso UI.
#### A Note on Advanced Fields You can get the ID by navigating to the template page and looking at the URL. The ID is the last part of the URL which looks like this "envelope_xxxxxxxx"
The advanced fields are: text, checkbox, radio, number, and select. Whenever you append any of these advanced fields to a document, you need to pass the `type` in the `fieldMeta` property: ### Retrieve the Recipient(s) ID(s)
```json Once you have the template ID, you will want to retrieve the full template so you can see the recipients.
...
"fieldMeta": { This is optional, and you only will want to do this if you want to modify the recipient details prior to creating the new document.
"type": "text",
} See the [Get Envelope](#get-envelope) section for more details on how to retrieve the envelope.
...
### Generate the Document
To generate a document from the template, you need to make a POST request to the [`/api/v2/envelope/use`](https://openapi.documenso.com/reference#tag/envelope/POST/envelope/use) endpoint.
You will need to replace the following:
- `api_xxxxxxxxxxxxxx` with your API key
- `envelope_xxxxxxxxxxxxxxx` with the template ID
- `RECIPIENT_ID_HERE` with the recipient ID you want to modify prior to sending
- `RECIPIENT_EMAIL_HERE` with the new recipient email you want to use
- `RECIPIENT_NAME_HERE` with the new recipient name you want to use
If you want to send the document immediately, you can add the `distributeDocument` parameter to the payload.
```sh
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
-H "Authorization: api_xxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"envelopeId": "envelope_xxxxxxxxxxxxxxx",
"recipients": [
{
"id": RECIPIENT_ID_HERE,
"email": "RECIPIENT_EMAIL_HERE",
"name": "RECIPIENT_NAME_HERE"
}
]
}'
``` ```
Replace the `text` value with the corresponding field type: #### Generate the Document with Custom Files
- For the `TEXT` field it should be `text`. You can also generate a document from a template using custom PDF files to replace the template's default PDF files.
- For the `CHECKBOX` field it should be `checkbox`.
- For the `RADIO` field it should be `radio`.
- For the `NUMBER` field it should be `number`.
- For the `SELECT` field it should be `select`. (check this before merge)
You must pass this property at all times, even if you don't need to set any other properties. If you don't, the endpoint will throw an error. To do this, you will need to grab the `envelopeItemId` of the item you want to replace, this is found in the GET request in the previous step.
## Pre-fill Fields On Document Creation You will need to replace the following:
- `CUSTOM_FILE_NAME_HERE.pdf` with the custom file name
- `envelope_item_xxxxxxxxxxxxxxxxx` with the `envelopeItemId` of the item you want to replace
```sh
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
-H "Authorization: api_xxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"envelopeId": "envelope_xxxxxxxxxxxxxxx",
"recipients": [
{
"id": RECIPIENT_ID_HERE,
"email": "RECIPIENT_EMAIL_HERE",
"name": "RECIPIENT_NAME_HERE"
}
],
"customDocumentData": [
{
"identifier": "CUSTOM_FILE_NAME_HERE.pdf",
"envelopeItemId": "envelope_item_xxxxxxxxxxxxxxxxx"
}
]
}' \
-F "files=@./CUSTOM_FILE_NAME_HERE.pdf;type=application/pdf"
```
You can now access the document in your Documenso account dashboard.
#### Pre-fill Fields On Document Creation
The API allows you to pre-fill fields on document creation. This is useful when you want to create a document from an existing template and pre-fill the fields with specific values. The API allows you to pre-fill fields on document creation. This is useful when you want to create a document from an existing template and pre-fill the fields with specific values.
To pre-fill a field, you need to make a `POST` request to the `/api/v1/templates/{templateId}/generate-document` endpoint with the field information. Here's an example:
```json ```json
{ {
"title": "my-document.pdf",
"recipients": [
{
"id": 3,
"name": "Example User",
"email": "example@documenso.com",
"signingOrder": 1,
"role": "SIGNER"
}
],
"prefillFields": [ "prefillFields": [
{ {
"id": 21, "id": 21,
@ -574,51 +270,96 @@ To pre-fill a field, you need to make a `POST` request to the `/api/v1/templates
"value": ["option-1", "option-2"] "value": ["option-1", "option-2"]
} }
] ]
// Other payload properties...
} }
``` ```
Check out the endpoint in the [API V1 documentation](https://app.documenso.com/api/v1/openapi#:~:text=/%7BtemplateId%7D/-,generate,-%2Ddocument). </Steps>
### API V2 ## Add Recipients
For API V2, you need to make a `POST` request to the `/api/v2-beta/template/use` endpoint with the field(s) information. Here's an example: The API allows you to add recipients to an envelope via the [`/api/v2/envelope/recipient/create-many`](https://openapi.documenso.com/reference#tag/envelope-recipients/POST/envelope/recipient/create-many) endpoint.
```json The following is an example of a request which creates 2 new recipients on the envelope.
{
"templateId": 111, ```sh
"recipients": [ curl https://app.documenso.com/api/v2/envelope/recipient/create-many \
--request POST \
--header 'Authorization: api_xxxxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"envelopeId": "envelope_brxkyaxywuxkyeuv",
"data": [
{ {
"id": 3, "name": "Recipient Name",
"name": "Example User",
"email": "example@documenso.com", "email": "example@documenso.com",
"signingOrder": 1,
"role": "SIGNER" "role": "SIGNER"
}
],
"prefillFields": [
{
"id": 21,
"type": "text",
"label": "my-label",
"placeholder": "my-placeholder",
"value": "my-value"
}, },
{ {
"id": 22, "name": "Recipient Two",
"type": "number", "email": "example+2@documenso.com",
"label": "my-label", "role": "APPROVER"
"placeholder": "my-placeholder",
"value": "123"
},
{
"id": 23,
"type": "checkbox",
"label": "my-label",
"placeholder": "my-placeholder",
"value": ["option-1", "option-2"]
} }
] ]
} }'
``` ```
Check out the endpoint in the [API V2 documentation](https://openapi.documenso.com/reference#tag/template/POST/template/use). ## Add Fields
The API allows you to add fields to an envelope via the [`/api/v2/envelope/field/create-many`](https://openapi.documenso.com/reference#tag/envelope-fields/POST/envelope/field/create-many) endpoint.
Before adding fields to an envelope, you will need the following details:
- **Envelope ID** - Which envelope the field will be added to
- **Recipient ID** - Which recipient the field will be added to
- **Envelope Item ID** - Which file the field will be added to. If blank, the field will be added to the first file.
See the [Get Envelope](#get-envelope) section for more details on how to retrieve these details.
The following is an example of a request which creates 2 new fields on the first page of the envelope.
Note that width, height, positionX and positionY are percentage numbers between 0 and 100, which scale the field relative to the size of the PDF.
```sh
curl https://app.documenso.com/api/v2/envelope/field/create-many \
--request POST \
--header 'Authorization: api_xxxxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"envelopeId": "envelope_xxxxxxxxxx",
"data": [
{
"recipientId": recipient_id_here,
"envelopeItemId": "envelope_item_id_here",
"type": "TEXT",
"page": 1,
"positionX": 0,
"positionY": 0,
"width": 50,
"height": 50,
"fieldMeta": {
"type": "text",
"label": "First test field"
}
},
{
"recipientId": recipient_id_here,
"envelopeItemId": "envelope_item_id_here",
"type": "TEXT",
"page": 1,
"positionX": 50,
"positionY": 50,
"width": 50,
"height": 50,
"fieldMeta": {
"type": "text",
"label": "Second test field"
}
}
]
}'
```
Field meta allows you to further configure fields, for example it will allow you to add multiple items for checkboxes or radios.
A successful request will return a JSON response with the newly added fields.

View File

@ -9,9 +9,9 @@ import { Callout } from 'nextra/components';
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. 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.
<Callout type="info">The current version of the API is `v1`.</Callout> <Callout type="info">The current version of the API is `v2`.</Callout>
The API version is specified in the URL. For example, the base URL for the `v1` API is `https://app.documenso.com/api/v1`. The API version is specified in the URL. For example, the base URL for the `v2` API is `https://app.documenso.com/api/v2`.
We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes. We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes.

View File

@ -119,16 +119,89 @@ NEXT_PRIVATE_SMTP_USERNAME="<your-username>"
NEXT_PRIVATE_SMTP_PASSWORD="<your-password>" NEXT_PRIVATE_SMTP_PASSWORD="<your-password>"
``` ```
### Update the Volume Binding ### Set Up Your Signing Certificate
The `cert.p12` file is required to sign and encrypt documents, so you must provide your key file. Update the volume binding in the `compose.yml` file to point to your key file: <Callout type="warning">
This is the most common source of issues for self-hosters. Please follow these steps carefully.
</Callout>
```yaml The `cert.p12` file is required to sign and encrypt documents. You have three options:
volumes:
- /path/to/your/keyfile.p12:/opt/documenso/cert.p12
```
After updating the volume binding, save the `compose.yml` file and run the following command to start the containers: #### Option A: Generate Certificate Inside Container (Recommended)
This method avoids file permission issues by creating the certificate directly inside the Docker container:
1. Start your containers:
```bash
docker-compose up -d
```
2. Set certificate password securely and generate certificate inside the container:
```bash
# Set certificate password securely (won't appear in command history)
read -s -p "Enter certificate password: " CERT_PASS
echo
# Generate certificate inside container using environment variable
docker exec -e CERT_PASS="$CERT_PASS" -it documenso-production-documenso-1 bash -c "
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /tmp/private.key \
-out /tmp/certificate.crt \
-subj '/C=US/ST=State/L=City/O=Organization/CN=localhost' && \
openssl pkcs12 -export -out /app/certs/cert.p12 \
-inkey /tmp/private.key -in /tmp/certificate.crt \
-passout env:CERT_PASS && \
rm /tmp/private.key /tmp/certificate.crt
"
```
3. Add the certificate passphrase to your `.env` file:
```bash
NEXT_PRIVATE_SIGNING_PASSPHRASE="your_password_here"
```
4. Restart the container to apply changes:
```bash
docker-compose restart documenso
```
#### Option B: Use an Existing Certificate File
If you have an existing `.p12` certificate file:
1. **Place your certificate file** in an accessible location on your host system
2. **Set proper permissions:**
```bash
# Make sure the certificate is readable
chmod 644 /path/to/your/cert.p12
# For Docker, ensure proper ownership
chown 1001:1001 /path/to/your/cert.p12
```
3. **Update the volume binding** in the `compose.yml` file:
```yaml
volumes:
- /path/to/your/cert.p12:/opt/documenso/cert.p12:ro
```
4. **Add certificate configuration** to your `.env` file:
```bash
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
NEXT_PRIVATE_SIGNING_PASSPHRASE=your_certificate_password
```
<Callout type="warning">
Your certificate MUST have a password. Certificates without passwords will cause "Failed to get
private key bags" errors.
</Callout>
After setting up your certificate, save the `compose.yml` file and run the following command to start the containers:
```bash ```bash
docker-compose --env-file ./.env up -d docker-compose --env-file ./.env up -d

View File

@ -27,3 +27,33 @@ NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=<your-client-secret>
``` ```
Finally verify the signing in with Google works by signing in with your Google account and checking the email address in your profile. Finally verify the signing in with Google works by signing in with your Google account and checking the email address in your profile.
## Microsoft OAuth (Azure AD)
To use Microsoft OAuth, you will need to create an Azure AD application registration in the Microsoft Azure portal. This will allow users to sign in with their Microsoft accounts.
### Create and configure a new Azure AD application
1. Go to the [Azure Portal](https://portal.azure.com/)
2. Navigate to **Azure Active Directory** (or **Microsoft Entra ID** in newer Azure portals)
3. In the left sidebar, click **App registrations**
4. Click **New registration**
5. Enter a name for your application (e.g., "Documenso")
6. Under **Supported account types**, select **Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)** to allow any Microsoft account to sign in
7. Under **Redirect URI**, select **Web** and enter: `https://<documenso-domain>/api/auth/callback/microsoft`
8. Click **Register**
### Configure the application
1. After registration, you'll be taken to the app's overview page
2. Copy the **Application (client) ID** - this will be your `NEXT_PRIVATE_MICROSOFT_CLIENT_ID`
3. In the left sidebar, click **Certificates & secrets**
4. Under **Client secrets**, click **New client secret**
5. Add a description and select an expiration period
6. Click **Add** and copy the **Value** (not the Secret ID) - this will be your `NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET`
7. In the Documenso environment variables, set the following:
```
NEXT_PRIVATE_MICROSOFT_CLIENT_ID=<your-application-client-id>
NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET=<your-client-secret-value>
```

View File

@ -19,13 +19,13 @@ device, and other FDA-regulated industries.
- [x] User Access Management - [x] User Access Management
- [x] Quality Assurance Documentation - [x] Quality Assurance Documentation
## SOC/ SOC II ## SOC 2
<Callout type="warning" emoji=""> <Callout type="info" emoji="">
Status: [Planned](https://github.com/documenso/backlog/issues/24) Status: [Compliant](https://documen.so/trust)
</Callout> </Callout>
SOC II is a framework for managing and auditing the security, availability, processing integrity, confidentiality, SOC 2 is a framework for managing and auditing the security, availability, processing integrity, confidentiality,
and data privacy in cloud and IT service organizations, established by the American Institute of Certified and data privacy in cloud and IT service organizations, established by the American Institute of Certified
Public Accountants (AICPA). Public Accountants (AICPA).
@ -34,9 +34,9 @@ Public Accountants (AICPA).
<Callout type="warning" emoji="⏳"> <Callout type="warning" emoji="⏳">
Status: [Planned](https://github.com/documenso/backlog/issues/26) Status: [Planned](https://github.com/documenso/backlog/issues/26)
</Callout> </Callout>
ISO 27001 is an international standard for managing information security, specifying requirements for ISO 27001 is an international standard for managing information security, specifying requirements
establishing, implementing, maintaining, and continually improving an information security management for establishing, implementing, maintaining, and continually improving an information security
system (ISMS). management system (ISMS).
### HIPAA ### HIPAA

View File

@ -11,120 +11,76 @@ Learn about the different fields you can add to your documents in Documenso and
The signature field collects the signer's signature. It's required for each recipient with the "Signer" role. The signature field collects the signer's signature. It's required for each recipient with the "Signer" role.
### Document Editor View
The field doesn't have any additional settings. You just need to place it on the document where you want the signer to sign.
![The signature field in the Documenso document editor](/document-signing/signature-field-document-editor-view.webp) ![The signature field in the Documenso document editor](/document-signing/signature-field-document-editor-view.webp)
### Document Signing View The signature field settings include:
The recipient will see the signature field when they open the document to sign. - **Font size** - The typed signature font size. You can disable typed signatures in the document settings.
The recipient must click on the signature field to open the signing view, where they can sign using their mouse, touchpad, or touchscreen.
![The signature field in the Documenso document signing view](/document-signing/signature-field-document-signing-view.webp)
The image below shows the signature field signed by the recipient.
![The signature field signed by the user in the Documenso document signing view](/document-signing/signed-signature-field.webp)
After signing, the recipient can click the "Complete" button to complete the signing process.
## Email Field ## Email Field
The email field is used to collect the signer's email address. The email field is used to collect the signer's email address. This will be the email address you entered when you created the recipients.
### Document Editor View
The field doesn't have any additional settings. You just need to place it on the document where you want the signer to sign.
![The email field in the Documenso document editor](/document-signing/email-field-document-editor-view.webp) ![The email field in the Documenso document editor](/document-signing/email-field-document-editor-view.webp)
### Document Signing View The email field settings include:
When the recipient opens the document to sign, they will see the email field. - **Font Size** - The font size of the email address.
- **Text Align** - The horizontal text alignment of the email address.
The recipient must click on the email field to automatically sign the field with the email associated with their account.
![The email field in the Documenso document signing view](/document-signing/email-field-document-signing-view.webp)
The image below shows the email field signed by the recipient.
![The email field signed by the user in the Documenso document signing view](/document-signing/signed-email-field.webp)
After entering their email address, the recipient can click the "Complete" button to complete the signing process.
## Name Field ## Name Field
The name field is used to collect the signer's name. The name field is used to collect the signer's name.
### Document Editor View
The field doesn't have any additional settings. You just need to place it on the document where you want the signer to sign.
![The name field in the Documenso document editor](/document-signing/name-field-document-editor-view.webp) ![The name field in the Documenso document editor](/document-signing/name-field-document-editor-view.webp)
### Document Signing View The name field settings include:
When the recipient opens the document to sign, they will see the name field. - **Font Size** - The font size of the name.
- **Text Align** - The horizontal text alignment of the name.
The recipient must click on the name field, which will automatically sign the field with the name associated with their account. ## Initials Field
![The name field in the Documenso document signing view](/document-signing/name-field-document-signing-view.webp) The initials field is used to collect the signer's initials. This is generally the first and last name initials.
The image below shows the name field signed by the recipient. ![The initials field in the Documenso document editor](/document-signing/initials-field-document-editor-view.webp)
![The name field signed by the user in the Documenso document signing view](/document-signing/name-field-signed.webp) The initials field settings include:
After entering their name, the recipient can click the "Complete" button to complete the signing process. - **Font Size** - The font size of the initials.
- **Text Align** - The horizontal text alignment of the initials.
## Date Field ## Date Field
The date field is used to collect the date of the signature. The date field is used to collect the date of the signature. You can change the date format in the document settings.
### Document Editor View
The field doesn't have any additional settings. You just need to place it on the document where you want the signer to sign.
![The date field in the Documenso document editor](/document-signing/date-field-document-editor-view.webp) ![The date field in the Documenso document editor](/document-signing/date-field-document-editor-view.webp)
### Document Signing View The date field settings include:
When the recipient opens the document to sign, they will see the date field. - **Font Size** - The font size of the date.
- **Text Align** - The horizontal text alignment of the date.
The recipient must click on the date field to automatically sign the field with the current date and time.
![The date field in the Documenso document signing view](/document-signing/date-field-document-signing-view.webp)
The image below shows the date field signed by the recipient.
![The date field signed by the user in the Documenso document signing view](/document-signing/date-field-signed.webp)
After entering the date, the recipient can click the "Complete" button to complete the signing process.
## Text Field ## Text Field
The text field is used to collect text input from the signer. The text field is used to collect text input from the signer.
### Document Editor View
Place the text field on the document where you want the signer to enter text. The text field comes with additional settings that can be configured. Place the text field on the document where you want the signer to enter text. The text field comes with additional settings that can be configured.
![The text field in the Documenso document editor](/document-signing/text-field-document-editor-view.webp) ![The text field in the Documenso document editor](/document-signing/text-field-document-editor-view.webp)
To open the settings, click on the text field and then on the "Sliders" icon. That opens the settings panel on the right side of the screen.
![The text field settings in the Documenso document editor](/document-signing/text-field-advanced-settings-document-editor-view.webp)
The text field settings include: The text field settings include:
- **Label** - The label displayed in the field. - **Label** - The label displayed in the field when the user opens the document to sign
- **Placeholder** - The placeholder text displayed in the field. - **Placeholder** - The placeholder text displayed in the field. The signer will see this prior to signing.
- **Text** - The default text displayed in the field. - **Text** - The text which will be prefilled into the field. The signer can change this text unless the field is read-only.
- **Character limit** - The maximum number of characters allowed in the field. - **Character limit** - The maximum number of characters allowed in the field.
- **Required** - Whether the field is required or not. - **Required** - Whether the field is required or not.
- **Read only** - Whether the field is read-only or not. - **Read only** - Whether the field is read-only or not.
- **Text Align** - The horizontal text alignment of the text in the field.
- **Vertical Align** - The vertical text alignment of the text in the field.
- **Line height** - The line height of the text in the field. Useful for multi-line text fields.
- **Letter spacing** - The spacing between each character in the text in the field.
It also comes with a couple of rules: It also comes with a couple of rules:
@ -133,71 +89,26 @@ It also comes with a couple of rules:
- The signer must fill out a required field. - The signer must fill out a required field.
- The text field characters count can't exceed the character limit. - The text field characters count can't exceed the character limit.
- The signer can't modify a read-only field. - The signer can't modify a read-only field.
- The field auto-signs if there is a default text value. - The field will be inserted automatically into the document if there is a default text value.
Let's look at the following example.
![A text field with the settings configured by the user in the Documenso document editor](/document-signing/text-field-with-filled-advanced-settings.webp)
The field is configured as follows:
- Label: "Address"
- Placeholder: "Your office address"
- Default Text: "Signing Street 1, 241245"
- Character Limit: 35
- Required: False
- Read Only: False
Since the field has a label set, the label is displayed instead of the default text field value - "Add text".
### Document Signing View
What the recipient sees when they open the document to sign depends on the settings configured by the sender.
In this case, the recipient sees the text field signed with the default value.
![Text field with the default value signed by the user in the Documenso document signing view](/document-signing/text-field-autosigned.webp)
The recipient can modify the text field value since the field is not read-only. To change the value, the recipient must click the field to un-sign it.
Once it's unsigned, the field uses the label set by the sender.
![Unsigned text field in the Documenso document signing view](/document-signing/text-field-unsigned.webp)
To sign the field with a different value, the recipient needs to click on the field and enter the new value.
![Text field with the text input in the Documenso document signing view](/document-signing/text-field-input-modal.webp)
Since the text field has a character limit, the recipient must enter a value that doesn't exceed the limit. Otherwise, an error message will appear, and the field will not be signed.
The image below illustrates the text field signed with a new value.
![Text field signed with a new value](/document-signing/text-field-new-value-signed.webp)
After signing the field, the recipient can click the "Complete" button to complete the signing process.
## Number Field ## Number Field
The number field is used for collecting a number input from the signer. The number field is used for collecting a number input from the signer.
### Document Editor View
Place the number field on the document where you want the signer to enter a number. The number field comes with additional settings that can be configured.
![The number field in the Documenso document editor](/document-signing/number-field-document-editor.webp)
To open the settings, click on the number field and then on the "Sliders" icon. That opens the settings panel on the right side of the screen.
![The number field in the Documenso document editor](/document-signing/number-field-document-editor-view.webp) ![The number field in the Documenso document editor](/document-signing/number-field-document-editor-view.webp)
The number field settings include: The number field settings include:
- **Label** - The label displayed is the field. - **Label** - The label displayed is the field.
- **Placeholder** - The placeholder text displayed in the field. - **Placeholder** - The placeholder text displayed in the field.
- **Value** - The default number displayed in the field. - **Value** - The value which will be prefilled into the field. The signer can change this value unless the field is read-only.
- **Number format** - The format of the number. - **Number format** - The format of the number.
- **Required** - Whether the field is required or not. - **Required** - Whether the field is required or not.
- **Read only** - Whether the field is read-only or not. - **Read only** - Whether the field is read-only or not.
- **Text Align** - The horizontal text alignment of the number in the field.
- **Vertical Align** - The vertical text alignment of the number in the field.
- **Line height** - The line height of the number in the field. Useful for multi-line number fields.
- **Letter spacing** - The spacing between each character in the number in the field.
- **Validation** - The validation rules for the field. - **Validation** - The validation rules for the field.
It also comes with a couple of rules: It also comes with a couple of rules:
@ -210,66 +121,18 @@ It also comes with a couple of rules:
- If the default number and a min value are set, the default number must be greater than the min value. - If the default number and a min value are set, the default number must be greater than the min value.
- The value must match the number format if a number format is set. - The value must match the number format if a number format is set.
In this example, the number field is configured as follows:
- Label: "Quantity"
- Placeholder: "Enter the preferred quantity"
- Default Value: 12
- Number Format: "123,456,789.00"
- Required: False
- Read Only: False
- Validation:
- Min value: 5, Max value: 50
![A number field with the label configured by the user in the Documenso document editor](/document-signing/number-field-label.webp)
Since the field has a label set, the label is displayed instead of the default number field value - "Add number".
### Document Signing View
What the recipient sees when they open the document to sign depends on the settings configured by the sender.
The recipient sees the number field signed with the default value in this case.
![Number field with the default value signed by the user in the Documenso document signing view](/document-signing/number-field-autosigned.webp)
Since the number field is not read-only, the recipient can modify its value. To change the value, the recipient must click the field to un-sign it.
Once it's unsigned, the field uses the label set by the sender.
![Unsigned number field in the Documenso document signing view](/document-signing/number-field-unsigned.webp)
To sign the field with a different value, the recipient needs to click on the field and enter the new value.
![Number field with the number input in the Documenso document signing view](/document-signing/number-field-input-modal.webp)
Since the number field has a validation rule set, the recipient must enter a value that meets the rules. In this example, the value needs to be between 5 and 50. Otherwise, an error message will appear, and the field will not be signed.
The image below illustrates the text field signed with a new value.
![Number field signed with a new value](/document-signing/number-field-signed-with-another-value.webp)
After signing the field, the recipient can click the "Complete" button to complete the signing process.
## Radio Field ## Radio Field
The radio field is used to collect a single choice from the signer. The radio field is used to collect a single choice from the signer.
### Document Editor View
Place the radio field on the document where you want the signer to select a choice. The radio field comes with additional settings that can be configured.
![The radio field in the Documenso document editor](/document-signing/radio-field-document-editor-view.webp) ![The radio field in the Documenso document editor](/document-signing/radio-field-document-editor-view.webp)
To open the settings, click on the radio field and then on the "Sliders" icon. That opens the settings panel on the right side of the screen.
![The radio field advanced settings in the Documenso document editor](/document-signing/radio-field-advanced-settings-document-editor-view.webp)
The radio field settings include: The radio field settings include:
- **Required** - Whether the field is required or not. - **Required** - Whether the field is required or not.
- **Read only** - Whether the field is read-only or not. - **Read only** - Whether the field is read-only or not.
- **Values** - The list of choices for the field. - **Values** - The list of choices for the field.
- **Direction** - The direction of the radio field. Can be "Vertical" or "Horizontal".
It also comes with a couple of rules: It also comes with a couple of rules:
@ -282,57 +145,22 @@ It also comes with a couple of rules:
- It should contain at least one option. - It should contain at least one option.
- The field can't have more than one option selected. - The field can't have more than one option selected.
In this example, the radio field is configured as follows:
- Required: False
- Read Only: False
- Options:
- Option 1
- Option 2
- Empty value
- Empty value
- Option 3
![The radio field with the settings configured by the user in the Documenso document editor](/document-signing/radio-field-options-document-editor-view.webp)
Since the field contains radio options, it displays them instead of the default radio field value, "Radio".
### Document Signing View
What the recipient sees when they open the document to sign depends on the settings configured by the sender.
In this case, the recipient sees the radio field unsigned because the sender didn't select a value.
![Radio field with no value selected in the Documenso document signing view](/document-signing/radio-field-unsigned.webp)
The recipient can select one of the options by clicking on the radio button next to the option.
![Radio field with the value selected by the user in the Documenso document signing view](/document-signing/radio-field-signed.webp)
After signing the field, the recipient can click the "Complete" button to complete the signing process.
## Checkbox Field ## Checkbox Field
The checkbox field is used to collect multiple choices from the signer. The checkbox field is used to collect multiple choices from the signer.
### Document Editor View
Place the checkbox field on the document where you want the signer to select choices. The checkbox field comes with additional settings that can be configured. Place the checkbox field on the document where you want the signer to select choices. The checkbox field comes with additional settings that can be configured.
![The checkbox field in the Documenso document editor](/document-signing/checkbox-document-editor-view.webp) ![The checkbox field in the Documenso document editor](/document-signing/checkbox-field-document-editor-view.webp)
To open the settings, click on the checkbox field and then on the "Sliders" icon. That opens the settings panel on the right side of the screen.
![The checkbox field settings in the Documenso document editor](/document-signing/checkbox-advanced-settings.webp)
The checkbox field settings include the following: The checkbox field settings include the following:
- **Validation** - The validation rules for the field.
- **Rule** - The rule specifies "At least", "At most", and "Exactly".
- **Number** - The number of choices that must be selected.
- **Required** - Whether the field is required or not. - **Required** - Whether the field is required or not.
- **Read only** - Whether the field is read-only or not. - **Read only** - Whether the field is read-only or not.
- **Options** - The list of choices for the field. - **Options** - The list of choices for the field.
- **Direction** - The direction of the checkbox field. Can be "Vertical" or "Horizontal".
- **Validation Rule** - The rule specifies "At least", "At most", and "Exactly".
- **Validation Number** - The number of choices that must be selected.
It also comes with a couple of rules: It also comes with a couple of rules:
@ -344,67 +172,20 @@ It also comes with a couple of rules:
- The signer can't modify the field if it's read-only. - The signer can't modify the field if it's read-only.
- It should contain at least one option. - It should contain at least one option.
In this example, the checkbox field is configured as follows:
- No validation rules
- Required: True
- Read Only: False
- Options:
- Option 1
- Empty value (checked)
- Option 2
- Option 3 (checked)
- Empty value
![The checkbox field with the settings configured by the user in the Documenso document editor](/document-signing/checkbox-advanced-settings-document-editor-view.webp)
Since the field contains checkbox options, it displays them instead of the default checkbox field value, "Checkbox".
### Document Signing View
What the recipient sees when they open the document to sign depends on the settings configured by the sender.
In this case, the recipient sees the checkbox field signed with the values selected by the sender.
![Checkbox field with the values selected by the user in the Documenso document signing view](/document-signing/checkbox-field-document-signing-view.webp)
Since the field is required, the recipient can either sign with the values selected by the sender or modify the values.
The values can be modified in 2 ways:
- Click on the options you want to select or deselect.
- Hover over the field and click the "X" button to clear all the selected values.
The image below illustrates the checkbox field with the values cleared by the recipient. Since the field is required, it has a red border instead of the yellow one (non-required fields).
![Checkbox field the values cleared by the user in the Documenso document signing view](/document-signing/checkbox-field-unsigned.webp)
Then, the recipient can select values other than the ones chosen by the sender.
![Checkbox field with the values selected by the user in the Documenso document signing view](/document-signing/checkbox-field-custom-sign.webp)
After signing the field, the recipient can click the "Complete" button to complete the signing process.
## Dropdown/Select Field ## Dropdown/Select Field
The dropdown/select field collects a single choice from a list of options. The dropdown/select field collects a single choice from a list of options.
### Document Editor View
Place the dropdown/select field on the document where you want the signer to select a choice. The dropdown/select field comes with additional settings that can be configured. Place the dropdown/select field on the document where you want the signer to select a choice. The dropdown/select field comes with additional settings that can be configured.
![The dropdown/select field in the Documenso document editor](/document-signing/select-field-sliders.webp) ![The dropdown/select field in the Documenso document editor](/document-signing/dropdown-field-document-editor-view.webp)
To open the settings, click on the dropdown/select field and then on the "Sliders" icon. That opens the settings panel on the right side of the screen.
![The dropdown/select field settings in the Documenso document editor](/document-signing/select-field-advanced-settings-panel.webp)
The dropdown/select field settings include: The dropdown/select field settings include:
- **Required** - Whether the field is required or not. - **Required** - Whether the field is required or not.
- **Read only** - Whether the field is read-only or not. - **Read only** - Whether the field is read-only or not.
- **Options** - The list of choices for the field. - **Options** - The list of choices for the field.
- **Default** value - The default value selected in the field. - **Default Value** - The default value selected in the field.
It also comes with a couple of rules: It also comes with a couple of rules:
@ -416,31 +197,3 @@ It also comes with a couple of rules:
- The field can't be signed with a value not in the options list. - The field can't be signed with a value not in the options list.
- The signer can't modify the field if it's read-only. - The signer can't modify the field if it's read-only.
- It should contain at least one option. - It should contain at least one option.
In this example, the dropdown/select field is configured as follows:
- Required: False
- Read Only: False
- Default Value: None
- Options:
- Document
- Template
- Other
### Document Signing View
What the recipient sees when they open the document to sign depends on the settings configured by the sender.
In this case, the recipient sees the dropdown/select field with the default label, "-- Select ---" since the sender has not set a default value.
![Dropdown/select field in the Documenso document signing view](/document-signing/select-field-unsigned.webp)
The recipient can modify the dropdown/select field value since the field is not read-only. To change the value, the recipient must click on the field for the dropdown list to appear.
![Dropdown/select field with the dropdown list in the Documenso document signing view](/document-signing/select-field-unsigned-dropdown.webp)
The recipient can select one of the options from the list. The image below illustrates the dropdown/select field signed with a new value.
![Dropdown/select field signed with a value](/document-signing/select-field-signed.webp)
After signing the field, the recipient can click the "Complete" button to complete the signing process.

View File

@ -9,85 +9,85 @@ import { Callout, Steps } from 'nextra/components';
This guide will walk you through the process of sending a document out for signing. You will learn how to upload a document, add recipients, place signature fields, and send the document for signing. This guide will walk you through the process of sending a document out for signing. You will learn how to upload a document, add recipients, place signature fields, and send the document for signing.
## Uploading a Document
<Steps> <Steps>
### Login Into Your Account ### Log In to Your Account
The guide assumes you have a Documenso account. If you don't, you can create a free account [here](https://documen.so/free-docs). The guide assumes you have a Documenso account. If you don't, you can create a free account [here](https://documen.so/free-docs).
### Upload Document ### Upload Document
Navigate to the [Documenso dashboard](https://app.documenso.com/documents) and click on the "Add a document" button. Select the document you want to upload and wait for the upload to complete. Navigate to the [Documenso dashboard](https://app.documenso.com/documents) and click on the "Upload Document" button located at the top right of the page. Select the documents you want to upload and wait for the upload to complete.
<Callout type="info">
The maximum file size for uploaded documents is 150MB in production. You can upload up to 5
documents at a time on standard plans. In staging, the limit is 50MB.
</Callout>
![Documenso dashboard](/document-signing/documenso-documents-dashboard.webp) ![Documenso dashboard](/document-signing/documenso-documents-dashboard.webp)
After the upload is complete, you will be redirected to the document's page. You can configure the document's settings and add recipients and fields here. After the upload is complete, you will be redirected to the editor. You can configure the document's settings and add recipients and fields here.
![Documenso document overview](/document-signing/documenso-uploaded-document.webp) </Steps>
### (Optional) Advanced Options ## Configuring and Sending Document
Click on the "Advanced options" button to access additional settings for the document. You can set an external ID, date format, time zone, and the redirect URL. The Documenso editor allows you to create and configure documents or templates. The editor consists of three sections that guide you through the process of preparing your document.
![Advanced settings for a document uploaded in the Documenso dashboard](/document-signing/documenso-uploaded-document-advanced-options.webp) 1. **Document & Recipients** - Upload files and add recipients
2. **Add Fields** - Add fields to the document
3. **Preview** - Preview the document before sending
The external ID allows you to set a custom ID for the document that can be used to identify the document in your external system(s). You can click each section to navigate to it, and the current section will be highlighted in the sidebar.
The date format and time zone settings allow you to customize how the date and time are displayed in the document. ![Editor page in the Documenso dashboard](/document-signing/editor-overview.webp)
The redirect URL allows you to specify a URL where the signer will be redirected after signing the document. There is also a quick actions section on the left-hand side which allows you to:
### (Optional) Document Access - **Document Settings** - Configure general settings, emails, or security settings for the document
- **Send Document** - Send the document to the recipients
- **Duplicate** - Duplicate the document
- **Download** - Download the document PDF
- **Delete** - Delete the document
Documenso enables you to set up access control for your documents. You can require authentication for viewing the document. The header contains some notable items:
The available options are: - **Title** - You can edit the title in the top-left corner of the header by clicking on the text
- **Status** - You can see the current status of the document next to the title
- **Attachments** - You can configure the attachments on the right-hand side of the header
- **Require account** - The recipient must be signed in to view the document. <Steps>
- **None** - The document can be accessed directly by the URL sent to the recipient.
![Document access settings in the Documenso dashboard](/document-signing/documenso-enterprise-document-access.webp) ### Upload Documents and Add Recipients
The first step in the editor is to upload your document and add recipients.
![Upload and add recipients page in the Documenso editor](/document-signing/editor-upload-recipients.webp)
#### Upload Document
You can upload documents by either clicking the "Add a document" dropzone, or dragging and dropping files into the dropzone.
<Callout type="info"> <Callout type="info">
The "Document Access" feature is only available for Enterprise accounts. The maximum file size for uploaded documents is 150MB in production. You can upload up to 5
documents at a time on standard plans. In staging, the limit is 50MB.
</Callout> </Callout>
### (Optional) Recipient Authentication #### Add Recipients
The "Recipient Authentication" feature allows you to specify the authentication method required for recipients to sign the signature field. Click the "+ Add Signer" button to add a new recipient. For each recipient you can configure the following:
The available options are: - **Email address** - The recipient's email address
- **Name** - The recipient's name
- **Order** - The sequence in which the recipient should sign the document. Only available if the "Enable Signing Order" checkbox is enabled
- **Role** - As seen below
- **Require passkey** - The recipient must have an account and passkey configured via their settings. Documenso has 5 roles for recipients with different permissions and actions.
- **Require 2FA** - The recipient must have an account and 2FA enabled via their settings.
- **None** - No authentication required.
![Document recipient action authentication in the Documenso dashboard](/document-signing/document-enterprise-recipient-action-authentication.webp)
This can be overridden by setting the authentication requirements directly for each recipient in the next step.
<Callout type="info">
The "Recipient Authentication" feature is only available for Enterprise accounts.
</Callout>
### Recipients
Click the "+ Add Signer" button to add a new recipient. You can configure the recipient's email address, name, role, and authentication method on this page.
You can choose any option from the ["Recipient Authentication"](#optional-recipient-authentication) section, or you can set it to "Inherit authentication method" to use the global action signing authentication method configured in the "General Settings" step.
![The required authentication method for a recipient](/document-signing/documenso-document-recipient-authentication-method.webp)
You can also set the recipient's role, which determines their actions and permissions in the document.
![The recipient role](/document-signing/documenso-document-recipient-role.webp)
#### Roles
Documenso has 4 roles for recipients with different permissions and actions.
| Role | Function | Action required | Signature | | Role | Function | Action required | Signature |
| :-------: | :-----------------------------------------------------------------------------: | :-------------: | :-------: | | :-------: | :-----------------------------------------------------------------------------: | :-------------: | :-------: |
| Signer | Needs to sign signatures fields assigned to them. | Yes | Yes | | Signer | Needs to sign signature fields assigned to them. | Yes | Yes |
| Approver | Needs to approve the document as a whole. Signature optional. | Yes | Optional | | Approver | Needs to approve the document as a whole. Signature optional. | Yes | Optional |
| Viewer | Needs to confirm they viewed the document. | Yes | No | | Viewer | Needs to confirm they viewed the document. | Yes | No |
| Assistant | Can help prepare the document by filling in fields on behalf of other signers. | Yes | No | | Assistant | Can help prepare the document by filling in fields on behalf of other signers. | Yes | No |
@ -95,15 +95,20 @@ Documenso has 4 roles for recipients with different permissions and actions.
### Fields ### Fields
Documenso supports 9 different field types that can be added to the document. Each field type collects various information from the recipients when they sign the document. Documenso supports 10 different field types that can be added to the document. Each field type collects various information from the recipients when they sign the document.
To create a field, you can either click and drag to draw a field on the document or drag and drop the field type from the sidebar.
![The available field types in the Documenso dashboard](/document-signing/documenso-document-fields.webp) ![The available field types in the Documenso dashboard](/document-signing/documenso-document-fields.webp)
The field created will be assigned to the currently selected recipient. You can change the recipient by clicking on the recipient's name in the right sidebar.
The available field types are: The available field types are:
- **Signature** - Collects the signer's signature - **Signature** - Collects the signer's signature
- **Email** - Collects the signer's email address - **Email** - Collects the signer's email address
- **Name** - Collects the signer's name - **Name** - Collects the signer's name
- **Initials** - Collects the signer's initials
- **Date** - Collects the date of the signature - **Date** - Collects the date of the signature
- **Text** - Collects text input from the signer - **Text** - Collects text input from the signer
- **Number** - Collects a number input from the signer - **Number** - Collects a number input from the signer
@ -118,11 +123,11 @@ All fields can be placed anywhere on the document and resized as needed.
page](/users/documents/fields). page](/users/documents/fields).
</Callout> </Callout>
#### Signature Required ### Preview
Signer Roles require at least 1 signature field. You will get an error message if you try to send a document without a signature field. In this section, you can preview what the document will look like once it is fully signed. This uses placeholder data for the fields.
![Error message when trying to send a document without a signature field](/document-signing/documenso-no-signature-field-found.webp) ![Preview section in the Documenso editor](/document-signing/documenso-editor-preview.webp)
### Email Settings ### Email Settings
@ -151,3 +156,54 @@ https://app.documenso.com/sign/12ACP777zxQLO52hjj_vCB
``` ```
</Steps> </Steps>
## Document Settings
To access the document settings, click the "Document Settings" button in the quick actions section.
### General Settings
![General Documenso editor settings](/document-signing/documenso-editor-general-settings.webp)
- **Language** - The language that the emails will be sent in
- **Allowed Signature Types** - The signature types that the recipient will be allowed to use
- **Date Format** - The date format that will be used for date fields
- **Time Zone** - The time zone that will be used for date fields
- **External ID** - A custom ID for the document that can be used to identify the document in your external system(s)
- **Redirect URL** - The URL where the signer will be redirected after signing the document
- **Document Distribution Method** - Whether to use emails to send the document, or none (which we will generate signing links for manual distribution)
### Email Settings
![Email Documenso editor settings](/document-signing/documenso-editor-email-settings.webp)
- **Reply To** - The email address that will be used as the reply to email address
- **Subject** - The subject of the email
- **Message** - The message of the email
- **Checkboxes** - You can select which emails should be sent during the document signing process
### Security Settings
Documenso enables you to set up access control for your documents. You can require authentication for viewing the document.
The available options are:
- **Require account** - The recipient must be signed in to view the document.
- **Require 2FA** - The recipient must use 2FA to sign the document.
- **None** - The document can be accessed directly by the URL sent to the recipient.
![Security Documenso editor settings](/document-signing/documenso-editor-security-settings.webp)
The "Recipient Authentication" feature allows you to specify the authentication method required for recipients to sign the signature field.
The available options are:
- **Require passkey** - The recipient must have an account and passkey configured via their settings.
- **Require 2FA** - The recipient must have an account and 2FA enabled via their settings.
- **None** - No authentication required.
This can be overridden by setting the authentication requirements directly for each recipient in the next step.
<Callout type="info">
The "Recipient Authentication" feature is only available for Enterprise accounts.
</Callout>

View File

@ -3,5 +3,6 @@
"members": "Members", "members": "Members",
"groups": "Groups", "groups": "Groups",
"teams": "Teams", "teams": "Teams",
"sso": "SSO",
"billing": "Billing" "billing": "Billing"
} }

View File

@ -0,0 +1,4 @@
{
"index": "Configuration",
"microsoft-entra-id": "Microsoft Entra ID"
}

View File

@ -0,0 +1,149 @@
---
title: SSO Portal
description: Learn how to set up a custom SSO login portal for your organisation.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Organisation SSO Portal
The SSO Portal provides a dedicated login URL for your organisation that integrates with any OIDC compliant identity provider. This feature provides:
- **Single Sign-On**: Access Documenso using your own authentication system
- **Automatic onboarding**: New users will be automatically added to your organisation when they sign in through the portal
- **Delegated account management**: Your organisation has full control over the users who sign in through the portal
<Callout type="warning">
Anyone who signs in through your portal will be added to your organisation as a member.
</Callout>
## Getting Started
To set up the SSO Portal, you need to be an organisation owner, admin, or manager.
<Callout type="info">
**Enterprise Only**: This feature is only available to Enterprise customers.
</Callout>
<Steps>
### Access Organisation SSO Settings
![Organisation SSO Portal settings](/organisations/organisations-sso-settings.webp)
### Configure SSO Portal
See the [Microsoft Entra ID](/users/organisations/sso/microsoft-entra-id) guide to find the values for the following fields.
#### Issuer URL
Enter the OpenID discovery endpoint URL for your provider. Here are some common examples:
- **Google Workspace**: `https://accounts.google.com/.well-known/openid-configuration`
- **Microsoft Entra ID**: `https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration`
- **Okta**: `https://{your-domain}.okta.com/.well-known/openid-configuration`
- **Auth0**: `https://{your-domain}.auth0.com/.well-known/openid-configuration`
#### Client Credentials
Enter the client ID and client secret provided by your identity provider:
- **Client ID**: The unique identifier for your application
- **Client Secret**: The secret key for authenticating your application
#### Default Organisation Role
Select the default Organisation role that new users will receive when they first sign in through the portal.
#### Allowed Email Domains
Specify which email domains are allowed to sign in through your SSO portal. Separate domains with spaces:
```
your-domain.com another-domain.com
```
Leave this field empty to allow all domains.
### Configure Your Identity Provider
You'll need to configure your identity provider with the following information:
- Redirect URI
- Scopes
These values are found at the top of the page.
### Save Configuration
Toggle the "Enable SSO portal" switch to activate the feature for your organisation.
Click "Update" to save your SSO portal configuration. The portal will be activated once all required fields are completed.
</Steps>
## Testing Your SSO Portal
Once configured, you can test your SSO portal by:
1. Navigating to your portal URL found at the top of the organisation SSO portal settings page
2. Sign in with a test account from your configured domain
3. Verifying that the user is properly provisioned with the correct organisation role
## Best Practices
### Reduce Friction
Create a custom subdomain for your organisation's SSO portal. For example, you can create a subdomain like `documenso.your-organisation.com` which redirects to the portal link.
### Security Considerations
Please note that anyone who signs in through your portal will be added to your organisation as a member.
- **Domain Restrictions**: Use allowed domains to prevent unauthorized access
- **Role Assignment**: Carefully consider the default organisation role for new users
## Troubleshooting
### Common Issues
**"Invalid issuer URL"**
- Verify the issuer URL is correct and accessible
- Ensure the URL follows the OpenID Connect discovery format
**"Client authentication failed"**
- Check that your client ID and client secret are correct
- Verify that your application is properly registered with your identity provider
**"User not provisioned"**
- Check that the user's email domain is in the allowed domains list
- Verify the default organisation role is set correctly
**"Redirect URI mismatch"**
- Ensure the redirect URI in Documenso matches exactly what's configured in your identity provider
- Check for any trailing slashes or protocol mismatches
### Getting Help
If you encounter issues with your SSO portal configuration:
1. Review your identity provider's documentation for OpenID Connect setup
2. Check the Documenso logs for detailed error messages
3. Contact your identity provider's support for provider-specific issues
<Callout type="info">
For additional support for SSO Portal configuration, contact our support team at
support@documenso.com.
</Callout>
## Identity Provider Guides
For detailed setup instructions for specific identity providers:
- [Microsoft Entra ID](/users/organisations/sso/microsoft-entra-id) - Complete guide for Azure AD configuration

View File

@ -0,0 +1,76 @@
---
title: Microsoft Entra ID
description: Learn how to configure Microsoft Entra ID (Azure AD) for your organisation's SSO portal.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Microsoft Entra ID Configuration
Microsoft Entra ID (formerly Azure Active Directory) is a popular identity provider for enterprise SSO. This guide will walk you through creating an app registration and configuring it for use with your Documenso SSO portal.
## Prerequisites
- Access to Microsoft Entra ID (Azure AD) admin center
- Access to your Documenso organisation as an administrator or manager
<Callout type="warning">Each user in your Azure AD will need an email associated with it.</Callout>
## Creating an App Registration
<Steps>
### Access Azure Portal
1. Navigate to the Azure Portal
2. Sign in with your Microsoft Entra ID administrator account
3. Search for "Azure Active Directory" or "Microsoft Entra ID" in the search bar
4. Click on "Microsoft Entra ID" from the results
### Create App Registration
1. In the left sidebar, click on "App registrations"
2. Click the "New registration" button
### Configure App Registration
Fill in the registration form with the following details:
- **Name**: Your preferred name (e.g. `Documenso SSO Portal`)
- **Supported account types**: Choose based on your needs
- **Redirect URI (Web)**: Found in the Documenso SSO portal settings page
Click "Register" to create the app registration.
### Get Client ID
After registration, you'll be taken to the app's overview page. The **Application (client) ID** is displayed prominently - this is your Client ID for Documenso.
### Create Client Secret
1. In the left sidebar, click on "Certificates & secrets"
2. Click "New client secret"
3. Add a description (e.g., "Documenso SSO Secret")
4. Choose an expiration period (recommended 12-24 months)
5. Click "Add"
Make sure you copy the "Secret value", not the "Secret ID", you won't be able to access it again after you leave the page.
</Steps>
## Getting Your OpenID Configuration URL
1. In the Azure portal, go to "Microsoft Entra ID"
2. Click on "Overview" in the left sidebar
3. Click the "Endpoints" in the horizontal tab
4. Copy the "OpenID Connect metadata document" value
## Configure Documenso SSO Portal
Now you have all the information needed to configure your Documenso SSO portal:
- **Issuer URL**: The "OpenID Connect metadata document" value from the previous step
- **Client ID**: The Application (client) ID from your app registration
- **Client Secret**: The secret value you copied during creation

View File

@ -8,82 +8,43 @@ Documenso allows you to create templates, which are reusable documents. Template
### Create New Template ### Create New Template
To create a new template, navigate to the ["Templates" page](https://app.documenso.com/templates) and click on the "New Template" button. To create a new template, navigate to the "Templates" page and click on the "Upload Template" button.
![Documenso template page](/templates/documenso-template-page.webp) ![Documenso template page](/templates/documenso-template-page.webp)
Clicking on the "New Template" button opens a new modal to upload the document you want to use as a template. Select the document and wait for Documenso to upload it to your account. You can select multiple files at once to upload into the template. Once the upload is complete you will be redirected to the editor.
![Upload a new template document in the Documenso dashboard](/templates/documenso-template-page-upload-document.webp) ![A successfully uploaded document in the Documenso dashboard](/templates/documenso-template-page-uploaded-document.webp)
Once the upload is complete, Documenso opens the template configuration page. See the [documents](/users/documents/sending-documents) page for more information on how to use the editor.
![A successfuly uploaded document in the Documenso dashboard](/templates/documenso-template-page-uploaded-document.webp)
You can then configure the template by adding recipients, fields, and other options. You can then configure the template by adding recipients, fields, and other options.
### (Optional) Email options
When you send a document for signing, Documenso emails the recipients with a link to the document. The email contains a subject, message, and the document.
Documenso uses a generic subject and message but also allows you to customize them for each document and template.
![Configuring the email options for the Documenso template](/templates/documenso-template-page-uploaded-document-email-options.webp)
To configure the email options, click the "Email Options" tab and fill in the subject and message fields. Every time you use this template for signing, Documenso will use the custom subject and message you provided. They can also be overridden before sending the document.
### (Optional) Advanced Options
The template also has advanced options that you can configure. These options include settings such as the external ID, date format, time zone and the redirect URL.
![Configuring the advanced options for the Documenso template](/templates/documenso-template-page-uploaded-document-advanced-options.webp)
The external ID allows you to set a custom ID for the document that can be used to identify the document in your external system(s).
The date format and time zone settings allow you to customize how the date and time are displayed in the document.
The redirect URL allows you to specify a URL where the signer will be redirected after signing the document.
### Add Placeholders or Recipients ### Add Placeholders or Recipients
You can add placeholders for the template recipients. Placeholders specify where the recipient's name, email, and other information should be in the document. When adding recipients, you can use dummy recipients which can be replaced with actual recipients when you use a template, which we will explain in the next section.
You can also add recipients directly to the template. Recipients are the people who will receive the document for signing. For example, the following has two recipients:
- placeholder@documenso.com
- me@documenso.com
![Adding placeholder recipients for the Documenso template](/templates/documenso-template-recipients.webp) ![Adding placeholder recipients for the Documenso template](/templates/documenso-template-recipients.webp)
If you add placeholders to the template, you must replace them with actual recipients when creating a document from it. See the modal from the ["Use a Template"](#use-a-template) section. You can then place fields for each recipient.
### Add Fields
The last step involves adding fields to the document. These fields collect information from the recipients when they sign the document.
Documenso provides the following field types:
- **Signature** - Collects the signer's signature
- **Email** - Collects the signer's email address
- **Name** - Collects the signer's name
- **Date** - Collects the date of the signature
- **Text** - Collects text input from the signer
- **Number** - Collects a number input from the signer
- **Radio** - Collects a single choice from the signer
- **Checkbox** - Collects multiple choices from the signer
- **Dropdown/Select** - Collects a single choice from a list of choices
![Adding fields for the Documenso template](/templates/documenso-template-page-fields.webp)
After adding the fields, press the "Save Template" button to save the template.
<Callout type="info"> <Callout type="info">
Learn more about the available field types and how to use them on the [Fields Learn more about the available field types and how to use them on the [Fields
page](signing-documents/fields). page](/users/documents/fields).
</Callout> </Callout>
### Use a Template ### Use a Template
Click on the "Use Template" button to create a new document from the template. Before creating the document, you are asked to fill in the recipients for the placeholders you added to the template. Once you're ready to use the template, you can click the "Use Template" button in the top right corner.
After filling in the recipients, click the "Create Document" button to create the document in your account. Here you can choose to use the template as is, or you can replace the emails or names with different recipients as you require.
You can now decide whether to create the document as a draft, or immediately send it to the recipients for signing.
![Use an available Documenso template](/templates/documenso-use-template.webp) ![Use an available Documenso template](/templates/documenso-use-template.webp)
@ -91,7 +52,7 @@ You can also send the document straight to the recipients for signing by checkin
### (Optional) Create A Direct Signing Link ### (Optional) Create A Direct Signing Link
A direct signing link allows you to create a shareable link for document signing, where recipients can fill in their information and sign the document. See the "Create Direct Link" button in the image from [step 4](#add-placeholders-or-recipients). A direct signing link allows you to create a shareable link for document signing, where recipients can fill in their information and sign the document.
<Callout type="info"> <Callout type="info">
Check out the [Direct Signing Links](/users/direct-links) section for more information. Check out the [Direct Signing Links](/users/direct-links) section for more information.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

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