Bhasha Setu · Backend & Content Studio

The Content Studio Knowledgebase

How the backend is built and how to run it: the data model, every collection, all activity types, the publish pipeline, roles, the API, and how to operate it day to day.

Payload CMS 3 · Next.js 15 · Postgres13 collections + 1 global~12 activity typesVersioned offline packs
01 Quick start

From login to the classroom in six steps

Sign in at the studio URL with the email and password the team gives you. Then: read your dashboard, find content (now by name, not code), build an activity in a friendly form, preview it live, send it through review, and publish.

studio · Dashboard
Bhasha Setu · Content Studio

नमस्ते, you

20Words
51Activities
49Modules
24Learners
Needs review · अभिवादन · सुनो और चुनोIn review

As you edit an activity, Live Preview plays it in the real app engine — same audio, same buttons. What you build is exactly what the teacher sees.

studio · Activity edit + live preview
An activity edit screen beside a live preview of the app playing it
02 Data model

What lives in the studio

Content is organised into collections, grouped in the left nav. Records now show a readable name, not a slug. “Versioned” means the collection keeps drafts separate from what's published.

Learning Content

CollectionWhat it isKey fieldsVersioned
LanguagesEach target language (Halbi, Bhatri, Ho…) plus Hindi.code, name, native_name
Learning Paths
(Courses)
An ordered set of modules in one language, e.g. Language Fundamentals.label, slug_id, title.hi, order, languageDrafts
ModulesA lesson within a course — the sequence of activities a learner completes together.label, slug_id, title.hi, order, xp_display, learning_pathDrafts
ActivitiesA single learning interaction inside a module.title_hi, module, type, order, payload (validated JSON)Drafts
WordsThe dictionary — words & phrases with audio used across activities.text, transliteration, translation, audio, languageDrafts
MediaUploaded audio & images. Pick from here inside any activity.upload, alt, sha256/bytes/mime (auto)

Learners & Progress

CollectionWhat it isKey fieldsNotes
LearnersTeachers who registered in the app.learner_ref, name, phone, language, role, subjects, state, profile_completeCreated by the app on sign-up
Learner StatsA live projection of each learner's XP, level and streak.total_xp, level, level_name, streak_days, last_event_atRead-only (computed)
Xp EventsThe progress ledger — one row per completed activity.event_key, learner_ref, activity_ref, score, xp_earned, attempted_atImmutable

System (admins only)

CollectionWhat it isKey fieldsNotes
PacksThe published output the app downloads — one versioned bundle per module.pack_id, pack_version, language, pack_doc, statusImmutable; current / superseded
Audit LogWho published what, and when.action, collection_slug, doc_id, actor_email, actor_roleImmutable
Otp ChallengesShort-lived phone-OTP records for app login.phone, code_hash, expires_at, attemptsHashed + peppered
UsersStudio author accounts.email, password, roleadmin / reviewer / editor

Global · Gamification Config — one settings record: XP defaults (e.g. listening_quiz_per_question = 10, flash_card_per_card = 5) and the level-up thresholds (level · name · min_xp).

03 Activity types

One schema, three jobs

Each activity type is defined once as a JSON Schema (schemas/activity.<type>.schema.json). That single definition drives the authoring form, validates content on publish, and tells the app which mechanic (interaction engine) to render. Adding a type = add a schema.

MechanicActivity typeWhat the learner does
tap_selectListening QuizHears audio, taps the option that matches.
tap_selectFill in the BlankPicks the word that completes a sentence (marked ____).
tap_selectWord CloudSelects the right words from a scattered set.
flip_swipeFlash CardsTaps to flip; swipes right = Known, left = Still Learning.
match_pairsMatching GameMatches pairs (word ↔ meaning, word ↔ picture).
place_labelPicture LabellingDrags labels onto the right parts of a picture.
assemble_orderDialogue BuilderDrags chips into order to build a phrase that matches a meaning.
passive_playInstructions / ReadingReads a passage — non-interactive.
passive_playAudio NarrationListens to phrases (each with audio) and repeats aloud.
passive_playVideo SnippetWatches a short clip.
record_comparePronunciation ImitationRecords their voice and compares to the model.

