Skip to content

Capstone: build "Employee Leave Management" ​

You will build a real application from zero and touch every kind of feature Studio ships in a plugin. The finished plugin is in the repository at help-docs/studio/capstone/leave-management/spk-assembly (27 files). It was installed on the production platform and checked by frontend/e2e/check-capstone-prod.mjs (25 of 25 checks pass), then uninstalled.

What the finished application contains ​

FeatureFile in the pluginProven on production
Application, module, menuapplication/, module/, menu/leave-management-menu.jsoninstalled, menu registered
Entities (3) with types, unique, indexed, enum, referencesentities/lm_leave_type, lm_leave_request, lm_leave_balancetables created, records created and listed
Roles with permissions (3)plugin.json rolesinstalled
Seed dataseed-data/leave-types.json (Annual, Sick, Unpaid)rows present after install
Providers (3)provider/installed
Data services (count KPIs)data_service/returns a live count
Data view (picker options)data_view/lm-leave-type-options-view.jsoninstalled
Page (list with search, add, edit)page/leave-types.jsoninstalled
Form: sections, conditional required, cross-field date check, submit actionform/leave-request-form.jsoninstalled (not clicked through)
Dashboarddashboard/leave-dashboard.jsoninstalled
Rules that reject (numeric and date-order)rules/lm_leave_request_days.json, lm_leave_request_dates.json400 with the message
Rule that starts a workflowrules/lm_leave_request_submit_workflow.jsonnew workflow instance
Workflow: two approvals, roles, SLA with breach stageworkflow/leave-management.leave-approval.jsonstarted by the rule
Reportreport/leave-type-list.jsoninstalled
Print template with QR codeprint_template/leave-approval-slip.jsoninstalled
Mobile navigationmobile_nav/leave-mobile-nav.jsoninstalled
Workspaceworkspace/leave-workspace.jsoninstalled
Themetheme/leave-calm.jsoninstalled
Translationsi18n/en.jsoninstalled
Declared extension pointplugin.json extensionPointslisted by the platform

"Installed" means the platform accepted it and it appears in its Studio list. Where a step needs a person to click, it says so.

The 25 steps ​

01 Requirements. Write the one-page requirements. 02 Create the plugin. Wizard, type Full application, id leave-management. 03 Entities. Add lm_leave_type, lm_leave_request, lm_leave_balance (Entities). 04 Relationships. employee_id and leave_type_id reference fields. 05 Security. Roles LEAVE_HR_ADMIN, LEAVE_MANAGER, LEAVE_EMPLOYEE; add record scope and field permissions in the Permission designer (Security). 06 Navigation. Application, module, menu, workspace, mobile nav (Navigation). 07 Employee page. The list page over a provider (Pages). 08 Request form. Sections, reason required only for unpaid leave, date order check (Forms). 09 APIs. Use the generated entity API; call it with a token (API Designer). 10 Business logic. The reject rule and the submit rule (Rules). 11 Approval workflow. Manager, then HR, two-day SLA. 12 Events. Rule after update on the request. 13 Notifications. A template per event, sent from the workflow. 14 Scheduled job. Nightly accrual (Jobs). 15 Dashboard. KPI cards over the count services (Dashboards). 16 Reports. The leave type report and the approval slip (Reports). 17 Integration. Payroll connector and integration flow (Integrations). 18 Tests. Test cases and simulate (Testing). 19 Security scan. Manual review today (Security scanning). 20 Build. Validate. 21 Package. .spk. 22 Deploy. Publish. 23 Install in a tenant. 24 Configure. Assign roles, adjust entitlements. 25 Upgrade. Raise the version, publish again.

Do it with the files ​

bash
cd help-docs/studio/capstone/leave-management
node ../../../../tools/spark-cli/spark.js validate spk-assembly
node ../../../../tools/spark-cli/spark.js package spk-assembly -o leave-management.spk

Then in Plugin Manager choose Upload and pick the file, or in Plugin Studio File, Import plugin (.zip / .spk). After install:

bash
# a leave type (dates are ISO timestamps)
POST /api/v1/entities/lm_leave_type/records       {"type_code":"STUDY","type_name":"Study leave","annual_days":5}
# a request, then submit it (this starts the approval workflow)
POST /api/v1/entities/lm_leave_request/records    {"request_number":"LR-1","employee_id":1,"leave_type_id":1,"from_date":"2026-10-01T00:00:00Z","to_date":"2026-10-03T00:00:00Z","days":3}
PUT  /api/v1/entities/lm_leave_request/records/1  {"status":"PENDING_APPROVAL"}
# zero days is rejected with "A leave request must be for at least one day."

Lessons the build taught (read these before you start) ​

  1. A published version is immutable. Installing changed content under the same version fails ("Refusing to overwrite immutable artifact"). Raise the version every time.
  2. Uninstall keeps your data. Tables and rows stay so a reinstall finds them; a unique key you tested with is still there.
  3. Rules can compare fields and dates. lt, lte, gt, gte order numbers and dates (ISO strings or timestamps), and ${record.other_field} compares with another field of the same record. Two values that cannot be ordered give a clear 400 message.
  4. Send dates as ISO timestamps, and update with only the changed fields.
  5. Lists are GET ...?page=0&size=20.
  6. Rules from an earlier version stay installed. If you rename or delete a rule in a new version, delete the old one in Entity rules.
  7. Name every role. An unnamed role fails the install.

Try it yourself in the designers ​

Open the installed plugin's page in the Page Designer and use Customize to change its title without touching the plugin, then bind the dashboard's KPI cards to lm-requests-pending-count. Both are covered in Customize and Dashboards.