Compose a campaign
Wire quests, points, a referral program and a weekly leaderboard into one launch campaign.
A campaign works best when one thing a member does moves several things at once. This page builds a launch campaign where a single welcome quest does four jobs:
- Earns XP. The member gets 10 XP for completing it.
- Unlocks the next quest. A follow-up quest opens once welcome is done.
- Pays the friend who invited them. Completing welcome qualifies the referral, and the inviter gets 25 XP.
- Moves the weekly leaderboard. XP from the campaign's quests ranks members each week, and the top member wins 100 XP.
What members see
A new member joins the launch campaign, completes the welcome quest and sees 10 XP land. The next quest unlocks. The friend who invited them gets their own 25 XP. Both of them can watch the weekly leaderboard, and when the week ends your team approves the prize.
Your developer or coding agent builds the campaign in code, following the steps below. Once it's published, your team runs it from Console: arrange the quests in a collection, watch the standings, and approve the weekly prize.
Before you start
Start with a connected project or new app that has the SDK and CLI installed. Use its test environment and two real member accounts for the referral check.
Choose the connections
One welcome completion drives every other part of the campaign:
| Source | Connection | Result |
|---|---|---|
| Welcome quest completion | Quest reward | The member earns 10 community XP. |
| Welcome quest completion | Completion prerequisite | The follow-up quest becomes available. |
| Invited friend's welcome completion | Referral qualification | Their inviter earns 25 community XP. |
| Community ledger entries | Weekly earned score | Both members' eligible earnings contribute to standings. |
Build the campaign
Define the shared campaign in campaigns/launch.ts:
import { defineCampaign } from "@domino-sdk/relay/authoring";
export const launch = defineCampaign({
key: "launch",
title: "Launch campaign",
window: {
from: Date.parse("2026-10-01T00:00:00Z"),
until: Date.parse("2026-11-01T00:00:00Z"),
},
enrollment: "required",
});Both quests below import this value as launch and set campaign: launch. The member enrolls once for both quests with relay.campaigns.enroll({ actionId, campaign: launch.key }). Use dates appropriate for your program.
Define the ledger
Save the typed ledger handle as ledgers.ts:
import { defineLedger } from "@domino-sdk/relay/authoring";
export const community = defineLedger({
key: "community",
name: "XP",
description: "Community participation",
});Merge these entries into relay.json, preserving the project's connection, app configuration, and existing resources. Keep authoring keys stable. If your starter already has a welcome quest, update that file instead of creating a second definition.
{
"pointLedgers": [
{ "entry": "ledgers.ts", "exportName": "community" }
],
"discover": {
"quests": ["quests/**/*.quest.ts"],
"questTypes": ["quests/**/*.quest-type.ts"]
},
"referrals": [{ "entry": "referrals/friends.ts" }],
"leaderboards": [{ "entry": "leaderboards/weekly.ts" }]
}Rewards, referrals, and the leaderboard import the same ledger handle.
Award points for the welcome quest
Save this as quests/welcome.quest.ts:
import { defineQuest, settings, manual, once, points } from "@domino-sdk/relay/authoring";
import { community } from "../ledgers";
import { launch } from "../campaigns/launch";
export const welcome = defineQuest({
key: "welcome",
title: "Welcome to the community",
campaign: launch,
settings: settings.object({}),
presentation: () => ({ instructions: "Claim your welcome XP." }),
trigger: manual(),
completion: once(),
rewards: [points(community, 10)],
});
export default welcome;Domino records completion and awards the points. Your app displays that result through the participant API.
Unlock the next activity
Save this as quests/next-step.quest.ts:
import { defineQuest, settings, manual, once, completed, points } from "@domino-sdk/relay/authoring";
import { community } from "../ledgers";
import { launch } from "../campaigns/launch";
import { welcome } from "./welcome.quest";
export const nextStep = defineQuest({
key: "next-step",
title: "Take the next step",
campaign: launch,
settings: settings.object({}),
presentation: () => ({ instructions: "Claim after completing the welcome quest." }),
requires: [completed(welcome)],
trigger: manual(),
completion: once(),
rewards: [points(community, 5)],
});
export default nextStep;This checks completion of a particular quest. It does not check whether the member currently holds 10 XP. Spending points does not erase that completion.
Keep a manual trigger when the member must take another action. For an automatic milestone after prerequisites are met, use automatic() instead. For consecutive days, use a streak. For several separate actions, such as three different posts, use a distinct-action counter. completed(...) means ever completed.
Qualify an invitation from the same completion
Save this as referrals/friends.ts:
import { defineReferral, points, questCompleted } from "@domino-sdk/relay/authoring";
import { community } from "../ledgers";
import { welcome } from "../quests/welcome.quest";
export default defineReferral({
key: "friends",
title: "Invite a friend",
qualification: questCompleted(welcome),
rewards: { inviter: [points(community, 25)] },
});Wire invitation-code acceptance through sign-in using the referral guide. A quest completion alone does not identify an inviter. Accept the invitation before the friend completes any other quest; completing the welcome quest first is fine, since it qualifies the referral.
The invited friend earns the quest's 10 XP. Their inviter receives a separate 25 XP award when the referral qualifies. The friend's award is not reduced.
Rank weekly earnings
Save this as leaderboards/weekly.ts:
import { defineLeaderboard, earned, points } from "@domino-sdk/relay/authoring";
import { community } from "../ledgers";
import { nextStep } from "../quests/next-step.quest";
import { welcome } from "../quests/welcome.quest";
export default defineLeaderboard({
key: "weekly",
title: "This week's contributors",
ledger: community,
score: earned({
window: { kind: "calendar", period: "week" },
quests: [welcome, nextStep],
}),
settle: {
at: "window-end",
ties: "shared",
prizes: [{ ranks: [1, 1], rewards: [points(community, 100)] }],
},
});The week starts on Monday in the project calendar's timezone. Set the project timezone once, in relay.json as "calendar": { "timeZone": "Europe/Budapest" }; streaks and budgets follow it too. The quest filter makes this a campaign ranking: referral awards do not count. Spending does not reduce an earned score; corrections can. See Choose what counts.
Render standings with the leaderboard reads for members. The board reads existing points; no extra score write is needed.
Publish to test
pnpm exec domino deploy --preview --environment test --json
pnpm exec domino deploy --environment test --jsonArrange the quests in a collection through Console if your app displays collections.
Verify the connections
Sign in as two members of the same test project, Alice and Bob, then check each connection in order:
| # | As | Do | Expect |
|---|---|---|---|
| 1 | Alice | Sign in and obtain her invitation code through your app's referral flow. | An invitation code. |
| 2 | Bob | Sign in, enroll in launch, and accept Alice's invitation. | Welcome is available; before Bob completes it, the next-step quest is locked. |
| 3 | Bob | Complete welcome. | A single completion, a 10 XP award, and availability of next-step. |
| 4 | — | Check the referral summary. | Bob is qualified, and Alice has a separate 25 XP referral award. |
| 5 | — | Read weekly standings. | Bob has 10. Alice's referral award is outside the board's campaign-quest filter. |
| 6 | Bob | Refresh the app and revisit welcome. | Completion and points persist without a second award. |
| 7 | Bob | Complete next-step. | His earned score becomes 15 under the same conditions. |
Follow publishing when the test behavior is ready for live.
Approve the weekly prize
At the end of the week, Console Leaderboards shows a frozen prize settlement. Review its eligible winner and select Approve and deliver, then confirm, to grant the 100-point prize. Equal first-place scores share the rank and each receive the prize. See Settle prizes after a window for review, API approval, and correction.
Extend the campaign
When operators need multiple configurable activities with shared behavior.
When verification needs a provider account or a wallet.
For Discord roles, show delivery separately from completion.
Add external content
Want members to post about the launch on X too? An X tracker collects launch posts, and a quest checks each one against the member's connected X account, with its own approval and engagement thresholds. Its reward can feed the same XP, leaderboard and referral used above. Collecting posts awards nothing until a quest acts on them. X doesn't allow rewards for posting, so require a group of creators you approve on these quests. Domino doesn't enforce this: a deployment with a quest that rewards X posts from anyone still publishes, with a warning. See UGC programs.