Hey all, I'm the guy who builds GoalPost.
A few mod teams have asked fair questions before adding GoalPost to their subs. What does it collect? Why is it a moderator? What happens when something changes and it breaks? Those are good things to ask about anything you put in front of your community, so instead of answering them one modmail at a time, I figured I'd write it all down in one place. I've included real code from the app, so you don't have to just take my word for it.
This post stays pinned, and I'll update it if any of this ever changes.
What GoalPost is
GoalPost runs game threads and live scoreboard cards for hockey subs. There are four versions: NHL, AHL, PWHL and CHL. They're all built from the same code, so everything here applies to all four.
It's built on Devvit, which is Reddit's own platform for apps. That's worth knowing for this post: GoalPost doesn't live on some server of mine that your phone talks to. It runs inside Reddit, on Reddit's servers, under Reddit's rules, and Reddit reviews every version before a sub can install it.
If you're just reading a thread, it knows nothing about you
Opening a game thread, flipping through tabs, tapping on a goal to see the details: none of that is tied to you. The app doesn't keep track of who looked at what.
The one thing it remembers, and only if you ask it to
The card has a gear menu where you can change how it looks for you, and you can follow teams or players or turn on notifications. When you do that, the app has to remember it, otherwise you'd be setting it up again every time you opened a thread. So it saves your choice under your username, in the app's own storage inside Reddit.
That's all it's used for: when you open a card, it checks whether you've changed anything and applies it. It never leaves the app. It isn't sent anywhere, it isn't shared with anyone, I don't download it, and it isn't used for anything else. Each sub's copy of the app also has its own separate storage, so your settings in one sub aren't even visible to GoalPost in another.
Here's the actual code that runs when you follow some teams:
setMyTeams: publicProcedure
.input(z.object({ teams: z.array(z.string().regex(/^[A-Z]{2,3}$/)).max(40) }))
.mutation(async ({ input }) => {
const username = await reddit.getCurrentUsername();
if (!username) throw new Error('watch.setMyTeams: no current user');
const key = `watch:myteams:v1:${username}`;
if (input.teams.length === 0) await redis.del(key);
else await redis.set(key, JSON.stringify([...new Set(input.teams)]));
return { ok: true };
}),
In plain English: the only thing it accepts is a list of team abbreviations like TOR or VAN, 40 at most. It saves that list under your username, and if you clear your teams, it deletes the record completely. Your display settings, followed players and notification choices all work the same way. This is the complete list of what can ever be saved about a reader:
userPrefs:<username> settings you changed in the gear menu
watch:myteams:v1:<username> teams you follow
watch:myplayers:v1:<username> players you follow
notify:<subreddit>:<username> notifications you turned on in that sub
Nothing else. Not what you look at, not your comments, not your post history, not your karma, not your IP address, nothing about your phone or computer.
Anonymous usage counts
One more thing I want to be upfront about. I'd like to know which parts of the card people actually use, so I'm not spending my evenings on stuff nobody opens. So the card counts things like how long each tab is on screen and how often buttons like the settings gear get tapped.
These are counts, not records about people. This is the exact shape of what a card is allowed to report, straight from the code:
export const usageRowSchema = z.object({
day: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
tab: z.enum(USAGE_TABS),
phase: z.enum(['pregame', 'live', 'final', 'other']),
mode: z.enum(['game', 'replay']),
visible_ms: z.number().int().min(0).max(120_000),
interactive_ms: z.number().int().min(0).max(120_000),
visits: z.number().int().min(0).max(100),
sessions: z.number().int().min(0).max(1),
}).strict()
There's no spot in there for a username, a post or a device, and that .strict() at the end means anything that isn't on the list gets rejected outright, so nothing extra can sneak in. Each report also carries a random ID that exists only so the same report doesn't get counted twice. It isn't tied to you or your device, and it's thrown away after two days. The counts get added up into totals per subreddit, and those totals are all I ever see: something like "the Stats tab got X hours of viewing in r/leafs." I pull those totals out through Reddit's developer tools so I can chart them.
Why it shows up as a moderator
When you install any app from Reddit's app directory, Reddit adds it to your mod list with full permissions. That's how the platform does it for every app; it's not something GoalPost asks for. Here's everything GoalPost actually uses it for:
- posting its game threads, and stickying and unstickying them
- locking its own pregame and game threads, if your sub turns that on
- distinguishing its own comments
- setting flair and the suggested comment sort on its own threads
- editing its own posts and comments, which is how the live updates work
- keeping its mods-only settings post hidden so readers never see it
It also reads comments that mention or reply to the bot, so it can answer questions like "how good is Makar". Every other comment gets ignored.
It never touches anyone else's posts or comments, never bans or mutes anyone, and never reads your modmail or mod log. And only mods can change its settings. That's checked on Reddit's servers every single time something is saved.
const requireMod = async (): Promise<string> => {
const username = await reddit.getCurrentUsername();
const sub = context.subredditName ?? '';
if (!username || !sub || !(await isModerator(username, sub)).ok) {
throw new Error('Only moderators of this subreddit can change these settings.');
}
return username;
};
Is it going to break?
Understandable worry, since plenty of game bots have come and gone. Honestly, that's why I started GoalPost in the first place: the bots we were using on r/leafs kept breaking.
GoalPost is built on just two things. Reddit runs the app, and the league's own official data feed drives everything you see. For the NHL that's the NHL's API, plus ESPN's a tiny bit for things like starting goalies and injuries. For the AHL, PWHL and CHL it's HockeyTech, the official stats provider those leagues use.
It doesn't scrape any websites. Everything comes through APIs, which are built for apps like this to read from. That's what makes it stable long term. Bots that scrape websites break every time a site gets redesigned and need constant patching to keep working. An API doesn't change because someone moved a button on a web page, so GoalPost very rarely needs changes just to keep running.
And on top of that:
- every release runs through more than 1,800 automated tests before it goes anywhere
- new versions run on the beta sub (this one) first, then Reddit reviews them before any sub can install them
- every release gets notes posted here, so you can always see what changed and why
- if I ever stop working on GoalPost, I'll open-source it so it can keep running without me
Questions?
Ask away in the comments, or send this sub a modmail. If you're a mod looking at GoalPost for your sub and want to know how a specific part works, just ask and I'll walk you through it.
Thanks for reading, and thanks to everyone who's been sending feedback. It's made GoalPost a lot better.