Skip to main content
The @edpire/sdk/react entry exports three React components:
ComponentUse when
EdpireAssessmentPlayerYou want the simplest integration — handles token fetch, mount, and cleanup. Start here.
AssessmentShellYou fetch the assessment yourself and want full control over data flow + submission.
EdpireQuestionYou’re building a Custom Flow (question-by-question, Duolingo-style).
import { EdpireAssessmentPlayer, AssessmentShell, EdpireQuestion } from "@edpire/sdk/react"
All components require React 19. CSS is bundled and injected automatically — no stylesheet imports needed.

EdpireAssessmentPlayer

The recommended way to embed a player in React. Wraps EdpireAssessment.mount() and handles token fetching, mounting, unmounting, and StrictMode double-invoke cleanup — the boilerplate you’d otherwise write with useRef + useEffect.
import { EdpireAssessmentPlayer } from "@edpire/sdk/react"

function LessonPage({ assessmentId }: { assessmentId: string }) {
  return (
    <EdpireAssessmentPlayer
      tokenEndpoint="/api/edpire/token"
      assessmentId={assessmentId}
      onComplete={(r) => console.log("Score:", r.score, "/", r.max_score)}
      onError={(e) => console.error(e.code, e.message)}
      style={{ width: "100%", height: "100vh" }}
    />
  )
}
Pair this with createEdpireTokenHandler() on the server — see Embedded Player → Quickstart.

EdpireAssessmentPlayerProps

PropTypeRequiredDescription
tokenEndpointstringYesURL of your server endpoint that returns { token }. The component POSTs { assessmentId } here.
assessmentIdstringYesAssessment UUID to embed. Sent to tokenEndpoint in the POST body.
tokenBodyRecord<string, unknown>NoExtra fields merged into the POST body (e.g. course ID for server-side validation).
onComplete(result: EmbedResult) => voidNoCalled on successful submission.
onError(error: EmbedError) => voidNoCalled on non-recoverable errors (token failure, origin blocked, etc.).
locale"en" | "fr" | "ar"NoUI language. RTL applied automatically for "ar".
brandingTakeBrandingNoOverride org branding (white-labelling).
returnUrlstringNoPost-submit redirect URL with score params appended.
reportLabelstringNoCustom label for the report button.
onBack() => voidNoBack button handler (shown before submission).
backLabelstringNoCustom back button label.
overlayConfigOverlayConfigNoCustomise the grading overlay.
mediaHandlerQuestionRuntimeProps["mediaHandler"]NoRequired for file-upload/recording OpenResponse questions.
baseUrlstringNoEdpire instance URL. Defaults to https://edpire.com.
classNamestringNoCSS class for the container <div>. Use this to size the player (e.g. "h-screen").
styleReact.CSSPropertiesNoInline style for the container. Defaults to { width: "100%", height: "100%" } when className is absent.
The player fills its container — you control the size via className or style.
These are React 19 components. CSS is bundled and injected automatically — no stylesheet imports needed.

AssessmentShell

The complete assessment-taking UI: sticky top bar with logo + countdown, exercise sections with score badges, submit confirmation, grading overlay, post-submit score banner, and per-question feedback. You supply the assessment data and an onSubmit handler that does the grading.
import { AssessmentShell } from "@edpire/sdk/react"
import type { SubmitResult } from "@edpire/sdk/react"

function Player({ assessment, meta }) {
  async function handleSubmit(answers): Promise<SubmitResult> {
    const res = await fetch("/api/grade", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ answers }),
    })
    if (!res.ok) throw new Error("Grading failed")
    return res.json()
  }

  return (
    <AssessmentShell
      assessment={assessment}
      meta={{ title: "Unit 3 Quiz", category: "Grammar" }}
      locale="en"
      onSubmit={handleSubmit}
    />
  )
}
What your /api/grade server endpoint must return. client.submit() returns GradeResult (snake_case); AssessmentShell expects SubmitResult (camelCase). Map between them on the server:
// POST /api/grade/route.ts
import { EdpireClient } from "@edpire/sdk/client"

const client = new EdpireClient({ apiKey: process.env.EDPIRE_API_KEY! })

export async function POST(req: Request) {
  const { assessmentId, answers } = await req.json()
  const result = await client.submit(assessmentId, {
    learner_ref: session.userId,
    answers,
  })

  // Map GradeResult (snake_case) → SubmitResult (camelCase)
  return Response.json({
    submissionId: result.submission_id,
    totalScore: result.score,
    maxScore: result.max_score,
    percentage: result.percentage,
    passed: result.passed,
    exerciseFeedback: [],   // GradeResult doesn't include per-node feedback;
                            // the score banner shows, but inline corrections won't.
    awaitingManualGrading: false,
  })
}
client.submit() returns per-question scores (exercise_results) but not the per-node visual feedback (exerciseFeedback) that AssessmentShell uses to highlight incorrect answers inline. If you need both, use the Embedded Player instead — it gets the full response from Edpire automatically.
When to use AssessmentShell vs EdpireAssessmentPlayer: EdpireAssessmentPlayer fetches assessment data client-side after mount and handles everything for you — start there. Drop down to AssessmentShell only when you need to control data loading yourself: server-rendering the assessment for a faster initial paint, pre-fetching it before the learner navigates, or plugging into your own caching layer. AssessmentShell expects data in the runtime shape (AssessmentContent), not the raw REST Assessment — you’re responsible for that conversion.

