MetaSync for Confluence — Documentation
Living Salesforce documentation in Confluence

← Documentation home

Concepts


How syncing works

MetaSync keeps your Confluence documentation current by running a background sync engine on Atlassian’s Forge platform. You never have to leave Confluence running or keep a browser open — the sync happens on Atlassian’s servers on a fixed schedule. This page explains how a sync actually runs, why very large orgs finish over several cycles, and what the Up to date status means.

The scheduled trigger (every 5 minutes)

Forge fires a scheduled trigger for MetaSync every 5 minutes (interval: fiveMinute in the app manifest). Each firing runs the sync handler. If there is nothing to do — no queued work and no schedule due — the handler exits almost immediately, so the 5-minute cadence is cheap when the org is idle.

Note — Why 5 minutes. The 5-minute tick is the heartbeat of the whole engine. Both scheduled jobs (see Scheduled Syncs) and any pending manual sync you kick off are picked up on the next tick, then carried forward tick-by-tick until the work is done.

Work is split into queue items

A sync is not one giant job. When a run starts, MetaSync writes a queue of small work items, and each item covers one slice of your org’s metadata. The engine pops items off the queue one at a time and publishes their pages. Splitting the work this way keeps any single unit small enough to finish inside a strict time limit.

The queue item types are, in the order the engine generally works through them:

Queue item types (from SyncQueueItemType in sync-orchestrator.ts)

Queue item What it covers
objects Custom & standard objects; also seeds the field-page work
fieldPages Individual field detail pages (paginated across ticks)
flows Flows and Validation Rules
apex Apex Classes and Apex Triggers
profiles Profiles (paginated)
permSets Permission Sets (paginated)
reports Reports (paginated)
miscSecurity Roles, Permission Set Groups, Muting Permission Sets, Sharing / Assignment / Approval / Workflow rules
miscIntegration Connected Apps, External Client Apps, Named Credentials, Remote Sites, Auth Providers, Custom Labels, Custom Metadata Types
miscUI Dashboards, Tabs, Apps, Quick Actions, List Views, Field Sets, Report Types
miscLayouts Page Layouts and Lightning Pages (heavy publish — isolated)
miscCustomSettings Custom Settings and Global Value Sets (heavy extract — isolated)
dataDictionary The cross-object Data Dictionary page (see Data Dictionary)
coverage The Documentation Coverage page (see Documentation Coverage)
dataSecurity The Data Security Posture page — detection, gaps, drift (see Data Security & Classification)
changeTimeline The Setup Audit Trail page (from the Setup Audit Trail)
diagrams Flow-diagram SVG uploads and the Entity Relationship Diagram page. Runs last, after the flow pages and the object graph exist, and is isolated on its own budget so per-page attachment uploads can never stall the core sync

Note — Why org-config is in several ‘misc’ groups. MetaSync documents ~23 org-configuration types. They are split across the five weight-balanced misc groups above so that no single queue item has to hold them all — on a large org that would exceed the memory and time available to one invocation. Each group is its own queue item with its own time budget.

The per-invocation time budget

The sync handler is given up to 290 seconds per firing (timeoutSeconds: 290 in the manifest). That is deliberately just under the 5-minute (300-second) tick, so one firing always finishes before the next one starts — the engine never runs two copies at once, which means no locking is needed.

Within a single firing the engine chains through as many queue items as it safely can. It stops starting new items once it crosses a 240-second soft ceiling, and before starting each additional item it also requires headroom of at least ~50 seconds (or 1.5× the last item’s duration, whichever is larger). Whatever is left on the queue is simply carried to the next 5-minute tick.

Tip — Large orgs complete over multiple cycles. Because leftover items roll forward, a big org isn’t a problem — it just takes more than one 5-minute cycle to finish the first full sync. Each cycle publishes more pages until the queue is empty. You can watch this progress live (see Scheduled Syncs for scheduled-run visibility).

Note — Self-healing on interruptions. If Forge ever hard-kills a firing part-way through a heavy item, that item is left at the front of the queue with an attempt counter. The next tick retries it. After 2 failed attempts the item is skipped so it can’t block the rest of the sync, and the run is marked partial rather than failed (see below). See Sync appears stuck or partial if a sync appears stuck.

