Charitable Blog

Everything you need to know about Charitable and our team.

Tutorial: Set Custom Receipt Pages per Campaign

Last updated on

  • By

By default, your site will have a single donation receipt page, which donors will automatically be redirected to after they make their donation.

You can use the auto-generated receipt page provided by Charitable out of the box, or you can specify a particular page to be used as your donation receipt page. Check the guide below if you’re not sure how to do this:

But what if you want to use a different receipt page for different campaigns? That isn’t possible out of the box, but thanks to Charitable’s built-in flexibility, it’s possible to achieve this with just two PHP functions.

The Solution

Our solution will add a new field to the campaign editor which allows you to select a static page as the donation receipt page for your campaign. Unless you choose a specific page, it will use the default option, which is whatever is set at Charitable > Settings > General for the “Donation Receipt Page” option.

I will explain the solution below, but if you just want the code, you can download it from Github:

https://github.com/Charitable/Charitable-Custom-Campaign-Receipt-Pages

Step 1: Add our function

As with the last two tutorials, this one starts with a PHP function which will run when the init hook is called within WordPress. init is a hook that happens early on while WordPress is loading, before the page has started rendering.

add_action( 'init', function() {
    // Our code will go in here.
} );

We are using an anonymous function here, which is fine as long as you are on a version of PHP greater than 5.2.

If you’re not sure how to add this to your site, check out our guide:

Step 2: Add the field to the Campaign editor

We can now add our “Donation Receipt Page” setting by registering a new field using the Campaign Fields API.

add_action( 'init', function() {

	/**
	 * Create a new field as an instance of `Charitable_Campaign_Field`.
	 *
	 * See all available arguments at:
	 *
	 * @https://github.com/Charitable/Charitable/blob/ef9a468fbdd6fa83307abe6ac0c38896f625cf45/includes/fields/class-charitable-campaign-field.php
	*/
	$campaign_field = new Charitable_Campaign_Field( 'donation_receipt_page', array(
		'label'      => 'Donation Receipt Page',
		'data_type'  => 'meta',
		'admin_form' => array(
			'type'     => 'select',
			'required' => false,
			'options'  => array(
				'default' => 'Use the default donation receipt page',
				'pages'   => array(
					'options' => charitable_get_pages_options(),
					'label'   => 'Pages',
				),
			),
			'default'  => 'default',
			'section'  => 'campaign-donation-options',
		),
                'show_in_export' => true,
	) );

	/**
	 * Now, we register our new field.
	 */
	charitable()->campaign_fields()->register_field( $campaign_field );

} );

This is the same approach that we used in the previous two tutorials, so if you read those this will look quite familiar to you. One new bit is how the field’s admin_form setting is set up:

'admin_form' => array(
	'type'     => 'select',
	'required' => false,
	'options'  => array(
		'default' => 'Use the default donation receipt page',
		'pages'   => array(
			'options' => charitable_get_pages_options(),
			'label'   => 'Pages',
		),
	),
	'default'  => 'default',
	'section'  => 'campaign-donation-options',
),

The type is set to select, which means that a dropdown select field will be used. Select fields require a set of options, which are passed as an array. Our array includes first of all a default choice, followed by a list of pages, retrieved using the charitable_get_pages_options() function. If you’re familiar with HTML, the way this is structured means that the options array inside pages creates an optgroup element, with label set as the label.

Our new Donation Receipt Page field

Step 3: Override the default donation receipt page

Within Charitable, the donation receipt URL for a particular donation is retrieved like this:

charitable_get_permalink( 'donation_receipt_page', array(
    'donation_id' => $donation_id,
) );

This function in turn uses the Endpoint API, which provides a structured way to deal with important endpoints within Charitable, such as campaigns, donation pages, the login page and the donation receipt page.

What is important for our case is that the returned permalink can be filtered. In the case of the donation receipt page, the filter to use is charitable_permalink_donation_receipt_page. It provides two parameters:

  • $default – The default permalink to be used. We will fall back to this if the campaign has not set a custom receipt page, or if it’s using the default.
  • $args – An array containing the donation ID.

Step 3a: Create the outline of the callback function

First of all, let’s see what the structure of our callback function will look like:

/**
 * Filter the page to redirect the donor to after making their donation.
 *
 * @param  string $default The endpoint's URL.
 * @param  array  $args    Mixed set of arguments.
 * @return string
 */
add_filter( 'charitable_permalink_donation_receipt_page', function( $default, $args ) {

   // Code goes here.

}, 10, 2 } );

As you can see, we are defining an anonymous function that is hooked into the charitable_permalink_donation_receipt_page filter. Importantly, our last line sets the callback function to operate with priority 10 (the default) and receive 2 parameters.

Step 3b: Get the campaign for the donation receipt

