Getting started
By the end of this page you’ll have Sheetbell running on your own computer, with a real form that writes to your spreadsheet and posts to your Slack. We’ll go one step at a time. If something doesn’t work, check Troubleshooting at the bottom — most first-run problems are listed there.
What you’ll set up (a checklist)
By the end you’ll have ticked all of these. Don’t create the accounts yet — the steps tell you when.
- git installed (to download the code)
- Node.js 22 installed (to run the app)
- A Slack app (for sign-in + notifications)
- A Google service account (so the app can edit your sheet)
- A Google Sheet, shared with that service account
- A filled-in
.envfile
Step 0 — Check your tools
Open a terminal and confirm git and Node.js are installed:
git --version
node --version
If git is missing, install it from git-scm.com. For
Node you want v22 (the project is pinned to it in .nvmrc). If you use a
version manager like nvm, run nvm use inside the project folder later and it
will pick the right version automatically.
Step 1 — Get the code and install
Download the project and move into its folder, then install its libraries:
git clone <REPO_URL> sheetbell
cd sheetbell
npm install
Replace
<REPO_URL>with the address of the repository you’re cloning from.
npm install downloads the libraries the app depends on into a node_modules
folder. It can take a minute. You only need to do it once (and again whenever
dependencies change).
This downloads the libraries the app depends on into a node_modules folder. It
can take a minute. You only need to do it once (and again whenever dependencies
change).
Now make your own copy of the settings file:
cp .env.example .env
.env is where your secret keys and IDs live. It is ignored by git on purpose
— never commit it. We’ll fill it in as we go. Leave it open in your editor.
Throughout this tutorial, whenever you see a line like
SLACK_CLIENT_ID=, it means: find that line in your.envfile and paste the value after the=.
Step 2 — Create a Slack app
Sheetbell uses Slack for two things: signing people in and posting notifications. Both come from one “Slack app” that you create now.
-
Go to https://api.slack.com/apps and click Create New App → From scratch. Give it a name (e.g. “Sheetbell”) and pick your workspace.
-
In the left menu, open OAuth & Permissions.
-
Under Redirect URLs, add this exact URL and save:
https://localhost:4321/api/auth/callbackThis is the page Slack sends people back to after they sign in. (When you deploy later, you’ll add your real website’s address here too.)
The URL must match exactly.
4321is the default dev port, but if it’s already in use the app picks the next free one (4322, …). In Step 5 you’ll start the app and it prints the real address — if it isn’t4321, come back and update this redirect URL to match. -
Still on that page, find Scopes. Scopes are permissions. Add:
- Under User Token Scopes:
identity.basic— lets the app learn who signed in (their name and ID), nothing more. - Under Bot Token Scopes:
chat:write— lets the app post messages.
- Under User Token Scopes:
-
Scroll up and click Install to Workspace, then Allow.
Now collect three values:
-
On Basic Information, copy the Client ID and Client Secret:
SLACK_CLIENT_ID=... SLACK_CLIENT_SECRET=... -
Back on OAuth & Permissions, copy the Bot User OAuth Token (it starts with
xoxb-):SLACK_OAUTH=xoxb-...
Pick a channel for notifications
Decide which Slack channel should receive the messages. In Slack, open that
channel, click its name, and copy the Channel ID at the bottom of the details
(it looks like C0123ABCD — an ID, not the #name). Paste it in:
SLACK_CHANNEL_ID=C0123ABCD
Finally, invite your app’s bot into that channel. In the channel’s message box,
type /invite @ and pick your bot from the autocomplete list (its name is
whatever you set on the Slack app, under App Home → Display Name — it may differ
from the app’s name):
/invite @your-bot-name
If you skip the invite, posting will fail with a not_in_channel error later.
Step 3 — Create a Google service account
A service account is a robot Google account that your app logs in as, so it can edit your spreadsheet without you being present.
-
Go to the Google Cloud Console and create (or pick) a project.
-
Search for Google Sheets API and click Enable.
-
Go to APIs & Services → Credentials → Create Credentials → Service account. Give it a name and create it.
-
Open the new service account, go to the Keys tab, and choose Add Key → Create new key → JSON. A
.jsonfile downloads. This is a secret — treat it like a password. -
The app needs this JSON as a single line. The downloaded file is spread across many lines, so don’t copy-paste it by hand. Instead, collapse it to one line with a command and copy the result:
# if you have jq installed (recommended): jq -c . < ~/Downloads/your-key.json # no jq? this works too: tr -d '\n' < ~/Downloads/your-key.jsonPaste the single-line output into
.env, wrapped in single quotes:GOOGLE_SERVICE_KEY='{"type":"service_account","project_id":"...","private_key":"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", ...}'Two things matter: keep it on one line, and leave the
\nsequences insideprivate_keyexactly as they are (they’re part of the key). If your editor soft-wraps the long line, that’s only visual — there must be no real line breaks.
Step 4 — Set up and share your spreadsheet
-
Create a Google Sheet (or use an existing one).
-
Look at its URL. The long code between
/d/and/editis the spreadsheet ID. Copy it:https://docs.google.com/spreadsheets/d/THIS_LONG_PART/editSPREADSHEET_ID=THIS_LONG_PART -
Add a tab named
Conversations. This is where submissions are appended. Put a header row in row 1 (suggested):Timestamp,Organizer,Contact,Date,Message. -
Share the sheet with your robot. Open the JSON key from Step 3 and find the
client_emailfield — it looks likesomething@your-project.iam.gserviceaccount.com. Click Share in the spreadsheet, paste that email, and give it Editor access.This step is easy to forget and is the #1 reason the app can’t write to the sheet. If your robot isn’t a sharer, Google says no. (Google may warn that the address is outside your organization or can’t be notified — that’s fine. Share anyway, and you can uncheck “Notify people.”)
Tip — don’t post to anything real on your first try. When you’re not in production (which includes local development), the app prefers
SPREADSHEET_ID_TESTandSLACK_CHANNEL_ID_TESTif you set them. Point those at a throwaway sheet and channel for testing. If you leave them blank, your local tests write to and ping your realSPREADSHEET_ID/SLACK_CHANNEL_ID.
The
Eligibletab (the contact-roster feature) is optional. You can skip it for now — the app simply does nothing extra when it’s missing. See Configuration when you’re ready for it.
Step 5 — Run it
Start the development server:
npm run dev
It runs over HTTPS (a secure connection), because Slack sign-in requires it. Your browser will warn that the certificate is self-signed — that’s expected on your own machine. Click through the warning to continue.
Open the exact address it prints (usually https://localhost:4321 — if it’s a
different port, that’s the one to use, and the one your Slack redirect URL must
match).
What signing in looks like
There is no login button in the app. The form is gated, so the moment you open it the app sends you straight to Slack’s “authorize” page. Approve it there, and Slack returns you to the form. That round-trip is the sign-in. After that, a cookie keeps you signed in for 7 days.
Heads up: signing in accepts any Slack account that completes the flow — it isn’t limited to your workspace unless you add that check yourself. See How it works for the full picture of what’s gated and what’s public.
Step 6 — Submit a test entry
Fill in the form and press Submit. If everything is wired up:
- Confetti celebrates the submission. 🎉
- A new row appears in your
Conversationstab. - A message shows up in your Slack channel.
That’s the whole loop working end to end. Congratulations.
Signing out
Click Log out in the top navigation bar (it appears next to your name once you’re signed in). This clears your session cookie.
One thing to expect: because the form is gated, logging out immediately sends you back to the Slack sign-in flow — and since Slack still considers you signed in, you may bounce straight back into the app. That’s normal. To fully sign out, also sign out of Slack itself in that workspace.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Browser warns the site is unsafe | Self-signed HTTPS cert in dev — safe to click through. |
| Stuck redirecting to Slack | The Redirect URL in your Slack app doesn’t exactly match https://localhost:4321/api/auth/callback. |
Slack error not_in_channel | You didn’t /invite the bot into the channel. |
Slack error channel_not_found | SLACK_CHANNEL_ID is a name, not an ID, or the bot can’t see it. |
| Nothing written to the sheet | The sheet isn’t shared with the service account’s client_email, or SPREADSHEET_ID is wrong. |
Failed to parse GOOGLE_SERVICE_KEY | The JSON has real line breaks or got truncated — paste it as one line. |
| Startup error about a missing/invalid environment variable | You haven’t filled in the required secrets (SLACK_OAUTH, SLACK_CLIENT_SECRET, GOOGLE_SERVICE_KEY) in .env yet. Fill them and restart. |
Submitting the form returns 401 Authentication required | Your session expired or you’re not signed in — reload the form and sign in with Slack again. |
Changes to .env not picked up | Restart npm run dev; environment values are read at startup. |
Once the loop works, head to Configuration to tailor the sheet layout, channels, and the optional contact-matching feature — or How it works to understand the code.