Run status: success, success with exception, partial, failed

Note — A permission you haven’t granted doesn’t make every run partial. A component MetaSync is permanently barred from reading (an access error — it will fail identically every run) is the one loss that does not degrade the status. It is disclosed on that type’s index instead (see “Not captured” on published pages), because otherwise a permission you never intend to grant would pin every future run at partial, withhold the up-to-date stamp, and force a full rewrite of the space every time. A transient loss still degrades to partial, because a retry can fix it.

Warning — Degraded runs: one type fails, the rest still publish. If a single metadata type throws during extraction, MetaSync demotes just that type instead of treating an empty result as fact. Its pages and its entry in the change snapshot keep the previous sync’s state, its incremental watermark is held so the next run re-scans it, and no removals are reported for it — which is what stops one bad extract from bannering every component of that type as deleted. The run is then recorded with an error naming the type and cause (e.g. “Extraction failed for: customLabels (…)”) rather than as a clean success. Everything else in the run publishes normally. See Sync appears stuck or partial.

What “Up to date” means

Up to date simply means the work queue is empty and the last run finished — there is nothing waiting to publish. It does not mean MetaSync polled Salesforce this second; it means the most recent completed sync reflects your org, and the next scheduled run will pick up any changes since then. On subsequent syncs MetaSync publishes only what changed (a delta), so staying up to date is fast and cheap.

Manual sync vs scheduled sync

There are two ways work lands on the queue, and both are then processed by the very same 5-minute engine:

Note — A manual sync isn’t instant. Because everything is driven by the 5-minute trigger, even a manual sync may take up to ~5 minutes to start, and a first full sync of a large org then continues over several cycles. This is normal — it is the background engine at work, not a hang.


Page structure: per type vs per component

MetaSync can publish your metadata documentation in one of two page-tree shapes. You choose the shape in the admin app under Configuration, and it is stored as the Page structure setting (PageTreeStyle in the code). The two modes are Per component (the default) and Per type.

Note — In short. Per component = one Confluence page per object/flow/profile (deep, browsable, linkable). Per type = one index page per metadata category that tabulates everything (flat, compact). They do NOT publish the same content: Per type publishes the index pages only, so everything that lives on a detail page — object field tables, flow logic steps and diagrams, profile and permission-set permission matrices, layout sections, Apex source, report columns — is not published at all.

Per component (default)

Per component builds the full tree: a category index page for each metadata type, and beneath it one detail page per component. This is the richest layout — every object, field, flow, profile and so on has its own Confluence page that you can link to directly, comment on, and reference from governance reports and change-impact listings.

Per-component tree (worked example)

MetaSync Home
├── Schema                       (CATEGORY page)
│   ├── Objects                  (type index — table of all objects)
│   │   ├── Account              (detail page)
│   │   ├── Contact              (detail page)
│   │   └── Longevity Assessment (detail page)
│   │       ├── Field: Status         (field detail page)
│   │       └── Field: Approval Date  (field detail page)
│   └── Record Types             (type index)
├── Automation                   (CATEGORY page)
│   └── Flows                    (type index)
│       └── Send Invoice Email   (detail page)
├── Security & Access            (CATEGORY page)
│   └── Profiles                 (type index)
│       ├── System Administrator (detail page)
│       └── Standard User        (detail page)
└── Entity Relationship Diagram  (direct child of Home — not in a category)

Per type (flat)

Per type publishes only the category index pages and skips the per-component detail pages. Each index page tabulates every component of its type — Name, API name, Status and Last modified — so the INVENTORY is complete. The detail is not: choosing Per type means giving up field tables, flow logic, permission matrices, layout sections and Apex source entirely, not relocating them. Three categories keep one grouping level below their index, because their components are already filed that way in Salesforce: Validation Rules gets a page per object, and Reports and Dashboards get a page per folder. Record Types get an index page like every other type; per-record-type detail (picklist overrides, page-layout assignments) is documented on object pages, which Per type does not publish. It is the right choice when you want a compact register of what exists, and the wrong one when the space is meant to answer questions about how a component is configured.

