# Review Invocable Apex Use Cases

Practical chaining recipes and realistic business scenarios for Folio's invocable Apex actions in Salesforce Flow.

Practical patterns for combining Folio invocable actions in Salesforce Flow. Start with the chaining recipes for the variable mappings you'll use repeatedly, then read the business scenarios for end-to-end examples grouped by action.

If you haven't read the per-action reference yet, start with [Automate with Invocable Apex](/docs/admin/automation-invocable-apex). For failure handling, see [Handle Invocable Apex Errors](/docs/admin/invocable-apex-errors).

## Get-then-Use recipes

Most real flows look up Documents first, then act on them, which is exactly what the **Get Documents** and **Get Document Shares** actions were created for. First retrieve what you need, then pass the output into another invocable to perform an action on those records. The patterns below are the chains you'll use repeatedly. In each one, the `documentIds` Text Collection from **Get Documents** maps directly into the next action's `documentIds` input.

**Most Folio invocable inputs require Text Collection variables.** Inputs like `shareWithIds`, `recordIds`, and `byParentRecordIds` are typed as **Text Collections** and only accept a Flow Text Collection variable. They will not accept simple Text variables, Constants, formulas, or any other variable type. Declare a Text Collection variable, add one or many values to it via an Assignment element, then pass that variable into the action.

### Get Documents, then Share them

Look up Documents by filter conditions, then grant a set of Users, Queues, or Public Groups Read or Edit access to all of them in one step.

<div class="mermaid">
flowchart TB
    A["<b>Get Documents</b><br/>filter by parent record, owner, Tag, or title"] -->|documentIds| Z["<b>Share Document</b><br/>+ shareWithIds: Text Collection of User and/or Group IDs<br/>+ accessLevel: Read or Edit"]
</div>

### Get Documents, then Apply a Tag to them

Look up Documents by filter conditions, then apply one or more Tags to all of them so they can be grouped, searched, and filtered together on the Folio Docs home page.

<div class="mermaid">
flowchart TB
    A["<b>Get Documents</b><br/>filter by parent record, owner, Tag, or title"] -->|documentIds| Z["<b>Apply Tag to Document</b><br/>+ tagNames: single Text, comma-separated"]
</div>

**`tagNames` is a single Text value, not a Text Collection.** Pass one comma-separated string — `Priority, Renewal, Q3` — not a Flow Text Collection. This is the one Folio input that breaks the collection convention. Existing Tags are matched by name and reused; unmatched names create a new Tag.

Idempotent — safe to rerun if the flow loops or retries.

### Get Documents, then Link them to Salesforce records

Look up existing Documents by filter conditions, then attach them to one or more additional Salesforce records so they surface on those records' pages too.

<div class="mermaid">
flowchart TB
    A["<b>Get Documents</b><br/>filter by parent record, owner, Tag, or title"] -->|documentIds| Z["<b>Link Documents to Records</b><br/>+ recordIds: Text Collection of Salesforce record IDs"]
</div>

The target object type must be configured as a Linkable Object in the Admin Panel.

### Get Documents, then Transfer their Owner

Look up Documents by filter conditions (typically by current owner), then bulk reassign ownership to a different user, optionally retaining Read or Edit access for the prior owner during the transition.

<div class="mermaid">
flowchart TB
    A["<b>Get Documents</b><br/>filter by current owner (or any other filter)"] -->|documentIds| Z["<b>Transfer Document to Owner</b><br/>+ newOwnerId: User ID of the new owner<br/>+ priorOwnerAccess: Read, Edit, or None"]
</div>

### Get Documents, then Clone them

Look up Documents by filter conditions, then create copies of all of them — optionally with their relationships preserved and ownership reassigned to a different user — so a team has pre-populated starting points instead of blank Documents.

<div class="mermaid">
flowchart TB
    A["<b>Get Documents</b><br/>filter by parent record, owner, Tag, or title"] -->|documentIds → sourceDocumentIds| Z["<b>Clone Document</b><br/>+ cloneRelationships: true / false<br/>+ newOwnerId: User ID of the clone's owner (optional)"]
</div>

