Skip to content

Contributing

Thank you for contributing to KoAkademy! Contributions should be focused, well-tested, and safe for institutions that self-host the platform.

  • Search existing issues and pull requests to avoid duplicate effort.
  • Open a discussion or feature issue first for substantial behavior or architectural changes so maintainers can confirm direction.
  • Do not post vulnerabilities in public issues. Follow SECURITY.md to report security concerns privately through GitHub Advisory.
  • Keep pull requests focused: avoid mixing feature work with mass formatting, dependency bumps, or generated file changes.
  • Note: KoAkademy is an open-source project maintained by volunteers; no fixed response-time or merge-time SLA is promised.

KoAkademy runs on PHP 8.5, Laravel 13, Inertia 3, React 19, and Node.js 22. Follow Development for full installation instructions:

Terminal window
# 1. Clone the repository and install dependencies
git clone https://github.com/yukazakiri/koakademy.git
cd koakademy
composer install
npm ci
# 2. Configure environment and database
cp .env.example .env
php artisan key:generate
touch database/database.sqlite
php artisan migrate
# 3. Build frontend assets and start servers
npm run build
php artisan serve

Run npm run dev in a separate terminal when working on React portal components with hot module replacement (HMR).

Whether you are fixing a bug or adding a feature, follow this step-by-step workflow:

  1. Create a topic branch from master:
    Terminal window
    git checkout -b fix/enrollment-fee-calculation
  2. Write or update a Pest test that reproduces the issue or verifies the new behavior.
  3. Implement your changes following repository conventions (strict types in PHP, TypeScript in React).
  4. Run local validation before opening your PR.

Write feature tests under tests/Feature/. Use model factories and actingAs() to verify authorization and response payloads:

<?php
declare(strict_types=1);
use App\Enums\UserRole;
use App\Models\StudentEnrollment;
use App\Models\User;
use Inertia\Testing\AssertableInertia;
use function Pest\Laravel\actingAs;
it('allows registrars to view pending enrollment applicants', function (): void {
$registrar = User::factory()->create(['role' => UserRole::Registrar]);
$enrollment = StudentEnrollment::factory()->create();
actingAs($registrar)
->get(portalUrlForAdministrators('/administrators/enrollments'))
->assertOk()
->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page
->component('administrators/enrollment/index', false)
->has('enrollments.data', 1)
->where('enrollments.data.0.id', $enrollment->id)
);
});
it('forbids unauthorized roles from updating enrollment records', function (): void {
$student = User::factory()->create(['role' => UserRole::Student]);
$enrollment = StudentEnrollment::factory()->create();
actingAs($student)
->put(portalUrlForAdministrators("/administrators/enrollments/{$enrollment->id}"), [
'status' => 'approved',
])
->assertForbidden();
});

2. Frontend React Form (Inertia 3 + Wayfinder)

Section titled “2. Frontend React Form (Inertia 3 + Wayfinder)”

Frontend components live in resources/js/pages/. Use generated Wayfinder route helpers from @/actions or @/routes with Inertia’s useForm:

import { store } from "@/actions/App/Http/Controllers/AdministratorEnrollmentPolicyController";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useForm } from "@inertiajs/react";
import type { FormEvent } from "react";
interface CreatePolicyProps {
onSuccess?: () => void;
}
export function CreatePolicyForm({ onSuccess }: CreatePolicyProps) {
const form = useForm({
name: "",
preset: "legacy",
school_id: null as number | null,
});
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
form.post(store.url(), {
preserveScroll: true,
onSuccess: () => {
form.reset();
onSuccess?.();
},
});
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Blueprint Name</Label>
<Input
id="name"
value={form.data.name}
onChange={(e) => form.setData("name", e.target.value)}
placeholder="College enrollment foundation"
aria-invalid={Boolean(form.errors.name)}
/>
{form.errors.name && (
<p className="text-destructive text-sm" role="alert">
{form.errors.name}
</p>
)}
</div>
<Button type="submit" disabled={form.processing}>
{form.processing ? "Saving..." : "Create Blueprint"}
</Button>
</form>
);
}

Before submitting your pull request, verify that every check passes locally:

Terminal window
# 1. Format and check PHP code style
vendor/bin/pint --test
# 2. Run backend test suite
php artisan test --parallel --compact
# 3. Build frontend bundle
npm run build
# 4. Check documentation mirrors and link integrity
npm run docs:check
# 5. Build documentation site
npm --prefix docs run build
# 6. Verify production Docker Compose syntax
KOAKADEMY_ENV_FILE=.env.production.example \
docker compose --env-file .env.production.example -f compose.production.yaml config --quiet
  • Tests: Add or update Pest tests for all behavioral changes.
  • Migrations: Add new migrations for schema changes; never modify or delete historical, released migrations.
  • Documentation: When user-visible behavior, configuration, or architecture changes, update the canonical root docs and run npm run docs:sync.
  • Secrets & Data: Ensure no credentials, personal records, tokens, or environment files are included in commits.
  • PR Titles: Use a concise, descriptive title. CI validates Conventional Commit format (e.g., feat(enrollment): add scholarship fee override or fix(auth): preserve tenant scope on 2FA challenge).
  • PHP: Strict typing enabled (declare(strict_types=1); on all new files), formatted with Laravel Pint. Follow Laravel 13 patterns (Form Requests, Eloquent Policies, Enums).
  • TypeScript / React: Strict TypeScript, React 19 patterns, shadcn/ui components, and Tailwind CSS 4 utility classes.
  • Accessibility: Ensure keyboard accessibility, visible focus rings, ARIA labels for icon buttons, and respect for prefers-reduced-motion.

KoAkademy maintains a single-source documentation system:

LocationRoleHow to Edit
GETTING_STARTED.md, DEPLOYMENT.md, CONFIGURATION.md, TROUBLESHOOTING.md, DEVELOPMENT.md, ARCHITECTURE.md, CONTRIBUTING.md, FAQ.mdCanonical SourcesEdit directly in the root directory.
docs/src/content/docs/start-here/contributing.mdx, etc.Generated MirrorsDo not edit manually. Run npm run docs:sync.
docs/src/content/docs/system/*, docs/src/content/docs/user-guide/*, docs/src/content/docs/enrollment-policies/*, docs/src/content/docs/api/*Native Docs PagesEdit directly under docs/src/content/docs/.

Standalone domain extensions (such as the Forms module) live in dedicated repositories under the KoAkademy module registry. Check the Module Creation Guide and registry guidelines before publishing a new module package.

Regulatory exports use a configuration-driven adapter contract so contributors can add compatible providers without coupling the shared registrar controller to one jurisdiction. Read the Regulatory Report Provider guide before changing this area.

Only the CHED E-Form B/C provider is built into this repository today. A new provider is an opt-in addition: it must implement App\Contracts\RegulatoryReportAdapter, add a definition under config/regulatory-reports.php (or an equivalent module configuration), and include authorization, tenant-isolation, preview, and workbook tests. Do not describe an unimplemented provider as supported, and do not add provider-specific assumptions to shared school or enrollment models.

If a provider has a user-facing registrar template, add its frontend template definition and renderer as part of the same contribution. The generic preview and export routes are available to configured providers, while the existing CHED template and legacy CHED routes remain compatible with current installations.

By contributing, you agree that your code will be licensed under KoAkademy’s GNU AGPL-3.0-or-later license. Ensure you have the rights to submit the code and preserve copyright notices for third-party material.