Per-type tree (same org, worked example)

MetaSync Home
├── Objects            (single page — table lists Account, Contact, Longevity Assessment, …)
├── Flows              (single page — table lists Send Invoice Email, …)
├── Profiles           (single page — table lists System Administrator, Standard User, …)
├── Record Types       (single page — table lists Account — Business, Case — Support, …)
├── Validation Rules   (index page, plus one page per object)
│   ├── Validation Rules: Account
│   └── Validation Rules: Longevity_Assessment__c
└── Reports            (index page, plus one page per folder — Dashboards is the same)
    ├── Reports: Public Reports
    └── Reports: MSB Reports

Which should I choose?

Choose Per component when… Choose Per type when…
You want to deep-link to a specific object or field You want a compact, at-a-glance space
You rely on change-impact links between components You have a very large org and want fewer pages
Teams comment on individual components in Confluence You mainly need a searchable reference table per type

Warning — Switching modes. Per component produces many more pages than Per type. Switching from Per component to Per type does not retroactively delete the detail pages a previous run already created — plan the switch (and any cleanup) deliberately, and see Managed regions & team annotations for how MetaSync manages the pages it owns.

Note — The preview is mode-aware. The Page-structure preview in the admin app renders the tree for the mode you have selected — a deep tree for Per component and a flat one for Per type — so you can see the resulting hierarchy before you sync. Existing orgs that never set this option are treated as Per component.


PII & data classification

MetaSync surfaces each field’s data classification so you can see, at a glance, which fields hold sensitive or regulated data. Crucially, this information comes from Salesforce — MetaSync only reads and displays it, it never sets or changes it. Classification is a governance decision you make in Salesforce; MetaSync makes it visible in Confluence.

Where the classification comes from

The source is Salesforce’s field-level governance metadata on the Tooling API FieldDefinition object (not the standard describe call). MetaSync reads three attributes per field:

How the High / Medium PII badge is derived

MetaSync collapses the two governance attributes above into a single badge — High, Medium, or none — using the derivePiiLevel() rule. The exact logic, verified against the code, is:

  1. High — if the Security Classification is Confidential, Restricted, or MissionCritical; OR if the Compliance Group contains any of PII, HIPAA, PCI, or GDPR (matched case-insensitively, as a substring, so PII;GDPR also counts).
  2. Medium — otherwise, if the Security Classification is exactly Internal, OR the Compliance Group is set to any non-empty value.
  3. No badge — the field has no High- or Medium-qualifying classification (typically Public, or nothing set at all).

Note — Worked examples. Restricted → High. Compliance Group PII;GDPR → High (regardless of classification). Internal with no compliance group → Medium. Compliance Group Marketing (a custom, non-regulated tag) → Medium (any non-empty group is at least Medium). Public with no group → no badge.

Warning — Substring matching. The regulated-group check is a case-insensitive substring test against PII, HIPAA, PCI, GDPR. So a compliance value like Contains-PII is treated as High. Keep your Salesforce compliance-group naming clean to avoid surprises.

Where the badges appear

Classification is maintained in Salesforce

Warning — MetaSync reads, never writes. If a field shows no classification, it has not been classified in Salesforce — MetaSync cannot and will not set it. To improve your badges, set Security Classification and Compliance Group on the field in Salesforce Setup; the next sync will pick them up. Classification enrichment is also best-effort: if the Tooling API query fails on a given org edition, fields simply stay unclassified rather than blocking the sync.


Managed packages & standard vs custom

Salesforce orgs contain a mix of your own configuration and components that arrived from installed managed packages (AppExchange apps). MetaSync helps you tell them apart, and by default keeps installed-package noise out of your access documentation.

Namespaced (managed-package) components

Components that belong to a managed package carry a namespace prefix — for example acme__Widget__c or acme__Admin_Perm. MetaSync uses that namespace to recognise a component as managed-package rather than org-native.

Standard vs Custom badge

