How to Embed Paprel Accounting UI in Your App

Production Web Components for accounting and reports — your auth, your chrome, and your navigation.

10 min read
Paprel Engineering Team
Developers integrating a product UI into a web application

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

ApproachBest for
REST / SDK onlyFully custom UX, server-side automation, batch jobs, agents
Embed Web ComponentsProduction operator accounting inside your app shell — accounts, journals, banking, reconciliation, and reports
MCPAI 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:

Text
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

Shell
npm install @paprel/embed-core @paprel/embed-accounting @paprel/embed-reports

Install the shared foundation and the domain packages you need:

PackageRole
@paprel/embed-coreHTTP client, token lifecycle, typed resources, i18n helpers
@paprel/embed-uiSemantic theme contract, included by domain packages
@paprel/embed-accountingTyped resources and Web Components for operational accounting
@paprel/embed-reportsTrial 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:

TypeScript
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

HTML
<!-- 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)

ElementPurposeNotable attributes / events
paprel-chart-of-accountsRead-only COA flat gridtree attribute; refresh() method
paprel-account-selectGrouped account <select>value, label; fires account-change
paprel-journal-listPaginated journal tablepage; fires journal-select with { journalId }
paprel-journal-detailHeader metadata + line gridjournal-id
paprel-journal-editorCreate / edit manual journaljournal-id, currency; fires journal-saved

Typical master–detail wiring:

TypeScript
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:

Text
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:

Text
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:

CSS
.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;
}
HTML
<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.

Shell
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:

TypeScript
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


Questions about embedding in production? Contact us — we work with design partners on vertical SaaS, neobanks, and marketplaces shipping accounting inside their own UX.

Posted by: Paprel Engineering Team · Engineering
Posted on: (Updated: August 25, 2026)

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.

Evaluate Paprel

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.

API-First Delivery
Audit-Ready Controls
Sandbox And Guided Rollout