Documents

A shared file library with favorites, labels, search, and AI extraction that reads PDFs and fills a form for you.

The document explorer is a flat, searchable library for uploaded files. A file can be standalone or attached to a record, such as a job, purchase order, or RFQ. Attached files appear on both the record and in the explorer. The library supports personal pins and labels, name and description search, and AI extraction that reads supported PDFs and prefills forms.

Open the explorer from Documents in the sidebar. It defaults to the All Documents view (useDocumentsSubmodules.tsx:15-45).

The explorer

The table lists accessible files with pinned files first (documents.service.ts:86-88). Each row shows the name, linked source document, personal labels, size, type, extension, and created and updated metadata (DocumentsTable.tsx:174-405).

Five views scope the list, switched by the tab rail down the left (useDocumentsSubmodules.tsx:15-45):

FieldType
Every active file in the company you have read access to.
Files you uploaded (createdBy = you).
Sorted by last activity — the newest view, download, label, or edit first.
Only files you have pinned.
Soft-deleted files, waiting to be restored or purged.

Type is derived, not chosen. When a file is uploaded, Carbon reads its name and classifies it into one of ten buckets: Archive, Document, Presentation, PDF, Spreadsheet, Text, Image, Video, Audio, or Other (20230423023136_documents.sql:1-12). The extension column is computed by the database from the filename itself (the text after the last dot), so it always matches the real file (20230423023136_documents.sql:20).

NOTE

Trash is a soft delete, not a purge

"Move to Trash" only flips the file's active flag to false — the row and the stored file both stay put, and "Restore from Trash" flips it back (documents.service.ts:124-152). To actually remove a file, use Permanently Delete from the row menu; that one cannot be undone (DocumentsTable.tsx:461-471).

Uploading

Upload accepts any file. The browser uploads it to private storage at {companyId}/{random}.{ext}, then creates a document row with its path, name, and size (DocumentCreateForm.tsx:22-69). Uploads, downloads, edits, favorites, and label changes create activity transactions that drive the Recent view (useDocument.ts:44-57, 20230423023136_documents.sql:117-146).

Who can see a file

Visibility depends on group membership and permissions. A user can view a document only when they belong to one of its readGroups and have documents view permission. Editing or deleting also requires membership in a writeGroups entry and the corresponding permission (20230423023136_documents.sql:40-66, useDocument.ts:22-42). Private storage applies the same read-group rule, so unauthorized links do not resolve (20230423023136_documents.sql:68-119).

NOTE

Groups are Carbon's company groups

Read and write groups are drawn from your membership, the same groups used elsewhere for scoping. Set them when you edit a document's properties; a file with no matching read group for a user is simply invisible to that user.

Pins and labels

Two personal organizers sit on top of the shared library, and both are per-user — what you pin or label is yours alone, not the whole company's.

  • Pins (shown as a pushpin, called "Favorite" in the row menu) float a file to the top of your list and power the Pinned view. Toggling one writes or removes a documentFavorite row keyed to you (useDocument.ts:113-130, documents.service.ts:199-219).
  • Labels are free-text tags you attach to a file. Type into the label popover on a row to reuse an existing label or create a new one on the fly; click a label to filter the list down to it (DocumentsTable.tsx:239-296, useDocument.ts:142-164). Labels are stored per user, so your "Q3-audit" label doesn't clutter a colleague's view (20230423023136_documents.sql:186-215).

Search performs a case-insensitive substring match on name and description, not file contents (documents.service.ts:72-76). Combine search with filters for source document, label, type, extension, or creator and with the view tabs. Download CSV exports the current result set with a signed download link for each row (search.tsx, DocumentsTable.tsx:393-405).

How documents attach to records

A document can point back at the record it belongs to. The row's Source Document column shows that link and jumps straight to the parent record (DocumentsTable.tsx:207-237). The set of record types a document can attach to is fixed (documents.models.ts:9-23):

