uidudevelopers
Domains

Donations

Fetch donation campaigns and accept donations against uidu's fundraising API.

Donation campaigns are your workspace's fundraisers — a charity drive, a sponsorship pool, a crowdfunding goal. You show them and their progress with the public token, and record each gift with createDonation from your server.

Mental model

  • DonationCampaign — the fundraiser: name, a rich-text body (Tiptap JSON — render it with <RichText>), cover, kind, startsAt, publishedAt, publicPath / publicUrl, a primaryAddress, and live totals: goal, donationsAmount, donationsCount, donationsAverage. It may carry a form — a donor questionnaire with the same shape as Forms — for anything beyond name and email.
  • Donation — one gift to one campaign (donationCampaignId): amount, currency, the donor as a contact, and optionally a message (body), a dedication ("in memory of…") and the answers to the campaign's form (formResponseAttributes). It comes back with id, amount, currency, createdAt and contact.id.

Shapes and gotchas:

  • Money is in cents. goal, donationsAmount, donationsAverage and a donation's amount are integers in the currency's minor unit: 5000 is 50.00. Divide by 100 to display, multiply by 100 (and round) before you write. currency is an ISO code: 'EUR', 'USD'…
  • Recording is not charging. createDonation records the gift and the donor; taking the money is a separate step with your payment processor (Stripe, …), unless payment runs through uidu's checkout (orderAttributes / subscriptionAttributes, below).
  • A gift is not a campaign. createDonation records a gift to a campaign; createDonationCampaign creates the campaign itself.
  • Both mutations return the payload as-is — { errors, donation } / { errors, donationCampaign }, or null — with validation failures in errors, not thrown. Both need the apiKey client: a donation is submitted from a server action, never with the public token in the browser. See Writes and Authentication.

Use cases

Campaigns page with progress bars

Goal: list the active campaigns, each with how much it has raised against its goal.

// app/donate/page.tsx — a Server Component
import Link from 'next/link';
import { listDonationCampaigns } from '@uidu/client';
import { uidu } from '@/lib/uidu';

const euros = (cents: number | null | undefined) =>
  ((cents ?? 0) / 100).toLocaleString('en', {
    style: 'currency',
    currency: 'EUR',
  });

export default async function CampaignsPage() {
  const campaigns = await listDonationCampaigns(uidu, { first: 20 });

  return (
    <ul>
      {campaigns.map((c) => {
        const pct = c.goal
          ? Math.min(100, ((c.donationsAmount ?? 0) / c.goal) * 100)
          : 0;
        return (
          <li key={c.id}>
            <Link href={`/donate/${encodeURIComponent(c.id)}`}>{c.name}</Link>
            <div className="h-2 rounded-full bg-slate-200">
              <div
                className="h-full rounded-full bg-emerald-500"
                style={{ width: `${pct}%` }}
              />
            </div>
            <p>
              {euros(c.donationsAmount)} raised of {euros(c.goal)} ·{' '}
              {c.donationsCount ?? 0} donors
            </p>
          </li>
        );
      })}
    </ul>
  );
}
  • Summaries are cheap: id, name, cover, goal, donationsAmount, donationsCount, startsAt, publishedAt, publicPath. The body, donationsAverage and the donor form come only from getDonationCampaign.
  • A campaign without a goal has no bar to fill — guard the division, as above.

Campaign page with a donation form

Goal: show one campaign and let a visitor give an amount, with an optional message.

// app/donate/[id]/page.tsx
import { createDonation, getDonationCampaign } from '@uidu/client';
import { RichText } from '@uidu/react';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu'; // createClient({ workspace, publicToken, apiKey })