Separately from managed-package status, MetaSync shows whether a component is Standard (delivered by Salesforce, e.g. the Account object) or Custom (created in the org, marked by the __c/__x suffix and Salesforce’s custom flag). This badge appears on object pages, field pages, and in the Data Dictionary so you can distinguish platform building blocks from org-specific ones.

Term Meaning
Standard Delivered by Salesforce itself (e.g. Account, Name).
Custom Created in this org (custom object/field, __c suffix).
Managed-package (namespaced) Came from an installed AppExchange package; carries a namespace prefix.

Note — Standard/Custom and managed are independent. A managed-package component is usually also “custom”, but the two ideas answer different questions: Standard vs Custom = did Salesforce or someone create it; managed vs org-native = did it arrive from an installed package or was it built here.

Managed-package filtering

By default MetaSync excludes managed-package (namespaced) components of five types from the sync — Permission Sets, Permission Set Groups, Muting Permission Sets, Lightning Web Components and Aura Components — so your documentation shows only org-native configuration and isn’t cluttered by dozens of package-owned components you don’t manage. This is controlled by the Include managed package setting (includeManagedPackage), which is off by default.

Warning — The filter covers those five types only — not every type. Every other synced type is documented regardless of namespace. Apex classes and triggers are the ones that surprise people: a managed-package Apex class gets a page like any other, so an org with installed packages sees namespaced classes in its Apex index while its Permission Sets index shows only org-native entries. The asymmetry is deliberate — package-owned permission sets are noise in an access review, whereas package Apex is often exactly what you are tracing when debugging. An index count always reflects what MetaSync documented, so compare like with like before reading a smaller total as a gap.

Setting Behaviour
Include managed package = off (default) Managed-package Permission Sets / PSGs / Muting Permission Sets / LWC / Aura bundles are skipped; only org-native components of those five types are documented.
Include managed package = on Installed-package components of those five types are documented too — useful for a full audit of everything in the org.

Note — Turn it on for a complete audit. Leave it off for clean, org-focused access docs. Turn it on when you need a comprehensive picture that includes AppExchange-delivered permissions — for example a full security or compliance review. See Governance and Field-Level Security / PII Access for the reports this feeds.


Truncation & display limits

To keep published Confluence pages fast to load and safely under Confluence’s storage-format size limits, MetaSync caps a handful of long lists. Nothing is silently dropped: wherever a list is capped, the page shows a note such as “Showing X of Y …” or moves the full list into an expandable block. The table below covers the caps that apply across types plus the ones most often asked about; a few types carry their own additional caps, and each of those is stated in that type’s Limits section in the Metadata reference. This page is not a substitute for those — it is the cross-cutting list.

Cross-cutting display caps (verified against src/confluence/publisher.ts, src/salesforce/extractor.ts and src/features/data-dictionary.ts)*

