Data Model Reference
Campaign#
The fundraising campaign itself — a customized standard Campaign carrying the public page design, default fund designation, and payment routing for everything raised under it.
standard · 63 fields · Campaigns & Content
Why it exists#
Campaign is the root of the object graph: nearly every other object in the Campaigns &
Content, Recurring Giving, and Events domains hangs off a Campaign record.
Reusing the standard object keeps campaign reporting inside Salesforce's native Campaign
tooling (member statuses, influence, standard reports) instead of building a parallel one.
The custom fields configure what the public campaign page looks like and does —
Design__c points at the page-design/theme record, Default_Designation__c sets where an
undesignated gift's funds land, and Recurring_Migration_Campaign__c supports moving
recurring schedules to a successor campaign at year-end. A campaign's public page, fundraisers,
tickets, and FAQs are all separate records that reference this one, not fields on it — see the
diagram for the full fan-out.
Relationships#
| Direction | Field | Related object | Relationship type |
|---|---|---|---|
| → out | Default_Designation__c |
Designation | Lookup |
| → out | Design__c |
Campaign Design | Lookup |
| → out | Payment_Account__c |
Payment Account | Lookup |
| → out | Recurring_Migration_Campaign__c |
Campaign | Lookup |
| ← in | Recurring_Migration_Campaign__c on Campaign |
Campaign | Lookup |
| ← in | Campaign__c on Campaign Post |
Campaign Post | Lookup |
| ← in | Campaign__c on Donation Staging |
Donation Staging | Lookup |
| ← in | Campaign__c on Event Attendee |
Event Attendee | Lookup |
| ← in | Campaign__c on Event Ticket Type |
Event Ticket Type | MasterDetail |
| ← in | Campaign__c on Guest Request |
Guest Request | Lookup |
| ← in | Campaign__c on Recurring Donation |
Recurring Donation | Lookup |
Fields#
| Field | Label | Type | Notes |
|---|---|---|---|
Accept_Donations_After_End_Date__c |
Accept Donations After End Date | Checkbox | Leave checked to keep accepting gifts after the end date passes — useful for an appeal whose date is a milestone rather than a deadline. Uncheck it for a campaign that must genuinely stop, such as a time-boxed match or an event that has already happened; donors then see a "no longer accepting donations" message instead of the form. Existing recurring gifts keep renewing either way. Docs: Only meaningful alongside a non-blank EndDate — a campaign with no end date is never closed by this field. Deliberately independent of Status and of the Campaign_Closed__c formula: Status = 'Active' is the publicity gate (a non-Active campaign is invisible to guests entirely), Campaign_Closed__c is the console's display flag, and this field is the only one that decides whether an ended-but-still-Active campaign may take a payment. Enforced server-side in CampaignService.assertAcceptingDonations, called from DonationService.createDonorAndIntent and EventRegistrationController.requireActiveCampaign; surfaced to the guest UI as the acceptingDonations key on the campaign payload so the form renders a closed state instead of failing at charge time. Renewal of an ALREADY-ACTIVE recurring schedule is deliberately not gated — see RecurringRenewalBatch and Recurring_Behaviour_At_Goal__c / Recurring_Migration_Campaign__c, which own that policy. |
Allow_Company_Match__c |
Allow Company Match | Checkbox | When on, donors can flag that their employer offers gift matching for this donation. |
Allow_Donation_Addon__c |
Allow Donation Addon | Checkbox | When on, ticket buyers are offered the option to add an extra donation at checkout. |
Allow_Recurring__c |
Allow Recurring | Checkbox | When on, donors can choose to make their gift recurring instead of one-time. |
Allow_Tribute__c |
Allow Tribute | Checkbox | When on, donors can dedicate their gift "in honor of" or "in memory of" someone. |
Allowed_Designation_Ids__c |
Allowed Designation Ids | LongTextArea | Only used when Designation Mode is Selectable — the set of funds donors may choose between on this campaign's donation page. Managed by the fund-scope picker, not edited directly. Docs: Mirrors Campaign.FAQ_Ids__c's JSON-array-in-a-field pattern (CampaignFaqService) — deliberately not a junction object, per the locked design decision. A LongTextArea comfortably holds hundreds of 18-char Ids as a JSON array; no realistic campaign approaches that limit. |
Amount_Impacts_JSON__c |
Amount Impacts (JSON) | LongTextArea | Advanced: JSON pairing each suggested amount with an impact line shown under its tile. Leave blank to skip impact hints. Docs: The JSON keys are free-text amounts, not validated server-side against Suggested_Amounts__c — a key that does not match a suggested-amount tile is simply never shown, and there is no error surfaced to the admin if the two lists drift apart. Keep them in sync by hand when either field is edited. |
Amount_Raised__c |
Amount Raised | Currency | Total raised toward this campaign's goal so far. Calculated automatically — do not edit directly. Docs: Apex-maintained, not a native roll-up (Opportunity to Campaign is a standard lookup, not master-detail). Recomputed by CampaignService.rollupCampaignTotals() from a fresh SUM query every time it runs, so it is always the source of truth rather than an incremental counter; called from the Opportunities domain on insert, on a Closed Won status change, on any post-close Amount change, and on a refund. A goal-crossing detected mid-rollup enqueues CancelGoalReachedRecurringQueueable when Recurring_Behaviour_At_Goal__c is Cancel. |
Campaign_Closed__c |
Closed | Checkbox | Automatically checked once this campaign's end date has passed. Drives the "Completed" tag in the Fundraisers console — you don't need to set this manually. Docs: Deliberately EndDate-only, matching the console's prior "Ended" derivation (c.EndDate != null && c.EndDate < today) — Status stays a separate, admin-set field and is not part of this formula. See CampaignsSelector.selectFundraisers (adds this to the query) and FundraiserAdminController.GetFundraisers (reads it as the "Ended" status source). |
Campaign_Stage_Icon__c |
Stage Icon | Text | A quick visual read of this campaign's stage: green means it's live and accepting donations, yellow means it's still a draft, red means it has ended. This is calculated automatically — there's nothing to set here. Docs: Uses Salesforce's built-in /img/samples/color_{green,yellow,red}.gif sample images (verified current against Salesforce Developer Docs' "Sample Image Link Formulas" reference, checked 2026-08-04) — no static resource or upload needed. Same three-state logic as Campaign_Closed__c's own doc comment and FundraiserAdminController.GetFundraisers: Status = 'Active' -> Active (green); else Campaign_Closed__c -> Ended (red); else -> Draft (yellow). This is the native record-page counterpart to lexFundraisersConsole's client-side SVG stage icons — that component needs inline markup for styling control, this field exists so the same stage is visible on the standard Campaign layout without opening the console. |
Campaign_Subtype__c |
Campaign Subtype | Picklist | What kind of fundraiser this is. Controls which public page layout and features are shown. Docs: Read by CampaignService.getPublicExperienceConfig() to choose the guest shell composition, and by the campaign gallery for its badge/filter. Every value shares the single packaged Fundraising record type, so this picklist, not RecordTypeId, is the real behavioral switch — a maintainer adding a new fundraiser shape should add a value here, not a new record type. |
Capacity_Mode__c |
Capacity Mode | Picklist | How ticket sales are capped for this event: no limit, per ticket tier, an overall event total, or both. |
Column_Ratio_Override__c |
Column Ratio Override | Picklist | Leave blank unless this one campaign needs a different split from its design — for example a short appeal whose story does not need the wide column. Docs: Replaces Form_Layout_Mode_Override__c and Form_Side_Override__c, which were the per-campaign half of the retired layout-mode pair. Blank means "use the design's ratio" — CampaignService reads it through the same campaign-over-design rule every other override on this page uses, so a blank never renders as a missing layout. |
Contact_Email_Override__c |
Contact Email Override | Only fill this in if this campaign is handled by someone other than your usual contact. Otherwise leave blank and the org default is used. Docs: Named _Override__c to match Form_Title_Override__c and its siblings: the suffix is how the next reader knows a blank means "fall back to the org default", not "this campaign has no contact". Resolved in CampaignService, never read directly by the LWC.* |
|
Contact_Phone_Override__c |
Contact Phone Override | Phone | Only fill this in if this campaign is handled by someone other than your usual contact. Otherwise leave blank and the org default is used. Docs: Named _Override__c to match Form_Title_Override__c and its siblings: the suffix is how the next reader knows a blank means "fall back to the org default", not "this campaign has no contact". Resolved in CampaignService, never read directly by the LWC.* |
Custom_Question_Ids__c |
Custom Question Ids | LongTextArea | Internal: ordered list of question template ids selected for this campaign. Managed by the Custom Questions picker, not edited directly. |
Custom_Questions_Active__c |
Custom Questions Active | Checkbox | Uncheck to hide the Custom Questions step from the public donation form without losing the saved question selection. |
Default_Amount__c |
Default Donation Amount | Currency | The gift amount pre-selected when a donor opens the donation form. Leave blank to have no amount selected by default. |
Default_Designation__c |
Default Designation | Lookup → Designation__c |
The fund this campaign supports. Its exact effect depends on the Designation Mode field above: the locked fund in Assigned mode, the pre-selected default in Selectable mode, or unused in All Active mode. Docs: This field predates Designation_Mode__c/Allowed_Designation_Ids__c (2026-08) and is reused by the new mode-based resolution in CampaignDesignationService rather than replaced — see specs/04-data-model.md for the full three-mode contract. A blank value here in Assigned mode falls back to Settings__c.Default_Designation__c via SettingsService.defaultDesignationId(), then to an undesignated gift if that also fails to resolve — never a hardcoded fund. |
Design__c |
Design | Lookup → Campaign_Design__c |
Which saved design/theme to use for this campaign's public page. Leave blank to use the default design. Docs: Resolved by CampaignService.resolveDesign()/selectDesignSystemMode(). The chosen design's theme JSON is normalized by CampaignService.normalizeThemeTokensJson() against the Theme_Token__mdt catalog before it ever reaches the guest page, so an admin cannot smuggle an arbitrary CSS var through a design record — only tokens present in that packaged catalog survive. |
Designation_Mode__c |
Designation Mode | Picklist | How donors choose a fund on this campaign's donation page: lock every gift to one fund (default), let them pick from every active fund, or offer a scoped list you choose below. Docs: Explicit picklist, not inferred from Allowed_Designation_Ids__c list length — this keeps admin intent stable while the admin is mid-edit of the fund list (e.g. temporarily down to one selected fund should not silently behave like Assigned mode). Default is Assigned — a new campaign locks to its single Default_Designation__c fund unless an admin opts into a broader mode; this field ships new alongside campaign designation scoping, so there is no pre-existing installed base to stay backward compatible with. This field's Assigned default is NOT what an API/Apex insert actually gets. Measured 2026-08-04 in pledgivo-dev-32376: a Campaign inserted with no RecordTypeId lands on the Fundraising record type, and that record type declares no picklist assignment for this field, so the platform supplies the first available value — All Active — overriding the Assigned default above. Only a caller that omits the field AND the record type is left with a blank. Campaigns created by the fundraiser wizard, the seed scripts, or any integration must therefore set this field EXPLICITLY rather than relying on either default. A record that does end up blank resolves to All Active in CampaignDesignationService — a deliberate choice so an unconfigured campaign is not silently narrowed to one fund, and it matches what the record type supplies. There is no validation rule enforcing this field; the fundraiser wizard's own save guards are the only place a blank mode is blocked. |
Donor_Count__c |
Donor Count | Number | Number of people who have donated to this campaign. Calculated automatically. Docs: Recomputed alongside Amount_Raised__c by CampaignService.rollupCampaignTotals(), which runs a grouped aggregate query rather than incrementing a counter, so a donor who gives twice is only counted once. Person Account orgs and standard orgs both feed the same count through DonorResolutionService, so this number is meaningful regardless of account model. |
Donor_Covered_Fees_Enabled__c |
Donor Covered Fees Enabled | Checkbox | When on, donors can choose to cover the processing fee so the full gift amount reaches the campaign. Docs: This flag is the ONLY gate on the feature — FeeCoverageService.isOffered() reads it, and every fee is recomputed server-side from Settings__c.Fee_Coverage_Percent__c / Fee_Coverage_Fixed__c, never from a browser-supplied number. The guest form sends only the donor's yes/no, so turning this off immediately stops any fee being added even if a stale page still shows the checkbox. The rate lives in Settings__c rather than here on purpose: it is a property of the payment processor, not of the appeal. Event ticket orders deliberately do not offer fee coverage regardless of this flag — a ticket already carries a fair-market-value split to disclose, and layering a second non-gift component on top makes the receipt materially harder to read. |
Event_Address__c |
Event Address | Text | Street address where this event takes place. |
Event_Capacity__c |
Event Capacity | Number | Maximum total attendees across all ticket types. Only used when capacity mode includes an event-wide limit. |
Event_City__c |
Event City | Text | City where this event takes place. |
Event_Country__c |
Event Country | Text | Country where this event takes place. |
Event_State__c |
Event State | Text | State or province where this event takes place. |
Event_Timezone__c |
Event Timezone | Text | Timezone the event runs in, e.g. "America/New_York". |
Event_Zip__c |
Event Zip | Text | Postal/zip code where this event takes place. |
FAQ_Ids__c |
FAQ Ids | LongTextArea | Internal: ordered list of FAQ template ids selected for this campaign. Managed by the FAQ picker, not edited directly. |
FAQs_Active__c |
FAQs Active | Checkbox | Uncheck to hide the FAQ accordion from the public donation page without losing the saved FAQ selection. |
Footer_Text_Override__c |
Public Page Footer Text Override | Text | Only fill this in if this campaign's pages need a different bottom line from the rest of your site. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Form_Description_Override__c |
Form Description Override | LongTextArea | Custom description text for this campaign's donation form. Leave blank to use the design's default text. |
Form_Disclaimer_Override__c |
Form Disclaimer Override | LongTextArea | Custom disclaimer text for this campaign's donation form. Leave blank to use the design's default text. |
Form_Footer_Override__c |
Form Footer Override | LongTextArea | Custom footer text for this campaign's donation form. Leave blank to use the design's default text. |
Form_Title_Override__c |
Form Title Override | Text | Custom title for this campaign's donation form. Leave blank to use the design's default title. |
Goal_Amount__c |
Goal Amount | Currency | How much this campaign is aiming to raise. On a General or Open Donation campaign this also drives the raised-of-goal progress bar donors see on the donation page and in embedded widgets — unless the page design has Show Progress Bar switched off. A Ticketed Event campaign shows a tier picker rather than a bar, so there the goal is used for your own reporting only. Docs: The bar's visibility rule lives in CampaignService.computeShowProgressBar (PROGRESS_BAR_SUBTYPES = {'General', 'Open_Donation'}) and is the single authority: pfHero, dfForm and the embed widget all render from the server-computed showProgressBar flag rather than re-deciding the subtype themselves. The help text spells the rule out because the field is on the layout for every subtype: before 2026-08-18 it promised "the public progress bar" unconditionally, which was false for an event. Open Donation was added to the set on 2026-08-18 (audit F-42), closing a three-way disagreement in which an open-ended appeal with a goal drew a bar on the donation form while its hero and its embed drew none, and unchecking Show Progress Bar removed it from two of the three. |
Header_Image_URL__c |
Header_Image_URL | Url | Web address of the hero image shown at the top of this campaign's page. Leave blank to use the design's default image. |
Match_Message__c |
Match Message | Text | Optional short line about a matching offer, shown as a chip on the public donation form (e.g. "Gifts matched 2x tonight"). Leave blank to hide. |
Payment_Account__c |
Payment Account | Lookup → Payment_Account__c |
Which of your configured Stripe accounts receives donations made to this fundraiser. Add more accounts from the Payments settings panel first. Every fundraiser needs one before it can go live. Docs: Replaced the Campaign_Payment_Account__c junction object (removed 2026-08-04). The junction modelled a many-to-many that was never implemented: every consumer took cpas[0] and discarded the rest, and its Is_Default__c was written but never read. Campaigns are many-to-one on payment account by product decision — an org may hold several Stripe accounts, a campaign collects to one. deleteConstraint is Restrict, so a Payment_Account__c still in use by a campaign cannot be deleted; SetupController.DeletePaymentAccount raises the same condition as a friendly admin-facing error before the platform ever gets to enforce it. |
Percent_Goal_Reached__c |
% of Goal Reached | Percent | Percentage of the goal reached so far. Calculated automatically. Docs: The formula intentionally returns the raw fraction (0.5), not 0 to 100 — Percent-type fields render the stored decimal already multiplied by 100, so a formula that also multiplies by 100 would display 50 as 5000%. Do not add a 100 term if this formula is ever revised.* |
Privacy_URL_Override__c |
Privacy URL Override | Url | Only fill this in if this campaign publishes a different privacy policy from the rest of your site. Otherwise leave blank. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Public_URL__c |
Public URL | Text | The web address (or slug) for this campaign's public donation page. Docs: Generated once from the campaign name by FundraiserAdminController.generateSlug() at creation time and only backfilled later if still blank — it is not regenerated when the campaign is renamed, so an old slug can outlive a name change. There is no unique-index enforcement on this field at the platform level; collision handling, if any, lives in the generation logic itself. |
Recurring_Behaviour_At_Goal__c |
Recurring Behaviour At Goal | Picklist | What to do with active recurring gifts once this campaign hits its goal: keep charging or automatically cancel them. Docs: Checked by CampaignService.rollupCampaignTotals() at the moment it detects a newly-crossed goal. When set to Cancel, that rollup enqueues CancelGoalReachedRecurringQueueable, which cancels active Recurring_Donation__c records in chunks of 200 with Transaction Finalizer auto-recovery (chunk size halves on a governor LimitException, up to 4 retries) rather than cancelling inline, so a large campaign never blocks the rollup that triggered it. |
Recurring_Migration_Campaign__c |
Recurring Migration Campaign | Lookup → Campaign |
Where to move active recurring gifts once this campaign closes. Leave blank to keep them here indefinitely. Docs: The description above states the intended behavior, but as of this writing no batch or service actually re-attributes recurring donations when a campaign closes; CampaignsSelector reads this field but nothing writes Recurring_Donation__c.Campaign__c off of it. Treat it as reserved for a not-yet-built migration job, not as evidence that closing a campaign moves anything automatically. |
Show_Contact__c |
Show Contact | Checkbox | Leave on so donors can reach a person before giving. Contact details fall back to the org defaults in Setup when this campaign leaves them blank. Docs: The band renders below both columns, full width. It resolves its email and phone through the three-tier fallback Campaign override then Settings__c org default; with the toggle on and both tiers blank the band renders nothing rather than an empty shell. |
Show_Documents__c |
Show Documents | Checkbox | Leave on to show donors your supporting files. The tab hides itself when there are no documents to show. Docs: Two conditions gate the tab: this flag AND at least one published Campaign_Post__c of type Document. The emptiness check lives in the guest component, not here, so an admin can pre-enable the tab before uploading the first file. |
Show_Story__c |
Show Story | Checkbox | Leave on to tell donors why you are asking. Turn off if this campaign needs nothing but a payment form. Docs: Defaults to true so a campaign that has a story shows it without an extra click. Read by CampaignService into the guest payload as sections.story; the guest shell also requires non-empty story content, so the toggle can hide a story but never conjure one. |
Show_Updates__c |
Show Updates | Checkbox | Leave on to report progress to donors. The tab hides itself when there are no updates to show. Docs: Same two-condition gate as Show_Documents__c — this flag AND at least one published Campaign_Post__c of type Update. |
Social_Facebook_URL_Override__c |
Social — Facebook URL Override | Url | Only fill this in if this campaign has its own Facebook page. Otherwise leave blank and your organization's page is shown. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Social_Instagram_URL_Override__c |
Social — Instagram URL Override | Url | Only fill this in if this campaign has its own Instagram account. Otherwise leave blank and your organization's account is shown. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Social_Share_Image_URL_Override__c |
Social Share Image URL Override | Url | Only fill this in if you want a different picture to appear when this campaign's page is shared on social media. Best at 1200x630 pixels. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Social_Share_Text__c |
Social Share Text | LongTextArea | Suggested caption pre-filled when someone shares this campaign on social media. |
Social_Twitter_URL_Override__c |
Social — X (Twitter) URL Override | Url | Only fill this in if this campaign has its own X (Twitter) account. Otherwise leave blank and your organization's account is shown. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Story__c |
Story | LongTextArea | The narrative donors read on the public page — who you help, why it matters, what their gift does. Written in Markdown using the toolbar on the Story step of the fundraiser wizard: ## for a heading, bold, a bullet list, a pull quote, an image URL, a YouTube link. Type it as plain paragraphs and it still renders correctly. Docs: Holds Markdown, never HTML. Every write from the fundraiser wizard passes through CampaignService.normalizeStoryMarkdown(), which strips HTML tags, blanks javascript:/data: URLs, drops malformed YouTube ids and throws above the byte ceiling. The public page renders it through uiMarkdownParser.js -> c-ui-markdown as a token tree of escaped LWC text bindings, so no author string ever reaches innerHTML. Stories written before Markdown (plain paragraphs, a leading ">" pull quote) are already valid Markdown and needed no migration. The length is the platform maximum and is one-way: a shipped field's length can be raised but never lowered, so 131072 is a permanent contract with every subscriber org. |
Suggested_Amounts__c |
Suggested Amounts | Text | Comma-separated preset amounts shown as quick-pick buttons, e.g. "25,50,100,250". Docs: Plain comma-separated text, not a structured list type, so any consumer must split and parse it defensively. Paired with Amount_Impacts_JSON__c, whose keys should match these values exactly for the impact hint to show — see the doc note there about the two lists not being validated against each other. |
Tagline__c |
Tagline | Text | A short phrase shown below the campaign title on the public page. |
Terms_URL_Override__c |
Terms URL Override | Url | Only fill this in if this campaign is governed by different terms from the rest of your site — a sponsored appeal with its own conditions, for example. Docs: Named _Override__c to match Contact_Email_Override__c and its siblings: the suffix is how the next reader knows a blank means "use the org-wide value from Setup", not "show nothing". Resolved in CampaignService.resolveIdentity, never read directly by an LWC.* |
Thank_You_CTA_Label__c |
Thank-You CTA Label | Text | Text shown on the thank-you CTA button (e.g. "See Our Impact"). Blank = use the org default. |
Thank_You_CTA_URL__c |
Thank-You CTA URL | Url | Where the thank-you CTA button sends donors (e.g. your programs page). Blank = use the org default. Docs: Sits in the middle of a precedence chain, not a standalone value: a page design's own CTA settings are checked first, then this campaign field, then the org-wide Settings default, and finally a hardcoded Continue fallback if nothing else is set. A blank value here does not mean no CTA — it means the next link in the chain decides. |
Tickets_Sold__c |
Tickets Sold | Summary | Total tickets sold across all ticket types for this event. Calculated automatically. Docs: The only native Roll-Up Summary field on Campaign — everything else on this object that looks like a rollup (Amount_Raised__c, Donor_Count__c) is Apex-maintained instead, because Opportunity to Campaign is a standard lookup, not master-detail, so a platform rollup is not available there. This one works natively because Event_Ticket_Type__c is Master-Detail to Campaign. |
Venue_Name__c |
Venue Name | Text | Name of the location where this event takes place. |