You don't write JSON. Pick the Type and the form shows the right fields — text boxes, an options list with a “correct” toggle, and a media picker for audio/images. The studio validates it against the schema when you save.

04 Content lifecycle

From draft to published

Every content record carries a review status alongside its draft/published state. Only approved content can be published (admins may bypass). Moves between statuses are role-gated.

StageStatusWho moves it forward
You write itDraftEditor
Send for reviewIn reviewEditor → submits
Reviewer checks itApproved or Changes requestedReviewer decides
Goes livePublishedReviewer or Admin (must be Approved)

If a reviewer requests changes, it returns to the editor, who fixes it and re-submits. Publishing is always a deliberate, separate action from saving a draft.

An operator can collapse the workflow to admin-only publish by setting REVIEW_WORKFLOW_ENABLED=false — useful for a small launch team.

05 How content reaches the app

The pack pipeline

The app never talks to the database directly. Publishing a module builds a versioned pack — a self-contained bundle the app downloads and runs offline.

Publish a moduleIts activities must be approved & published.
Rebuild queuedA rebuildPack job fires automatically.
Pack builtEach activity payload is re-validated against its schema, then bundled.
Versioned + storedNew pack is current; the old one becomes superseded.
App syncsApp reads /api/pack-index, downloads the new pack, renders offline.

Because the rebuild is asynchronous, a freshly published change appears in Packs as a new current version within moments, and reaches a device on its next sync.

06 Roles & permissions

Who can do what

CapabilityEditorReviewerAdmin
Create & edit content
Send for review
Approve / request changes
Publish (push to app)
Delete content
Manage Users, Packs, System & Audit Log

✍️ Editor

Authors and edits content; submits it for review.

✅ Reviewer

Works the review queue; approves or sends back; can publish.

⚙️ Admin

Everything, plus users, packs, system collections and the audit trail.

07 API surface

The endpoints the app uses

The Flutter app talks to a small, stable API. Learner endpoints require a phone-OTP JWT; studio endpoints use the CMS session.

EndpointMethodPurposeAuth
/api/auth/request-otpPOSTSend a login code to a phone.Public
/api/auth/verify-otpPOSTVerify the code, return a learner token.Public
/api/meGETCurrent learner's profile.Learner
/api/me/profilePOSTUpdate profile (name, language, role…).Learner
/api/me/statsGETXP, level and streak.Learner
/api/pack-indexGETList the current packs to download.Learner
/api/pack/[packId]GETDownload one versioned pack.Learner
/api/ingest-eventsPOSTSubmit completed-activity events (progress).Learner
/api/activity-schemaGETSchema for a type — powers the authoring form.Studio
/api/preview-activityGET/POSTBuild the preview shown in Live Preview.Studio
08 Configuration

Environment

The studio is configured entirely through environment variables — see cms/.env.example. Secrets are generated per environment; the server refuses to start in production without the required ones.

VariableWhat it doesRequired
DATABASE_URIPostgres connection string.Yes
PAYLOAD_SECRETEncrypts studio sessions.Yes
AUTH_JWT_SECRETSigns learner phone-OTP tokens (HS256).Prod
OTP_PEPPERExtra secret mixed into the OTP hash before storage.Prod
NEXT_PUBLIC_SERVER_URLThe public URL of the studio.Yes
ASSET_BASE_URLCDN base for media URLs in packs.Yes
REVIEW_WORKFLOW_ENABLEDtrue = full review gate; false = admin-only publish.Optional
09 Operations

Running it day to day

Spot a bug? Note the screen, what you did, and what you expected — and send it over. The Audit Log records every publish, which helps trace what changed.

10 Glossary

Words used here

Pack
A versioned, self-contained bundle of one module's content that the app downloads and runs offline.
Mechanic
The app's interaction engine a type maps to — e.g. tap_select, flip_swipe, match_pairs.
Payload
The type-specific content of an activity, stored as JSON and validated against that type's schema.
learner_ref
A stable learner ID linking an app account to its progress and stats.
Draft vs Published
Payload's versioning: edits live as drafts; only published content builds packs.
Current vs Superseded
Pack status: the newest pack for a module is current; older ones are superseded but kept.