**Get Documents' `documentIds` output feeds Clone Document's `sourceDocumentIds` input directly** — no Assignment or transformation element in between. It's the most natural pairing in the whole action set.

**Set the clone flags you actually want.** All four of Clone Document's optional inputs default to `False`, so a bare call copies content only — no Tags, no shares, no linked records. For a "pre-populated starting point" use case you almost always want **Clone Relationships** set to `True`, and often **Clone Tags** as well.

**`newDocumentIds` comes back in the same order as `sourceDocumentIds`.** That positional guarantee is what makes this chainable: index *n* of the output is the clone of index *n* of the input, so you can correlate each clone with its source in a downstream loop.

If you only want to clone the most recent matching Document, pair this with a **Get Records** step on `folio__Document__c` ordered by `LastModifiedDate DESC` and capped to 1 — Get Documents itself does not provide a sort/limit interface.

### Clone Documents, then Share, Tag, or Link the new ones

**Clone Document** returns `newDocumentIds`. Use it as the `documentIds` input on any modify-action — share the new clones with a group, apply Tags, link them to additional records, etc.

<div class="mermaid">
flowchart TB
    A["<b>Clone Document</b>"] -->|newDocumentIds| Z["<b>Any modify-action</b><br/>Share Document, Apply Tag to Document,<br/>or Link Documents to Records<br/>+ remaining inputs for the chosen action"]
</div>

The same pattern works for **Create Document from Template** — feed its `newDocumentIds` into any modify-action's `documentIds`.

### Get Document Shares, then downgrade everyone to Read except the Owner

Look up existing share rows on a set of Documents, filter the result down to rows where the user is **not** the record owner and the access level is **Edit**, then update those rows to **Read** access. Useful for locking down a Document set after a project closes, or for enforcing a periodic access review.

Because **Share Document** is additive only and never downgrades, the actual downgrade has to be performed via a Salesforce **Update Records** step on the `folio__Document__Share` records returned by **Get Document Shares**.

<div class="mermaid">
flowchart TB
    A["<b>Get Document Shares</b><br/>byDocumentIds"] -->|documentShareRecordsNoOwners| F["<b>Filter in Flow</b><br/>AccessLevel = Edit"]
    F -->|matching share rows| Z["<b>Update Records</b> (Salesforce standard action)<br/>Object: folio__Document__Share<br/>Set AccessLevel = Read"]
</div>

**Use the `documentShareRecordsNoOwners` output and skip the owner filter.** That output already excludes rows whose `RowCause` is `Owner`, so there's no need to compare `UserOrGroupId` against `OwnerId` in Flow. Owner share rows can't be modified by anyone, and including them fails the Update Records step.

### Get Document Shares, then revoke access entirely

Remove access outright rather than reducing it — the standard offboarding or access-review pattern.

<div class="mermaid">
flowchart TB
    A["<b>Get Document Shares</b><br/>byDocumentIds<br/>(optionally byUserIds to target specific people)"] -->|documentShareRecordsNoOwners| Z["<b>Delete Records</b> (Salesforce standard action)<br/>Object: folio__Document__Share"]
</div>

Deleting the share row removes the access outright, rather than reducing it as the downgrade recipe above does.

> **Use the No Owners output.** Owner share rows can't be deleted, and including them causes the Delete Records step to fail.

Note what this does and doesn't reach: access granted by other means — auto-share from record ownership, [team sharing](/docs/admin/admin-panel/settings#automatic-team-sharing), or Salesforce sharing rules — is recreated by the platform or the package and will reappear. This recipe removes **explicitly granted shares**. See [Update Data in Bulk](/docs/admin/bulk-data-updates#document-share-object) for the underlying schema and [Handle Invocable Apex Errors](/docs/admin/invocable-apex-errors) for failure handling.

### Get Document Junctions, then remove a Tag from Documents

Unlink a Tag from a set of Documents without touching the Tag itself.

<div class="mermaid">
flowchart TB
    A["<b>Get Document Junctions</b><br/>junctionType = Tag<br/>+ byTagNames"] -->|junctionRecords| Z["<b>Delete Records</b> (Salesforce standard action)"]
</div>

Deleting a Tag junction unlinks the Tag from that Document without touching the Tag record itself, or any other Document that carries it.