AssessmentShellProps

PropTypeRequiredDescription
assessmentAssessmentContentYesThe assessment content (runtime shape) to render.
meta{ title: string; category: string | null }YesTitle + category shown in the top bar.
onSubmit(answers) => Promise<SubmitResult>YesGrades the attempt. Should throw on failure (the shell shows an error banner).
locale"en" | "fr" | "ar"NoShell string language. Default "en". RTL auto-applied for "ar".
brandingTakeBrandingNoLogo, colors, fonts. See Branding.
timeLimitMinutesnumber | nullNoShows a countdown chip; auto-submits at 0:00.
startedAtstring (ISO)NoISO timestamp when the attempt started. If provided, the countdown picks up from where it left off after a page refresh instead of restarting from the full limit.
onViewReport(result: SubmitResult) => voidNoCalled when the learner clicks “View Full Report”. Omit to hide the button.
reportLabelstringNoCustom label for the report button.
onBack() => voidNoCalled when the learner clicks the Back button in the top bar (visible only before submission). Use it to navigate back in your app or close a modal.
backLabelstringNoCustom back-button label (default: localized “Back”).
allowResetbooleanNoShow a Reset button in the top bar.
onReset() => voidNoReset handler.
onClose() => voidNoShow a close (✕) button and handle it.
overlayConfigOverlayConfigNoCustomise the grading overlay. See Branding.
containedbooleanNoScope the grading and time-up overlays to the shell’s bounding box instead of the full viewport. Use when the player is embedded in a page alongside other content, rather than filling the full screen.
mediaHandlerQuestionRuntimeProps["mediaHandler"]NoRequired for file-upload/recording OpenResponse questions. See mediaHandler example.
fallbackLogoUrlstringNoLogo shown when branding.logoUrl is absent. Defaults to an inline Edpire SVG.

SubmitResult (returned by onSubmit)

interface SubmitResult {
  submissionId: string
  totalScore: number
  maxScore: number
  percentage: number
  passed: boolean
  /** If null/undefined, the "View Full Report" button is hidden. */
  returnUrl?: string | null
  exerciseFeedback: ExerciseFeedbackData[]
  /** When true, score banners hide the score (still under review). */
  awaitingManualGrading?: boolean
}
exerciseFeedback drives the inline per-question feedback. See the type catalog for ExerciseFeedbackData, QuestionFeedback, and NodeFeedback.

EdpireQuestion

Renders one question and reports answer changes. The core building block for Custom Flow.
import { EdpireQuestion } from "@edpire/sdk/react"
import type { RuntimeAnswer } from "@edpire/sdk/react"

function QuestionStep({ step }) {
  const [answers, setAnswers] = useState<RuntimeAnswer[]>([])
  const [feedback, setFeedback] = useState<Record<string, unknown> | null>(null)

  return (
    <EdpireQuestion
      content={step.content}        // ContentAST from flattenAssessment()
      onAnswersChange={setAnswers}
      feedback={feedback}           // null = no feedback; pass /check result to show it
      dir="ltr"                     // "rtl" for Arabic
    />
  )
}

EdpireQuestionProps

PropTypeRequiredDescription
contentunknownYesThe question’s content_ast (a FlatStep.content).
onAnswersChange(answers: RuntimeAnswer[]) => voidNoFires on every answer change. Collect these for /check and submit.
feedbackRecord<string, unknown> | nullNoPass the feedback field from POST /check directly — the component converts it internally. null/undefined clears feedback.
initialAnswersRuntimeAnswer[]NoPre-fill answers (e.g. restoring state when navigating back).
classNamestringNoClass added to the question root <div>.
dir"ltr" | "rtl"NoText direction. Default "ltr".
feedback takes the raw JSON from the /check endpoint — no manual mapping. Internally the component converts it to the Map the runtime expects.

GradingOverlay

The branded spinner overlay shown while grading. You don’t usually render it directly — AssessmentShell and the Embedded Player show it for you and accept an overlayConfig. The OverlayConfig / SpinnerTheme types and theming options are documented under Branding → Grading overlay.

Working examples

See the starter templates for complete, runnable integrations:
npx --package=@edpire/sdk create-edpire-app nextjs my-app       # EdpireAssessmentPlayer + token handler
npx --package=@edpire/sdk create-edpire-app vite-express my-app  # same, with Vite + Express
For a Custom Flow (question-by-question), see the Custom Flow guide.