FieldType
Quote, Request for Quote, Sales Order, Sales Invoice, Sales Return Order, Shipment.
Purchase Order, Purchase Invoice, Purchase Return Order, Purchasing Request for Quote, Supplier Quote.
Job, Gauge Calibration Record, Issue.
Part, Material, Tool, Consumable, Service.
Supplier, Customer. These are the master records rather than transactions, for files that belong to the party itself.

The attachment is stored as a sourceDocument (the type) plus a sourceDocumentId (the record's id) on the file (documents.service.ts:154-183). Because the file also lives in the shared explorer, you can find it either by opening the record or by searching the library. See the sales orders, purchase orders, and jobs references for where each record surfaces its files, and suppliers & customers for the party-level Documents tab.

AI extraction

In supported workflows, Carbon can extract fields from a PDF and prefill a form. Two document types are supported (document-extraction.ts:9-13, documents.models.ts:126):

  • a Request for Quote PDF, on the sales side, and
  • a Purchase Invoice PDF, on the purchasing side.

What it does, step by step

When you drop a PDF into one of those flows, Carbon creates a documentExtraction row (starting at status pending) and enqueues a background job (documents.service.ts:248-296, 20260609001724_add-document-extraction.sql:4-30). The job then (extract-document.ts:26-232):

  1. flips the row to processing;
  2. downloads the PDF from private storage and extracts its text with a PDF parser (this is text extraction, not image OCR — a scanned image-only PDF yields no text);
  3. loads the company's candidate records so the model can resolve names to real ids itself — suppliers and payment terms for an invoice, customers for an RFQ (extract-document.ts:84-140);
  4. sends the text plus those candidate lists to the AI model, which returns every field as a { value, confidence } pair (schemas.ts:1-88);
  5. filters by confidence: any field scoring below the threshold (default 0.85) is dropped to null, so a low-confidence guess never lands in your form (extract-document.ts:184-224, packages/env/src/index.ts:173-178); and
  6. normalizes dates to YYYY-MM-DD and saves both the raw output and the filtered result on the row, status completed (extract-document.ts:184-232).

The form then reads the filtered values and pre-fills itself, matching extracted contacts and addresses against the customer's or supplier's existing records where it can (documents.models.ts:168-255).

NOTE

It resolves ids for you, it doesn't invent them

The model is handed the actual list of your suppliers, payment terms, or customers and told to return the id of the single best match or null — never to make one up (extract-document.ts:141-152). So supplierId / customerId / paymentTermId come back either as a real record you already have or as blank, and the form's own matching fills the rest.

What it pulls out

For a Purchase Invoice, extraction targets the supplier (name, contact, address, and a resolved supplierId), the invoice header (number, invoice and due dates, payment terms and a resolved paymentTermId, referenced PO number, currency), the money (subtotal, tax, shipping, total), and the line items (part number, description, quantity, unit price, line total) (schemas.ts:20-54).

For a Request for Quote, it targets the customer (name, a resolved customerId, plus separate purchasing and engineering contacts and the customer address), the RFQ header (number, RFQ date, due date, requested delivery date), and the line items (part number, description, quantity) (schemas.ts:63-86).

HEADS UP

Extraction is a draft, not a commit

A documentExtraction row is only a staging area — it never creates an invoice or an RFQ by itself. The extracted values pre-fill the normal form, and the real record is created through the usual permission-gated route action when you save (20260609001724_add-document-extraction.sql:37-39). If the background job can't be enqueued or the parse fails, the row is marked failed with the error, and you just fill the form manually (documents.service.ts:272-295, extract-document.ts:225-231).

Extraction quality depends on the PDF being text-based and on the AI keys being configured for your deployment; without them the flows still work, you simply type the fields yourself.

NOTE

Not the document template customizer

This page is about the file library. The separate document template feature — the drag-and-drop customizer for the PDFs Carbon generates (quotes, order confirmations, invoices) — is a different subsystem and isn't covered here.