Where Limit What happens beyond the limit
Related objects on an object page 30 First 30 related (named-relationship) objects shown, then a “Showing 30 of N related objects.” note.
Referenced-by / “where used” on object pages 50 First 50 references shown, then a “Showing 50 of N references.” note.
Referenced-by / “where used” on field pages 50 First 50 references shown, then a “Showing 50 of N references.” note.
Field where-used index (data captured per field) 50 At most 50 usages are stored per field for the cross-reference index that powers the above.
Data Dictionary — single flat table 400 fields At/under 400 fields the page is one flat table; above 400 it flips to a per-object expandable layout. Both forms are drawn from at most 2,500 indexed fields — an org with more says so on the page (“showing the first 2500 here to stay within Confluence’s page-size limit”), so above that the layout note is about presentation, not completeness.
To-document checklist (Coverage page) 200 First 200 undocumented fields listed, with “Showing the first 200 of N undocumented fields.”
Least-documented objects (Coverage page) 10 Only the 10 worst-documented objects are listed (ranked ascending by documented %).
Report columns on a report page 40 40 or fewer columns render inline; above 40 the full column list moves into a collapsible expand block (none dropped).
Reports extracted per run 2,000 A single SOQL page of 2,000 reports. An org with more has reports that are not documented at all this run; hitting the cap is logged loudly rather than passed off as the whole answer.
Report column / filter / grouping detail 200 reports Those three values come from the Analytics REST describe, which runs for the first 200 reports of the run. Beyond the cap — and for any report whose describe is refused — Columns, Filters and Groupings read “Not captured”, never 0, and the Reports index states how many reports were affected.
Custom Metadata Type detail 100 types Fields and records are captured for the first 100 custom metadata types (alphabetical); the rest read “Not captured”. Within a captured type: 50 record rows, 15 custom columns, and 100 characters per cell — with the true record count still shown via a COUNT().
Permission-set assignee names 50 Display names are captured for the first 50 assignees per set (alphabetically, so the window is stable between syncs). The assignee COUNT is the full total, and the expand says “Showing X of N assignees.”
Workflow rule time-triggered actions 25 At most 25 time-triggered actions are listed across all of one rule’s time triggers, then a “Showing 25 of N time-triggered actions” note. Immediate actions are not capped.
Picklist values on a field 10 First 10 values shown, then “+N more”.
Flow-element referenced fields 5 First 5 referenced fields per flow element shown, then “+N more”.
Global Value Set values 50 First 50 values shown, then a note that further values are omitted.
Public Group / Queue members 100 At most 100 members are captured per group at extraction; the page notes “N additional member(s) omitted.”
Changelog — “Changes in latest run” table 50 The Changelog tabulates the first 50 changes of the most recent run, then “N additional changes omitted.” The Setup Audit Trail page is not capped — it tabulates every change it holds, which on a busy org is thousands of rows.
Setup Audit Trail — Live View macro tab 500 entries The macro reads from app storage, which keeps only the 500 newest Setup Audit Trail entries to stay under the per-key size limit — so the Live View tab shows fewer rows than the Confluence page built from the same sync. The banner states “Showing the N most recent of M changes” whenever the cap is in force.
Excluded-component note — names listed 25 Where an index discloses components it could not read, the note names the first 25 and then “…and N more.” The COUNT in the note is always the true total, so the disclosure never understates the gap — only the list of names is trimmed. Fires today on the Layouts, Lightning Pages, Assignment Rules and List Views indexes. (Components dropped by a scope filter use the separate note in the next row.)
Scope-exclusion note — names per reason 10 The scope-exclusion note groups excluded components by cause and names the first 10 in each group, then “…and N more”. As above, the per-reason count is the true total.
Sensitive data by object (Posture page) 100 The per-object footprint table lists the 100 objects holding the most sensitive fields; the page states how many further objects exist.
Bundle source file preview (LWC / Aura) 6,000 characters Each captured bundle file renders its first 6,000 characters and then states “showing N of M characters” — never a silent truncation.
Change Impact — changes analysed per run 100 The first 100 detected changes get a risk rating and dependency analysis. Beyond that the tab shows a “+N more changes not analyzed” banner naming the run’s true change total. The unanalysed changes are still published to your documentation pages. See Change Impact.
Change Impact — dependents / references listed per entry 20 The detail panel lists 20 of each, then “+N more dependents not listed” / “+N more references not listed”. The headline dependency count is the true total, not the listed sample.
Change Impact — attribute changes listed per entry 12 The “What changed” before/after list shows 12 attributes, then “+N more attribute changes not shown”.
Sync history runs retained 20 Only the 20 most recent runs are kept; older runs are dropped. The Sync history table pages through them 10 or 20 at a time. See Sync History.
Governance snapshots retained 4 A rolling window of the last four snapshots is kept so a previous period’s reports stay readable; the fifth-oldest has its report data purged. See Governance.

Note — Assigned profiles on layouts are NOT capped. There is no 30-item (or other) truncation on a Page Layout’s assigned-profile list — the collapsible “Assigned profiles (N)” block shows all of them. Assignments are read from the Salesforce ProfileLayout table; if that read fails the row reads “Not captured” instead of a count — see “Not captured” on published pages.

Note — Why these caps exist. Confluence stores each page as a single storage-format document with a maximum size. Extremely long tables can exceed that limit or make pages sluggish. Capping the longest lists (with a clear “showing X of Y” note) keeps every page reliable to publish and pleasant to read. For per-type vs per-component page shapes, see Page structure: per type vs per component.


