Applet Structure
Reference for the parts of an applet that the platform constrains. Everything not described here is yours to decide.
Getting Started walks the same ground in build order. This page is organised by subject and is the one to come back to.
Project layout
An applet is one Angular application. A minimal one has four files that differ from any other Angular application:
my-applet/
├── angular.json # builder switched to custom-webpack
├── my-applet-webpack.config.js # unique bundle globals
├── elements-build.js # concatenates the build output into one file
└── src/
├── index.html # single placeholder element, id="customtag"
├── main.ts # standard platformBrowserDynamic bootstrap
├── environments/
│ └── environment.prod.ts # api_domain
└── app/
├── app.module.ts # defines the custom element; ngDoBootstrap() {}
├── app.routing.ts # one exported route-prefix constant
└── app.component.ts # root component; becomes the custom elementBigLedger’s own applets are built in shared Angular workspaces holding many applet projects at
once, which is why their paths are long (projects/<group>/applets/<applet>/src/…) and their
build commands always pass --project. That arrangement exists to share code across dozens of
internally-maintained applets. An external applet has no reason to copy it: use a single-project
workspace and drop --project from the build command.
The runtime contract
What the shell does
- The user launches your applet from the shell.
- The shell reads your registration record and generates an 8-digit instance number.
- It writes the session into browser storage (table below).
- It rewrites its own route table so that your registered route prefix points at its applet loader, and navigates there.
- The loader appends
<script src="{your bundle URL}" async defer>to the document head. - Loading your bundle registers your custom element as a side effect.
- The loader mounts
<your-tag-NNNNNNNN>.
Your applet has no lifecycle hook into any of this. It runs when its element is upgraded.
Session keys
| Key | Store | Written by | Meaning |
|---|---|---|---|
randomNumber | session | shell, per launch | 8-digit instance number; suffix of your element name |
tenantCode | session | shell | the tenant this launch is scoped to |
routerLink | session | shell | the route prefix the shell navigated to |
es_module_url | session | shell | the URL your bundle was loaded from |
appletMFTag | session | shell | the resolved element markup, not the tag: <your-tag-NNNNNNNN></your-tag-NNNNNNNN>, which the loader injects as HTML |
appletName, appletGuid, appletLoginSubjectLinkGuid | session | shell | registry values, rarely needed by an external applet |
authToken | local | shell, at sign-in | the signed-in user’s bearer token |
Treat all of these as read-only. authToken is the only one in local storage.
Two further keys appear in BigLedger’s own applets and are worth knowing about:
appletToken(session) — a token scoped to one applet within one tenant, rather than to the user across the platform. BigLedger’s shared layout obtains and refreshes it. An external applet using the user’sauthTokendoes not need it.appletCode(session) — the applet’s own registered code. BigLedger’s applets set it themselves inmain.tsfor local development, and their shared settings screens read it to decide which settings to render.
The four agreements
| Agreement | Your side | Registered as | Symptom when wrong |
|---|---|---|---|
| Bundle URL | where you host the file | ES Module Url | nothing loads; 404 in the network panel |
| Element name | customElements.define('my-applet-elements-' + randomNumber, …) | Custom Element Tag (bare, no suffix) | bundle loads, blank page, no error |
| Route prefix | the path of your top-level route | Applet Router | applet loads, then falls through to its wildcard route |
| Session keys | your reads | not configurable | the tenant context is missing; API calls unauthorised |
The element name and the route prefix are the two that fail quietly. Check them first.
Reaching the API
The client library
blg-akaun-ts-lib is on the public npm registry under the MIT licence. It is an Angular library:
@angular/core and @angular/common are peer dependencies, declared as >=11 <16. It contains
one injectable service per resource, plus the models and query-criteria classes those services
take and return.
Install it and inject the service you need. Do not construct URLs by hand — the library owns the URL shape, and the URL shape changes.
The API visa
Every service method takes an ApiVisa as its last argument:
interface ApiVisa {
tenantCode?: string; // which tenant the call is scoped to
applet_code?: string; // your registered applet code; defaults to 'none'
api_domain_url?: string; // 'https://api.akaun.com/'
jwt_secret: string; // the bearer token
AccessID?: string; // long-lived key pair, for server-to-server callers
AccessKey?: string;
}For an applet, tenantCode and jwt_secret come from browser storage and api_domain_url from
your environment file. AccessID / AccessKey are for integrations that run outside a browser
session; they are not how an applet authenticates.
The library turns the visa into request headers. An applet call carries authorization,
tenantCode and appId.
URL shape
The library composes each request URL as:
{api_domain_url}core2/{scope}/{layer}/{module}/{resource}/{access-endpoint}- scope —
tntfor tenant-scoped resources,platformfor platform-scoped ones. A large number of resources sit directly undercore2/dm/with no scope segment. - layer —
dm(domain) orms(micro-services). - module — the functional area:
erp,inv,fi,crm,hr,alg, and others. Often with a sub-module of its own (erp/fi/…,alg/cc/…). - resource — the plural resource name, e.g.
companies,items,generic-doc. - access endpoint — who is allowed to call it. Optional: roughly half the library’s services have no such segment.
Query calls append /query with the criteria as query parameters.
Example, the resource behind CompanyService:
https://api.akaun.com/core2/dm/companies — no scope segment and no access endpoint, which is
exactly why the advice above is not to compose URLs by hand.
Access endpoints
The last path segment is not decoration. It selects which category of caller the endpoint serves,
and the same resource is usually exposed under several. The client library reflects this in its
method names — getByCriteriaPublic targets the public endpoint, getByCriteriaLoginEntity the
entity-login one, and so on; the plain getByCriteria uses whatever path the service was built
with.
| Endpoint | Caller |
|---|---|
backoffice-ep | a signed-in staff user working inside the shell — this is the applet case |
login-ep | a signed-in user acting on their own records |
login-entity-ep | a signed-in external party — a customer or supplier acting on their own records |
public-ep | unauthenticated callers, where the resource permits it |
etl-ep | server-to-server integrations authenticating with a long-lived key |
website-ep | the storefront |
tenant-admin-ep, sysadmin-ep | administrative callers |
Unless your applet is deliberately serving customers or suppliers, use the plain method names.
Be aware that they do not always resolve to backoffice-ep: about half the library’s services
carry a backoffice-ep path, and the rest are built on a path with no access-endpoint segment at
all (CompanyService is one of those — its base URL is https://api.akaun.com/core2/dm/companies).
A handful target login-ep, sysadmin-ep or login-entity-ep instead. If it matters which
endpoint you hit, check the service rather than assume. The library has no etl-ep methods and
no etl-ep constant: that surface is for server-to-server integrations, not for applets.
Responses
interface ApiResponseModel<T> { code: string; message?: string; data?: T[]; }
interface BasicApiResponseModel<T> { code: string; message?: string; data?: T; }
interface PagingResponseModel<T> { data: T[]; totalRecords: number; offset: number;
limit: number; code: string; message: string; }code is a platform response code, not an HTTP status — read it, do not infer success from the
transport alone. List calls that take a Pagination return the paging envelope.
Settings
BigLedger models applet configuration at two levels.
At registration. A small number of values are fixed when the applet is registered — its route, its element tag, its bundle URL, its short code and the permission codes it declares it needs. Changing any of them means changing the registration record, which is a hand-off to BigLedger.
Per tenant. Everything a tenant can vary — which fields are visible, which are mandatory,
which defaults are pre-selected — is stored against the applet within that tenant and edited from
inside the applet. By convention this lives under a settings child route with one child per
settings screen, and a matching personalization route for per-user rather than per-tenant
preferences:
{route prefix}/settings/field-settings
{route prefix}/settings/default-selection
{route prefix}/settings/feature-visibility
{route prefix}/settings/permission-set-listing
{route prefix}/settings/user-permission-listing
{route prefix}/settings/team-permission-listing
{route prefix}/settings/role-permission-listing
{route prefix}/personalization/personal-default-selection
{route prefix}/personalization/sidebarBigLedger’s own applets add more under settings/ — general-settings, webhook,
permission-wizard-listing, release-notes, applet-log — where the applet needs them.
blg-akaun-ng-lib npm package (MIT), so
you can read it and use it — but the published package declares Angular 11 peer dependencies
and pins an old blg-akaun-ts-lib, so it does not install cleanly alongside anything newer. The
route convention above is worth following either way, so your applet is where users expect it to
be.Users reach these screens from the applet’s own navigation, not from a platform-wide settings area. An applet with no settings simply omits the routes.
Permissions
Permissions are defined by the platform, granted inside a tenant, and consumed by your applet.
- At registration, your applet names the permission codes it requires. These are selected from the codes the platform already defines; an applet does not invent permission codes.
- Inside a tenant, an administrator grants those permissions to users, teams and roles.
- At runtime, the API enforces them. A call the signed-in user is not permitted to make fails at the API, whatever your applet’s UI allows.
The practical consequence: do not treat your UI as the enforcement point. Hiding a button is a usability decision. Whether the operation is allowed is decided server-side, and your applet should handle the refusal.
If your applet needs a permission code that does not exist, that is a request to BigLedger, not something you can add.
Packaging and release
The bundle contract
One JavaScript file, fetched by <script src>, that registers a custom element as a side effect
of loading. That is the whole contract. It implies three things:
- No module system at load time. The file is loaded as a classic script, not as an ES module, despite the registration field being called ES Module Url.
- Everything is inlined. There is no code-splitting, no lazy chunk loading and no shared vendor bundle across applets. Each applet ships its own copy of Angular, so a bundle of a few megabytes is normal.
- Bundle globals must be unique. Two applets whose webpack chunk-loading globals or library names collide will break each other when both are loaded into one page. Derive both from your applet name.
styles.css is emitted next to the bundle. Component styles are inside the bundle; the separate
stylesheet carries the global styles declared in angular.json.
Versioning
The registration record holds one bundle URL. There is no version field, no channel and no staged rollout: every tenant with your applet installed loads whatever is at that URL the next time a user launches it.
Two consequences worth planning for:
- Releasing is overwriting. Keep the previous bundle so you can roll back by re-uploading it.
- Users get the new version on their next launch, not immediately. A session already running keeps the bundle it loaded.
If you need staging and production to differ, that is two registration records with two URLs, and they are installed into different tenants.
The registration record
What BigLedger records about your applet. You supply these values; BigLedger creates the record.
| Field | Required | Constraint |
|---|---|---|
| Applet Name | yes | display name in the shell |
| Applet Code | yes | no spaces; unique |
| Applet Short code | yes | two characters by convention, no spaces — nothing enforces the length, in the form or on the server, so pick two and stay consistent |
| Applet Type | yes | see below. The registration form does not validate it, but the server rejects a value outside the list |
| Applet Router | yes | your route prefix, verbatim |
| Custom Element Tag | yes | your element name without the instance suffix |
| ES Module Url | yes | must be a URL |
| Applet Store | yes | which catalogue the applet is listed in |
| Applet Vendor | yes | who publishes it |
| Required Permission | no | permission codes, selected from those the platform defines |
| Description | no | |
| Icon | no | image upload |
Applet types. The type determines the scope the applet is installed and launched at:
| Type | Scope |
|---|---|
TNT-USER | a normal user applet inside a tenant — the usual choice |
TNT-ADMIN | a tenant-administration applet |
ROOT-USER, ROOT-ADMIN | platform-level applets |
TNT-APPLET | a tenant applet registration that is neither of the two above |
ETL-CLIENT, SDK-CLIENT, CLI-CLIENT, PROGRAM-CLIENT, WEB-CLIENT | registrations for non-browser callers rather than for shell-mounted applets |
Those ten are the whole allowed set; anything else is rejected on save. Most applets in the
catalogue are TNT-USER.
Known limits
Stated plainly, because they change what is worth building.
- Registration is not self-service. There is no public API and no console for creating or editing a registration record. Every change to your route, tag or bundle URL is a request to BigLedger.
- The shared applet chrome is published only in an Angular 11 form. Layout, settings screens,
permission screens, personalization and search ship inside
blg-akaun-ng-libon public npm under MIT, but that package’s peer dependencies are Angular 11 and it pins an oldblg-akaun-ts-lib. BigLedger’s own applets consume a private submodule instead. On Angular 12 or later, read it and build your own. - There is no published applet starter. No CLI, no schematic, no template repository, and no reference applet registered in production that you could open and inspect.
- There is no local shell. You cannot run the shell locally, so end-to-end verification means
hosting a bundle and launching it in a real tenant. During development, serve the applet
standalone with
ng serveand set the session keys by hand — with norandomNumberpresent your element simply registers asmy-applet-elements-nulland mounts normally. BigLedger’s own applets do the equivalent, seeding development-only session values inmain.tsbehind anif (environment.production) { … } else { … }guard. - One URL per registration. No versions, no channels, no rollback other than re-uploading.
If any of these blocks you, say so in an issue at BigLedger-Support/public — several are product gaps rather than deliberate design.