For a one-off cleanup, the [Tags tab](/docs/admin/admin-panel/tags) does the same thing in the UI, with merge and replacement-tag handling built in. Use this recipe when the removal needs to be automated or conditional. See [Update Data in Bulk](/docs/admin/bulk-data-updates#junction-object) for the Junction schema.

### Get Document Junctions, then unlink a record

Remove a Document's link to a Salesforce record.

<div class="mermaid">
flowchart TB
    A["<b>Get Document Junctions</b><br/>junctionType = record link<br/>+ byDocumentIds and/or byLinkedRecordIds"] -->|junctionRecords| Z["<b>Delete Records</b> (Salesforce standard action)"]
</div>

**Linking is additive — Link Documents to Records never removes an existing link — so unlinking is always a separate deletion step.** There is no "unlink" invocable, and this is why.

Every other relationship on the Document — Tags, other linked records — is untouched. This is the deletion half of [Re-link Documents when an Opportunity moves Accounts](#re-link-documents-when-an-opportunity-moves-accounts); pair the two when a record needs to move rather than merely gain a link. See [Update Data in Bulk](/docs/admin/bulk-data-updates#junction-object) and [Handle Invocable Apex Errors](/docs/admin/invocable-apex-errors).

### Create Documents from a Template in bulk

Instantiate a template across an entire collection of records in a single call.

<div class="mermaid">
flowchart TB
    A["<b>Get Records</b><br/>(or any collection of record IDs)"] --> B["<b>Transform</b><br/>into a Text Collection"]
    B -->|sourceRecordIds| C["<b>Create Document from Template</b><br/>+ templateId"]
    C -->|newDocumentIds| Z["<b>Any modify-action</b><br/>Share, Tag, or Link"]
</div>

`sourceRecordIds` is a Text Collection, and the action creates **one Document per source record** — so a single call can instantiate a template across a whole set of records. Merge fields resolve per record.

**`newDocumentIds` comes back in the same order as `sourceRecordIds`**, so index *n* of the output corresponds to index *n* of the input. That positional guarantee makes the results correlatable in a downstream loop, exactly the way [Clone Document](#get-documents-then-clone-them) works.

The template must be in **Active** status — see [Template Status](/docs/admin/admin-panel/templates#template-status). You'll also need its ID, available from **Copy ID** on the template's row in the [Templates tab](/docs/admin/admin-panel/templates#the-template-list).

### Common chaining mistakes

- **Empty/null collections.** Passing an empty `documentIds` from an upstream Get is a no-op, but make sure your Decision node checks for emptiness so the flow logs reflect "no Documents matched" rather than silently doing nothing.
- **Wrong ID type.** `shareWithIds` accepts User IDs, Queue IDs, and Public Group IDs. The error is explicit: *"Only public groups (Type = "Regular") are allowed as share targets… Queues, Roles, Role-and-Subordinates groups, and Customer Portal groups are not supported here."* It names the offending group, its ID, and its type.
- **Draft or Archived template IDs.** Only templates in **Active** status can instantiate a Document. Passing a Draft or Archived `templateId` fails at runtime, not at design time — a common surprise when a template is built and tested but never activated. See [Template Status](/docs/admin/admin-panel/templates#template-status).
- **Sharing with inactive or unlicensed users.** `shareWithIds` validates every user target: each must be active and hold a Folio permission set — **Folio Docs User** or **Folio Docs Administrator**. In the custom user lookup recipe, a stale lookup pointing at a departed user fails the whole call. Add a Decision or a Get Records filter on `IsActive` before building the collection. The same active-plus-licensed rule applies to `newOwnerId` on **Transfer**, **Clone**, and **Create from Template**. It does *not* apply to `priorOwnerAccess` — retaining access for a now-deactivated prior owner is fine.
- **Overbroad filters.** Leaving every Get Documents filter empty returns empty (by design). Always supply at least one filter.
- **Mixing record-collection loops with text-collection inputs.** If you have a Record Collection from a Get Records step, use a **Transform** element to map the record IDs into a Text Collection — don't try to map the record collection directly into a `documentIds` slot.

## Business use cases

The recipes below are examples of real business problems and how Folio's invocable Apex actions can be combined in Flow to solve them. Each example highlights a primary action, but most production flows chain several together — use these as starting points and adapt the inputs and triggers to fit your org's processes.

### Account Plan refresh

Functionally, this flow **freezes last year's Account Plan**, **spins up a fresh one from a clone of it**, and **notifies the owner** to start updating the new copy — all 90 days before the Account's renewal.

- **Trigger:**  
  Scheduled flow that runs daily and identifies any Account whose renewal date is exactly 90 days out.
- **Step 1 — Find the most recent Account Plan.**  
  Call **Get Documents** filtered by `byParentRecordIds = {Account Id}` and `byTags = ["Account Plan"]`. Use a follow-up **Get Records** on `folio__Document__c` ordered by `CreatedDate DESC` and limited to 1 to pick the most recent matching Document.
- **Step 2 — Clone it into a new Account Plan.**  
  Call **Clone Document** with `sourceDocumentIds = {That Doc Id}`, `cloneTags = true` and `cloneRelationships = true` so the new clone inherits the `Account Plan` Tag and the existing Account record link, and `cloneSharing = false` so the new Document starts shared only with the owner by default (plus any Account Team sharing that applies automatically).
- **Step 3 — Lock down the original.**  
  Use the [Get Document Shares, then downgrade everyone to Read except the Owner](#get-document-shares-then-downgrade-everyone-to-read-except-the-owner) recipe on the original Document so the prior year's plan can no longer be edited by anyone except the Owner. Note this doesn't technically prevent the Owner from editing the Document. If the original truly needs to be fully locked, transfer it to a Salesforce Administrator via **Transfer Document to Owner** with `priorOwnerAccess = "Read"` so the Account owner retains read access but loses edit access.
- **Step 4 — Mark the original as historical.**  
  Use Salesforce **Update Records** on the original Document to prepend `Old — ` (or your preferred convention) to its `folio__Title__c` so it's clearly archived. Optionally apply a `Historical` Tag as per your organization's preferences using **Apply Tag to Document**.
- **Step 5 — Email the owner.**  
  Pull the new Document's **Document Deep Link** field and email it to the Account owner. The Document Deep Link opens the new Document directly inside the Folio Docs home page, so the owner doesn't have to hunt for it.

### Create a Close Plan on Stage change

When an Opportunity moves to a late-stage like Legal Negotiation, sales reps benefit from a structured Close Plan tied to the deal. This pattern instantiates a Close Plan Template the moment the Opportunity hits that stage, sets the Opportunity owner as the Document owner, and emails them a deep link to start filling it in.

- **Trigger:**  
  Record-triggered flow on Opportunity (`StageName` becomes "Legal Negotiation").
- **Step 1 — Instantiate the Close Plan Template.**  
  Call **Create Document from Template** with `templateId = {Close Plan Template}`, `sourceRecordIds = {Opportunity Id}` (Text Collection of one), and `newOwnerId = {Opportunity.OwnerId}`. The Close Plan Template should have a `Close Plan` Tag configured on it so the new Document inherits the Tag automatically. With the right Admin Panel settings, the rest of the linking and sharing happens for you with no extra Flow steps:
    - The new Document is **default-linked to the Opportunity** (the Source Record).
    - If **Auto-link from Opportunity to Account** is enabled, the package also links the new Document to the Opportunity's parent Account.
    - If **Auto-Share Level with Record Owner** on the Account Linkable Object is set to **Read** or **Edit**, the Account Owner is automatically granted that level of access on the new Document as well.
  
  Net effect: the Document is created from the Template, linked to both the Opportunity and the Account, and shared with both the Opportunity Owner and the Account Owner — all automatically.
- **Step 2 — Email the Opportunity owner.**  
  Send an email to the Opportunity owner prompting them to complete their Close Plan by the target close date. Include the new Document's **Document Deep Link** field in the email body so they can click to easily access the Document on the Folio Docs home page without hunting for it.
- **Business outcome:**  
  Every late-stage Opportunity has a structured Close Plan ready for the rep the moment they need it, pre-tagged for downstream filtering and reporting on the Folio Docs home page.

### Share Case Documents on escalation

- **Trigger:**  
  Record-triggered flow on Case (`IsEscalated` becomes `true`).
- **Inputs used:**  
  `documentIds = {Documents linked to Case}`, `shareWithIds = {Support Manager Group ID}`, `accessLevel = "Edit"`.
- **Action sequence:**  
  Get Documents (`byParentRecordIds = {Case Id}`) → Share Document.
- **Business outcome:**  
  Escalation managers get immediate access to all Case context without manual sharing.

### Share Documents with users in a custom user lookup field

It's common practice to track role-specific ownership on a record using a **custom user lookup** that's distinct from the standard Owner — for example a **Customer Success Manager**, **Solution Consultant**, **Implementation Manager**, or **Renewal Manager** on an Account, Opportunity, or Case. Because these users aren't the record Owner, Folio's built-in auto-share rules and Account Team sharing don't always cover them. To grant read or edit access to the user sitting in any custom user-lookup field, use the pattern below.

- **Trigger:**  
  Record-triggered after-save flow on `folio__Junction__c` (the object that links a Document to a record), firing on **Created** events.
- **Step 1 — Filter to the right parent object.**  
  Add a Decision element that checks whether the new Junction's parent record ID begins with the Salesforce key prefix for the object you care about (e.g. `001` for Account, `006` for Opportunity, `500` for Case). If not, exit the flow.
- **Step 2 — Look up the parent and the custom user-lookup field.**  
  Use a Salesforce **Get Records** on the parent object, filtered by the Junction's parent record ID, retrieving the custom user-lookup field(s) you want to share with — Customer Success Manager, Solution Consultant, Implementation Manager, Renewal Manager, or any other role-specific user lookup.
- **Step 3 — Share the Document.**  
  Build a Text Collection containing the user IDs from those lookup fields, then call **Share Document** with `documentIds = {The Junction's Document Id}`, `shareWithIds = {User Id Collection}`, and `accessLevel = "Edit"` (or `"Read"` depending on your access policy).
- **Business outcome:**  
  Every Document attached to the parent record is shared with the role-specific users the moment it's linked, without manual sharing or relying on Account Team membership. The same pattern works for any custom user-lookup field on any Linkable Object.

### Tag Account Documents by industry

- **Trigger:**  
  Record-triggered flow on Account (`Industry` field change).
- **Inputs used:**  
  `documentIds = {Documents linked to Account}`, `tagNames = "{Industry name}"`.
- **Action sequence:**  
  Get Documents → Apply Tag to Document.
- **Business outcome:**  
  Documents become filterable on the Folio Docs home page by industry without manual tagging.

### Re-link Documents when an Opportunity moves Accounts

- **Trigger:**  
  Record-triggered flow on Opportunity (`AccountId` change).
- **Inputs used:**  
  `documentIds = {Documents linked to Opportunity}`, `recordIds = {New AccountId}`.
- **Action sequence:**  
  Get Documents → Link Documents to Records.
- **Optional — remove the link to the prior Account at the same time.**  
  Linking the Documents to the new Account does not remove the existing link to the prior Account, so by default the Documents stay linked to both. If you want the move to also drop the old link, follow the link step with a Salesforce **Get Records** on `folio__Junction__c` filtered by Document = {Documents linked to Opportunity} AND Linked Record = {Prior AccountId}, then **Delete Records** on the returned Junctions. Every other relationship on those Documents (Opportunity, Tags, other linked records) is left untouched.
- **Business outcome:**  
  Existing Opportunity Documents now appear in the Folio Document Editor component on the new Account's record page — and, if you opt into the cleanup step, no longer appear on the previous Account's record page.

### Reassign Documents when a rep leaves

- **Trigger:**  
  Screen flow run by an Ops admin from the user's record page.
- **Inputs used:**  
  `documentIds = {Documents owned by departing user}`, `newOwnerId = {New owner User Id}`, `priorOwnerAccess = "Read"`.
- **Action sequence:**  
  Get Documents (`byOwnerIds = {departing user}`) → Transfer Document to Owner.
- **Business outcome:**  
  Ownership transitions cleanly with a Read window for the original owner during handoff.

### Create a Sales-to-CS Handoff on close

- **Trigger:**  
  Record-triggered flow on Opportunity (`StageName` becomes "Closed Won").
- **Inputs used:**  
  `templateId = {Sales-to-CS Handoff Template}`, `sourceRecordIds = {Opportunity Id}`, `newOwnerId = {Assigned CSM Id}`.
- **Action sequence:**  
  Create Document from Template → Apply Tag (`Handoff`) → Share Document with the CS team Group.
- **Business outcome:**  
  A pre-filled handoff doc is waiting for the CSM the moment the deal closes.

### Keep Documents current when a custom object changes

- **Trigger:**  
  After-save record-triggered flow on a custom object (e.g., `Project__c`), configured for **Updated** and/or **Deleted**.
- **Inputs used:**  
  `$Record` → **Record (New State)**; `$Record__Prior` → **Record (Prior State)**. In a **Deleted** flow, also set **Record Was Deleted = True**.
- **Action sequence:**  
  Folio: Refresh Document from Record Changes.
- **Prerequisite:**  
  The object must be configured as a **Linkable Object**. For the owner re-sharing half of this to do anything, its **Auto-Share Level with Record Owner** must be **Read** or **Edit** — with auto-share set to **None**, field updates still propagate but no sharing is applied.
- **Business outcome:**  
  Every Document linked to the project stays current with the project record — names on Record Links, and values in Related Lists, Workbenches, Status Bars, Record Previews, and Kanban tiles all update live for anyone viewing. When the project changes owner, linked Documents are re-shared to the new owner at the configured level.
- **Note:**  
  This action replaces the former **Apply New Owner Sharing**, which only handled the sharing half. It is also the only Folio invocable that lives in a flow on the *linked record* rather than a flow about Documents. **Account**, **Contact**, **Opportunity**, and **Case** need no flow at all — they ship with packaged triggers. See [Set up Real-Time Updates](/docs/admin/real-time-updates).

### Clean up Documents when a parent record is retired

- **Trigger:**  
  Record-triggered flow on the parent object when a status field moves to a terminal value (e.g., `Project__c.Status__c = 'Cancelled'`).
- **Inputs used:**  
  Text Collection of Document IDs from **Get Documents** filtered by the parent record.
- **Action sequence:**  
  Get Documents → Folio: Delete Document.
- **Business outcome:**  
  Documents tied to the retired record are removed — or **archived**, if [hard delete is disabled](/docs/admin/admin-panel/settings#delete-permissions), which is the default. Using the invocable rather than a raw **Delete Records** element is what makes that distinction work, and what keeps Junctions from being orphaned. See [Updating and deleting Folio data](/docs/admin/automation-invocable-apex#updating-and-deleting-folio-data).
- **Note:**  
  **Delete Document** returns `archivedInsteadOfDeleted` (Boolean) alongside `success`. Branch on it to tailor what happens next — logging, notifying, or a different follow-up when the org's **Allow Document Hard Delete** setting caused an archive rather than a delete. This is how a flow tells the two outcomes apart, since both return `success = true`.

### Audit which Documents carry a given Tag

- **Trigger:**  
  Scheduled flow, or an on-demand screen flow.
- **Inputs used:**  
  `junctionType` = Tag links; `byTagNames` = the Tag(s) you're auditing.
- **Action sequence:**  
  Folio: Get Document Junctions → loop / report over `junctionRecords`.
- **Business outcome:**  
  A list of every Document carrying a governance-relevant Tag.
- **Note:**  
  Take the `junctionRecords` output rather than `junctionIds` — it carries the field values a report needs, with no separate **Get Records** step. To remove the Tag rather than report on it, see [Get Document Junctions, then remove a Tag from Documents](#get-document-junctions-then-remove-a-tag-from-documents).

**Related:** [Automate with Invocable Apex](/docs/admin/automation-invocable-apex) · [Handle Invocable Apex Errors](/docs/admin/invocable-apex-errors) · [Manage Templates](/docs/admin/admin-panel/templates) · [Set up Real-Time Updates](/docs/admin/real-time-updates) · [Update Data in Bulk](/docs/admin/bulk-data-updates)