export default async function CampaignPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const campaign = await getDonationCampaign(uidu, {
    id: decodeURIComponent(id),
  });
  if (!campaign) notFound();
  const campaignId = campaign.id;

  async function donate(formData: FormData) {
    'use server';
    const message = String(formData.get('message') ?? '');
    const result = await createDonation(uidu, {
      input: {
        attributes: {
          donationCampaignId: campaignId,
          amount: Math.round(Number(formData.get('amount')) * 100), // euros in, cents out
          currency: 'EUR',
          contactAttributes: {
            email: String(formData.get('email')),
            contactableAttributes: {
              kind: 'person',
              firstName: String(formData.get('firstName')),
            },
          },
          body: message
            ? {
                type: 'doc',
                content: [
                  {
                    type: 'paragraph',
                    content: [{ type: 'text', text: message }],
                  },
                ],
              }
            : undefined,
        },
      },
    });
    if (result?.errors?.length) return; // return result.errors to the form
    // result?.donation?.id, result?.donation?.amount (cents) — now charge with your processor
  }

  return (
    <article>
      <h1>{campaign.name}</h1>
      {campaign.body && <RichText doc={campaign.body} />}
      <form action={donate}>
        <input name="amount" type="number" min="1" step="0.01" required />
        <input name="firstName" required />
        <input name="email" type="email" required />
        <textarea name="message" />
        <button>Donate</button>
      </form>
    </article>
  );
}
  • The donor. contactAttributes creates the donor's contact in the same mutation — a new contact on every gift unless you pass the id of an existing one inside it (contactAttributes: { id }). DonationAttributes also has contactId.
  • Message and dedication are JSON — typically a Tiptap document, as above.
  • The campaign's form. When campaign.form is set, render it like any form (its questions are at campaign.form.inputs) and send the answers as formResponseAttributes: { formId: campaign.form.id, fieldValuesAttributes: [...] } — each content is { value }, see Forms.
  • Paying through uidu's checkout. createDonation's input also takes orderAttributes (a one-time payment) or subscriptionAttributes (recurring billing), next to attributes. With either, the donation's amount is computed from the order's or the plan's price, not from the amount you send. The attributes they expect depend on how checkout is configured in your workspace — see Schema for OrderAttributes and SubscriptionAttributes.
  • The demo's version, with error handling, is submitDonation in apps/demo/src/lib/actions.ts.

Launch a campaign from your server

Goal: create a fundraiser with a goal from a script or an admin tool.

import { createDonationCampaign } from '@uidu/client';
import { uidu } from '@/lib/uidu'; // must carry apiKey

const result = await createDonationCampaign(uidu, {
  input: { attributes: { name: 'Winter appeal', goal: 500000 } }, // €5,000.00 in cents
});

if (result?.errors?.length)
  throw new Error(result.errors.map((e) => e.name).join(', '));
result?.donationCampaign?.id;
  • DonationCampaignAttributes also takes body, abstract, cover, kind, category, start and end dates and publishedAt — see Schema. Change it later with updateDonationCampaign, remove it with deleteDonationCampaign.
  • The same from a terminal: uidu donations create | update | delete — CLI.

API at a glance

Every @uidu/client function in Donations, generated from the source — the full reference has the rest of the SDK.

FunctionKindAuthCLIDescription
createDonationwriteBearer—Record a donation (amount in cents) to a campaign, creating the donor's contact inline; returns { errors, donation } as-is.
createDonationCampaignwriteBearerdonations createCreate a donation campaign (goal in cents); returns the payload as-is, { errors, donationCampaign }.
deleteDonationCampaignwriteBearerdonations deleteDelete a donation campaign by { input: { id } }. Returns the payload (with errors) or null.
getDonationCampaignreadpublic tokendonations getOne donation campaign with its body, its totals (goal, donationsAmount, in cents) and its donor form, or null.
listDonationCampaignsreadpublic tokendonations listThe workspace's donation campaigns as a flat array of summaries with their totals in cents; page with first/after.
updateDonationCampaignwriteBearerdonations updateUpdate a donation campaign: { input: { id, attributes } }. Returns the payload (with errors) or null.

Types: DonationCampaign

CLI:

Entitylistgetcreateupdatedelete
donations✓✓✓✓✓

On this page