The $args parameter will include a donation_id property. We use this to get a Charitable_Donation object, and in turn use that to get the Charitable_Campaign object:

/**
 * Get the donation object.
 */
$donation_id = isset( $args['donation_id'] ) ? $args['donation_id'] : get_the_ID();
$donation    = charitable_get_donation( $donation_id );

/**
 * Get the campaign that received the donation.
 */
$campaign_id = current( $donation->get_campaign_donations() )->campaign_id;
$campaign    = charitable_get_campaign( $campaign_id );

Step 3c: Get the campaign’s donation receipt page

Now that we have a Charitable_Campaign object, we can get the donation receipt page setting for the campaign with the get() method:

/**
 * Get the campaign's donation receipt page.
 */
$receipt_page = $campaign->get( 'donation_receipt_page' );

Step 3d: Check whether to return the default receipt URL

Remember that you can set the campaign to use the default receipt. It’s also possible that you haven’t updated your campaign since adding the Donation Receipt Page setting, in which case the $receipt_page variable will be false. If either of these is the case, we will return the $default variable since we don’t have a custom receipt for this campaign:

/**
 * If we haven't set a donation receipt page or we chose
 * the default option, return the default.
 */
if ( ! $receipt_page || 'default' == $receipt_page ) {
    return $default;
}

Step 3e: Return the custom receipt URL

If we’re still around at this point, it means that a custom receipt page is set for this campaign. All that’s left to do is to get the permalink for that page and then append the donation_id to it, as well as donation_receipt. The resulting URL will look like this:

https://mysite.com/my-custom-receipt-page/?donation_id=123&donation_receipt=1

Here’s how we do that:

/**
 * Get the permalink for the receipt page and then append
 * the donation_id and donation_receipt arguments to it.
 */
$receipt_page_url = get_permalink( $receipt_page );
$receipt_page_url = add_query_arg(
	array(
		'donation_id'      => $donation_id,
		'donation_receipt' => 1,
	),
	$receipt_page_url
);

/**
 * Return the escaped URL.
 */
return esc_url_raw( $receipt_page_url );

get_permalink is a core WordPress plugin used to get the URL for a particular page or post. We use that as the basis for our URL, and then append the required query arguments to it with add_query_arg. Finally, we return it as an escaped value with esc_url_raw.

Extra Step: Set up your custom receipt page

This solution allows you to use any page as a donation receipt page. Chances are, you would like to show information about the donation to the donor when they land on the receipt. To include this, you can use the [donation_receipt] shortcode; it will show the dynamic receipt information that is normally shown on Charitable receipts.

Bonus ideas for customizing the donation receipt page

We’ve seen here how to customize the donation receipt page on a per-campaign basis. But what if you would like to use a different donation receipt page based on a different factor?

Over on our Code Snippets library, we have a couple snippets available that show you how you can use a different donation receipt in other scenarios:

Got an idea for another tutorial?

If you have an idea for another tutorial, I’d love to hear it! Leave a comment below or get in touch via our Support page.

author avatar
Eric Daams

Disclosure: Our content is reader-supported. This means if you click on some of our links, then we may earn a commission. We only recommend products that we believe will add value to our readers.

Leave a Reply

Your email address will not be published. Required fields are marked *

Get free tips and resources right in your inbox, along with 60,000+ others

Join our Newsletter

We won’t spam you. We only send an email when we think it will genuinely help you. Unsubscribe at any time!

Featured Video:

Watch more videos on our YouTube channel.

What's New In Charitable

View The Latest Updates
🔔 Subscribe to get our latest updates
📧 Subscribe to Emails

Email Subscription

Join our Newsletter

We won’t spam you. We only send an email when we think it will genuinely help you. Unsubscribe at any time!

ambassadors New

🖨️ Introducing Fliers and Posters: Raise Money Away From the Screen!

A lot of peer-to-peer fundraising happens away from a screen: at the finish line of a 5K, on a school noticeboard, in an office kitchen. Charitable Ambassadors 3.4.7 adds 2 printables, a fundraiser flier carrying each fundraiser’s own story and QR code, and a recruitment poster that signs new fundraisers up. Both open as a normal web page and print straight from the browser. Why this is important:

  • 🏁 A flier for every fundraiser: Each one carries that fundraiser’s own photo, story and goal, with a QR code that opens their page rather than your main campaign.
  • 📌 A poster that recruits: It shows “Now Recruiting” above your campaign name with a large QR code that opens your sign-up form, so someone can start fundraising without waiting on a link from you.
  • 🪄 No design tool needed: Both printables render as a normal web page and print from the browser, with no PDF plugin and no designer.
  • 📄 2 flier layouts: Hand out carries the full story for giving to friends and family, and Post up carries a short hook with 5 tear-off tabs for a noticeboard.
  • 🛡️ Off until you switch it on: Fliers stay off after the update until you enable them under Charitable » Ambassadors » Other » Fliers, and only the owner or an administrator can open one.

