How to Embed Paprel Accounting UI in Your App
Production Web Components for accounting and reports — your auth, your chrome, and your navigation.

You can integrate Paprel over the REST API and build every screen yourself. Many teams start there — and our 20-minute API walkthrough is the fastest way to prove the ledger loop.
But if your product already has navigation, layout, and branding, you shouldn't have to rebuild chart-of-accounts trees and journal line editors from scratch. Paprel Embedded Accounting UI ships that surface as framework-agnostic Web Components backed by the same Core API and permissions model as the Paprel workspace.
This guide covers the published 0.1.0-beta package line: install, authenticate, mount components, theme them, and connect host-owned routing and URL state. The canonical reference is kept current as the packages evolve.
Canonical docs: Step-by-step reference lives under Embedded UI — overview, build the embed token backend (BFF), get started, patterns, and per-component pages. This blog post is the narrative walkthrough.
When to use embed UI vs API-only
| Approach | Best for |
|---|---|
| REST / SDK only | Fully custom UX, server-side automation, batch jobs, agents |
| Embed Web Components | Production operator accounting inside your app shell — accounts, journals, banking, reconciliation, and reports |
| MCP | AI agents (ChatGPT, Claude, Cursor) calling tools against the same books |
Embed UI is not a separate product fork. Components call the same endpoints your integration would; validation, posting rules, and double-entry logic stay on Core.
Architecture: keep secrets on your server
The browser never holds App Connect client_secret. Your backend-for-frontend (BFF) exchanges credentials for a short-lived access token and hands only the token to the embed SDK:
Browser Partner backend (BFF) Paprel API
│ │ │
│ GET /api/embed-token │ │
│ ─────────────────────────► │ POST /v1/app-connect/ │
│ │ oauth/token │
│ │ ────────────────────────► │
│ { accessToken, expiresAt }│ ◄──────────────────────── │
│ ◄───────────────────────── │ │
│ │ │
│ GET /v1/accounting/… │ │
│ Authorization: Bearer … │ │
│ ─────────────────────────────────────────────────────► │
For token exchange details, see Build the embed token backend (BFF). For creating credentials, see Authentication: client credentials to access token. For scope design, see OAuth scopes and permissions for embedded accounting.
Get started
Install
npm install @paprel/embed-core @paprel/embed-accounting @paprel/embed-reports
Install the shared foundation and the domain packages you need:
| Package | Role |
|---|---|
@paprel/embed-core | HTTP client, token lifecycle, typed resources, i18n helpers |
@paprel/embed-ui | Semantic theme contract, included by domain packages |
@paprel/embed-accounting | Typed resources and Web Components for operational accounting |
@paprel/embed-reports | Trial balance, balance sheet, P&L, cash flow, and general ledger |
Configure once at app boot
Call configureAccounting before mounting components. Every accounting and report widget reads auth and API base URL from this shared context:
import { configureAccounting } from "@paprel/embed-accounting";
configureAccounting({
baseUrl: "https://api.paprel.com", // or "" when proxying /v1 on same origin
locale: "en", // en | ko | es | ru — system account labels follow Paprel i18n keys
auth: {
partnerDomain: "app.yourproduct.com",
async getTokens() {
const res = await fetch("/api/embed-token", { credentials: "include" });
const { accessToken, expiresAt, permissions, companyId } = await res.json();
return { accessToken, expiresAt, permissions, companyId };
},
onSessionExpired() {
window.location.href = "/login";
},
},
});
// Register custom elements after configure
import "@paprel/embed-accounting";
import "@paprel/embed-reports";
Mount components
<!-- Chart of accounts (flat grid by default; add tree for nested hierarchy) -->
<paprel-chart-of-accounts></paprel-chart-of-accounts>
<!-- Journals: list + read-only detail -->
<paprel-journal-list page="1"></paprel-journal-list>
<paprel-journal-detail journal-id="…"></paprel-journal-detail>
<!-- Create or edit a manual journal -->
<paprel-journal-editor currency="USD"></paprel-journal-editor>
<!-- Reusable account picker (used inside the editor; usable standalone) -->
<paprel-account-select label="Account"></paprel-account-select>
That is the entire embedding pattern — no React provider tree required. Use the same tags in Vue (:journal-id), React (pass attributes as props on the custom element), or plain HTML.
Component catalog (v1)
| Element | Purpose | Notable attributes / events |
|---|---|---|
paprel-chart-of-accounts | Read-only COA flat grid | tree attribute; refresh() method |
paprel-account-select | Grouped account <select> | value, label; fires account-change |
paprel-journal-list | Paginated journal table | page; fires journal-select with { journalId } |
paprel-journal-detail | Header metadata + line grid | journal-id |
paprel-journal-editor | Create / edit manual journal | journal-id, currency; fires journal-saved |
Typical master–detail wiring:
document.querySelector("paprel-journal-list")?.addEventListener("journal-select", (e) => {
const detail = document.querySelector("paprel-journal-detail");
if (detail) detail.journalId = e.detail.journalId;
});
document.querySelector("paprel-journal-editor")?.addEventListener("journal-saved", () => {
document.querySelector("paprel-journal-list")?.refresh?.();
});
Components expose imperative refresh() where caching matters (accounts tree/list). Journal reads always hit the network on load.
Permissions
Grant scopes on your App Connect client for the surfaces you mount. Examples for COA + journals v1:
accounting:account-list
accounting:journal-list
accounting:journal-add
UI may hide actions based on permissions[] returned with the token; Core still enforces every write. Add banking scopes when you enable transaction widgets in a later release:
accounting:banking-connection-list
accounting:banking-transaction-list
accounting:banking-transaction-reconcile
accounting:match-policy-apply
Match your product chrome
Components inherit typography and color from CSS custom properties on a wrapper — no shadow-DOM piercing required for the basics:
.paprel-shell {
--paprel-color-primary: #0f172a;
--paprel-color-primary-text: #ffffff;
--paprel-color-border: #e5e7eb;
--paprel-color-muted: #64748b;
--paprel-font-family: "Inter", system-ui, sans-serif;
--paprel-radius: 12px;
}
<div class="paprel-shell">
<paprel-journal-list page="1"></paprel-journal-list>
</div>
System account names (e.g. Primary Operating Account) resolve through the embed i18n catalog synced with the Paprel workspace — set locale in configureAccounting to match your app language.
Local sample app
The public Paprel Embed examples repository contains a framework-neutral, production-shaped real-estate application. It demonstrates host-owned routing, multi-entity App Connect sessions, shared events, URL state, accounting workflows, and reports.
git clone https://github.com/nexara-global/paprel-embed-ui-examples.git
cd paprel-embed-ui-examples
npm install
cp apps/real-estate-accounting/.env.example apps/real-estate-accounting/.env.local
npm run dev
Open http://127.0.0.1:5181/. Use its local-only BFF and host adapters as references before porting the pattern into your production stack.
Pass criteria: connected session chip, COA loads, journal list loads, row click opens detail, editor saves without console errors.
Headless escape hatch
Teams that want full control use the typed resources from the accounting domain package:
import { createAccountingClient } from "@paprel/embed-accounting";
const client = createAccountingClient({ baseUrl, auth });
const tree = await client.accounts.tree();
const journals = await client.journals.list({ page: 1 });
Same auth contract, no Web Components. TypeScript SDK generation from OpenAPI is documented in How to use OpenAPI to generate and maintain an SDK.
AI agents on the same books
Embed UI targets human operators in your app. Paprel MCP targets AI clients — company-bound endpoint, OAuth discovery, scoped tools. Both sit on App Connect tokens and the same ledger. See Paprel MCP for AI agents and accounting workflows and the Embedded Accounting product page for the full picture.
What ships today
The published beta packages cover accounts, journals, banking, transactions, matching, reconciliation, transaction locks, trial balance, balance sheet, income statement, cash flow, and general ledger. Invoices, bills, expenses, and credit notes remain API-first surfaces.
Where to go next
- Embedded UI documentation — canonical reference (get started, patterns, components)
- Build embedded accounting in 20 minutes (API) — prove the ledger loop with
curlfirst - Authentication guide — token exchange and request signing
- API reference — schemas for every endpoint the components call
- Embedded accounting product overview — platform fit, MCP, evaluation checklist
- Evaluate an embedded accounting vendor — run the same flows against any vendor
Questions about embedding in production? Contact us — we work with design partners on vertical SaaS, neobanks, and marketplaces shipping accounting inside their own UX.
Engineering notes from the team building Paprel's ledger core and API — ledger architecture, multi-tenancy, idempotency, and the patterns behind audit-grade accounting. Reflects how the system actually works and is updated as the product evolves.
Build in sandbox, launch with a production trial
Use sandbox for developer testing with no billing. When you are ready for real workflows, start production on a monthly plan with a 14-day free trial.