“Not captured” on published pages

Some Salesforce details simply aren’t available through the APIs MetaSync reads. Where that happens, a published page says “Not captured” rather than showing 0, — or None. The distinction matters: on a profile or permission-set page a confident 0 reads as evidence that no access is granted, which is exactly the kind of false negative an auditor would act on.

Warning — What it does and doesn’t mean. “Not captured” means MetaSync does not collect this detail from Salesforce. It does not mean your org has none of it. Check the value in Salesforce Setup before drawing any conclusion. Anything MetaSync did measure and found genuinely empty still shows as None or 0.

The three states

Note — Why a source that returned nothing still reads “Not captured”. Some Salesforce columns are simply blank for whole classes of component — EntityDefinition.DeploymentStatus and LastModifiedDate, for instance, are null for standard objects, so Object: Account reads “Not captured” for both even though the query succeeded. MetaSync deliberately does not render that as None, 0 or —: it has no evidence about the real value, and a confident-looking empty answer is precisely the false assurance this convention exists to prevent. The rule is only claim what was measured — so the marker covers “asked, and got nothing usable” as well as “never asked”. It errs toward admitting a gap, never toward inventing certainty.

Any page that uses the marker carries a single footnote at the bottom repeating the definition, so a reader who lands on one page mid-audit gets the caveat without having to know this convention.

Where you’ll see it

Sections that render “Not captured” and the Salesforce source MetaSync doesn’t read

Page What reads “Not captured” Where to look in Salesforce instead
Profiles & Permission Sets Apex class access, Tab visibility and App access — now genuinely extracted (SetupEntityAccess / PermissionSetTabSetting); a section shows the marker only when that source’s query failed for the run, e.g. an org that restricts it Setup → the profile or permission set → Apex Class Access / Object Settings / Assigned Apps
Profiles Assigned users, when the user-count query fails (a genuine zero still shows as 0) Setup → Profiles → View Users
Lightning Pages Assigned apps — FlexiPage app-assignment metadata isn’t read Setup → Lightning App Builder → the page → Activation
Global Value Sets Consumed by — reads “Not captured” only when the field index has not been built yet (the first sync, or Objects excluded from scope). Once it has, the page names the picklist fields drawing on the value set, or says explicitly that none do Setup → Picklist Value Sets → the value set
Roles & the Roles index Assigned users, when the aggregate user-count query fails (a genuine zero still shows as 0) Setup → Roles → the role
Objects Sharing model, Deployment status and Last modified, when the EntityDefinition enrichment that carries all three fails — all three read Not captured together rather than falling back to a guessed Unknown / Deployed / stamped date Setup → Object Manager → the object → Details

Components MetaSync could not read at all

“Not captured” covers a detail MetaSync couldn’t measure. A different case is a whole component the integration user has no permission to read — a layout on a managed-package object, say. There is no page to put a caveat on, so the disclosure has to happen where you’d otherwise never notice: the index.

Note — Permanent vs transient — and why your run still says success. MetaSync distinguishes a permanent read failure (INSUFFICIENT_ACCESS — it will fail identically every run) from a transient one (a 404 or 5xx blip, retried first). A transient loss degrades the run to partial; a permanent, disclosed one does not, because a permission you haven’t granted would otherwise pin every future run at partial and force a full page rewrite each time. See How syncing works.

These fields are genuinely extracted, so they carry real values on a healthy sync: object History tracking, flow API version, queue Members counts, layout Assigned profiles / Assigned record types / Last modified, Lightning-page Last modified, record-type Picklist overrides / Assigned layouts, and profile / permission-set Apex class access / Tab visibility / App access. Each falls back to “Not captured” only when its Salesforce source (a Tooling query or Metadata read) failed during that sync — a captured empty answer shows as a real 0 or an explicit “No … “ line.

Note — It self-heals. These markers are driven by whether the extractor actually supplied a value — not by a hard-coded “unsupported” list. The day MetaSync starts extracting one of them, the real number or list appears on the next sync with no change to the page layout. Per-type detail is in the Metadata reference group, e.g. Profiles, Page Layouts and Record Types.