Check out our announcement here.

author avatar
Eric Daams
Improvement Payments

💸 Introducing Tiered Rates for Fee Relief: Match the Ask to the Gift!

Fee Relief asks your donors one short question at checkout: would you like to cover the processing fee on this gift? Until now that question used a single rate for every donation on a gateway, so a $10 gift and a $500 gift were asked for the same percentage. Fee Relief 1.3.0 lets you set as many donation bands as you like, each carrying its own rate. Why this is important:

  • 🪙 Gentler on small gifts: A fixed transaction cost is a far bigger share of a $10 donation than a $500 one, so bands let you ask for less at the bottom.
  • 🎚️ As many bands as you need: Each band carries its own percentage and its own fixed amount, and the top band runs on with no upper limit.
  • 🛑 An optional ceiling: A Maximum fee field stops any donor being asked for more than a number you choose, however large the gift.
  • 🔌 Set per payment gateway: Stripe can run on four bands while Windcave and Payfast stay on a single rate, because bands belong to a gateway and not to your whole site.
  • 🛡️ Nothing changes until you say so: Your current rates stay exactly as they are until you switch Use Tiered Rates on under Charitable » Settings » Advanced » Fee Relief.

Check out our announcement here.

author avatar
Eric Daams
Donations Improvement

📊 Your Pro Dashboard Can Now Compare, Export, and Score!

Most fundraising dashboards only tell you how much you have raised. Charitable Pro 1.8.19 rebuilds the dashboard and the Reports Overview around the harder questions: whether anything is broken, whether this period beat the last one, and which single change would make the most difference today. Why this is important:

  • 📈 Every figure has a trend: Pick a period and the tiles and the chart move together, with each total shown against the previous period.
  • 🗓️ Charts that stay readable: Short ranges group donations day by day, while longer ranges group by week or month, so a full year reads as a curve instead of a flat line.
  • 🔀 Compare any 2 periods: Not just consecutive ones, so you can ask how this December did against last December. That matters because fundraising is seasonal.
  • 📤 Export the whole summary: Send the full overview out as CSV or PDF, ready for a board pack or a funder report.
  • 🎯 Score your setup for free: Have your fundraising setup scored, so the one change worth making next is the one you can see.

Check out our announcement here.

author avatar
Eric Daams
Feature New

🎟️ Introducing Charitable Events: Sell Tickets Inside Your Campaigns!

Most nonprofits sell event tickets in one tool and collect donations in another, which leaves two sets of reports and a manual match at the end of the year. Charitable Events brings both together, selling tickets through the same donation forms and payment gateways you already run in Charitable. Why this is important:

  • 🎟️ Every ticket is a donation: Ticket sales run through the gateway you already use and count toward the campaign goal, with everything managed under Charitable » Events.
  • 📱 QR check-in from any phone: Each attendee gets their own QR code on the receipt, and volunteers scan at the door through a check-in link that needs no WordPress account.
  • 🧾 Receipts ready for tax time: Give each ticket type a fair market value, and Charitable puts the correct tax-deductible amount on every donor receipt.
  • 📋 Attendee details at checkout: Ask every guest for a meal choice, shirt size, or access needs, with a separate attendee record created for each ticket.
  • 🗓️ Blocks and calendars included: Three editor blocks plus Sugar Calendar and The Events Calendar support put your events where supporters already look.

Check out our announcement here.

author avatar
Eric Daams
ai New

🚀 Unleash AI Power in Charitable!

Your AI assistant can now build, edit, and manage your Charitable fundraising campaigns directly — just by asking. With Charitable Pro adding write support for the WordPress Abilities API, tools like ChatGPT, Claude, and Cursor can handle full administrative actions safely through MCP. Why this is important:

  • 🤖 Campaign creation in plain language: Prompt your assistant with a goal like “Set up a fundraiser for the shelter with a $5,000 goal”, and it builds a real draft campaign complete with suggested donation tiers.
  • 🎨 Instant donation form tweaks: Add custom fields, change button colors, or reorder elements without diving into form settings.
  • 📝 Offline gift logging: Tell your assistant about cash or check gifts, and it will match donor emails and record transactions into your history.
  • 🛡️ Safety and control by default: Write access stays strictly off until turned on in Charitable » Tools » AI MCP, keeping read-only reporting safe by default.
  • 📜 Full audit logging: Every change made by an AI assistant is tracked under Charitable » Tools » Logs, so you always know who (or what) updated your site.

Check out our announcement here.

author avatar
Eric Daams