CRM School

Welcome to CRM School: structured lessons for Biz1 CRM, documents, and the App SDK.

1. Document PDF templates

Create your own HTML/CSS designs for document PDFs. When no custom template is selected (or set as default), Biz1 keeps using the existing PDF flow.

Where to manage templates

Open: Settings → Document PDF templates (/setting/document_pdf_templates).

From this page you can:

  • Add a template
  • Edit a template
  • Test with mock data
  • View the template list
  • Set a default template
  • Remove / archive a template
  • Filter by document type

Supported document types

order_proposals
purchase_orders
invoice
receipt
receipt_tax_invoice
credit_invoice
delivery_invoice
proforma_invoice
detail_orders
gi_ir
organization_receipt

How a template is chosen when creating a document

Optional request fields:

FieldMeaning
pdf_template_idUse this template (must belong to the user and match the document type)
pdf_template_strictIf 1 and the template is invalid / render fails → return an error instead of falling back

Behavior:

  1. pdf_template_id empty → use the active default custom template (if one exists)
  2. No default custom template → legacy / private PDF flow
  3. Valid pdf_template_id → use that template
  4. Invalid id + pdf_template_strict = 1 → error
  5. Invalid id + strict not 1 → fall back to legacy PDF

Each saved document stores pdf_template_id and pdf_template_version_id so the PDF keeps the exact version used at create time.

Required placeholders (must exist before save / test)

Required partAccepted placeholders
Company name{{company.company_name}}, {{company.name}}, {{owner.company_name}}
Company ID{{company.company_tax_id}}, {{company.company_id}}, {{owner.company_tax_number}}
Company address{{company.company_address}}, {{company.address}}, {{owner.address}}
Company phone{{company.company_phone}}, {{company.phone}}, {{owner.phone}}, {{owner.mobile}}
Customer name{{customer.name}}
Document date{{document.date_created}}, {{document.date}}
Document ID{{document.last_documents_id}}, {{document.id}}
Biz1 logo{{crm_logo}}
Biz1 credit text{{crm_credit}}

Recommended credit block:

<a href="https://biz1.co.il">
  <img src="{{crm_logo}}" width="48" />
  <span>{{crm_credit}}</span>
</a>

Unsafe HTML/CSS is blocked. Missing required parts return a detailed validation error.

Main placeholders

Document

{{document.id}}
{{document.last_documents_id}}
{{document.date_created}}
{{document.date}}
{{document.due_date}}
{{document.type}}
{{document.lang}}
{{document.direction}}
{{document.coin}}
{{document.total}}
{{document.tax}}
{{document.discount}}
{{document.final_amount}}
{{document.note}}
{{document.note_html}}
{{document.payment_link}}
{{document.invoice_israel_code}}

Customer

{{customer.name}}
{{customer.email}}
{{customer.mobile}}
{{customer.phone}}
{{customer.address}}
{{customer.company}}
{{customer.corporation}}
{{customer.cf_name}}

Company / invoice settings

{{company.company_name}}
{{company.company_tax_id}}
{{company.company_address}}
{{company.company_phone}}
{{company.email}}
{{company.logo}}
{{company.company_letter}}
{{company.order_letter}}
{{company.receipt_letter}}
{{company.invoice_receipt_letter}}
{{company.purchase_order_letter}}
{{company.details_order_letter}}

Owner / user

{{owner.name}}
{{owner.email}}
{{owner.phone}}
{{owner.mobile}}
{{owner.website}}
{{owner.logo}}
{{owner.invoice_logo}}
{{owner.address}}
{{owner.company_name}}
{{owner.company_tax_number}}

Biz1

{{crm_logo}}
{{crm_credit}}

{{crm_credit}} is language-aware.

Payment / approval link

{{payment_link}}
{{document.payment_link}}
{{payment_title}}
{{payment_text}}
{{payment_link_label}}

When available:

  • order_proposals — while the proposal is not approved / signed yet
  • invoice, detail_orders, purchase_orders — when unpaid and the payment button is enabled
  • Otherwise — empty string

Example:

{{#if payment_link}}
<section class="payment-cta">
  <strong>{{payment_title}}</strong>
  <span>{{payment_text}}</span>
  <a href="{{payment_link}}">{{payment_link_label}}</a>
</section>
{{/if}}

{{payment_title}}, {{payment_text}}, and {{payment_link_label}} are language-aware.

Repeated header, footer, and page numbers

Default:

{{header}}
{{footer}}

Custom:

{{#header}}
<div style="font-family:Arial;font-size:9px;width:100%">
  {{company.company_name}} - {{document.last_documents_id}}
  <span style="float:right">{{page}} / {{pages}}</span>
</div>
{{/header}}

{{#footer}}
<div style="font-family:Arial;font-size:8px;width:100%">
  <a href="https://biz1.co.il">{{crm_credit}}</a>
  <span style="float:right">{{page}} / {{pages}}</span>
</div>
{{/footer}}

Page tokens (use only inside header/footer):

{{page}}
{{pages}}
{{total_pages}}

Header/footer markers are removed from the body and repeated on each page. Margins increase when header/footer are used.

RTL and language

Hebrew and other RTL documents get dir="rtl" / lang="he". English and Thai use LTR.

.document-pdf-template-root[dir=rtl] {
  direction: rtl;
  text-align: right;
}

.document-pdf-template-root[dir=ltr] {
  direction: ltr;
  text-align: left;
}

Notes and rich text

Escaped notes:

{{document.note}}

Safe rich HTML (triple braces):

{{{document.note_html}}}
{{{company.header_notes_order_proposal_html}}}
{{{company.order_letter_html}}}
{{{company.purchase_order_letter_html}}}
{{{company.details_order_letter_html}}}

Header notes (by document type), bottom letters, and multi-footer JSON fields are also available from company invoice settings — use the matching {{company.header_notes_*}}, {{company.*_letter}}, and {{company.multi_footer_notes_for_*}} placeholders.

Items, products, categories, and options

Loop all lines:

{{#items}}
<tr>
  <td>{{item.name}}</td>
  <td>{{item.qty}}</td>
  <td>{{format_money(item.price, document.coin)}}</td>
  <td>{{format_money(item.qty * item.price, document.coin)}}</td>
</tr>
{{/items}}

Item fields: name, qty, price, discount, description, notes, sku, discount_type, vat_include, line_total, options_text.

Also available per line: {{product.*}}, {{category.*}}.

Filter by category id:

{{#items_by_category 123}}
<tr>
  <td>{{item.name}}</td>
  <td>{{category.id}}</td>
  <td>{{product.color}}</td>
</tr>
{{/items_by_category}}

Generic filter:

{{#items where category.id == 456}}
<tr>
  <td>{{item.name}}</td>
  <td>{{format_money(item.line_total, document.coin)}}</td>
</tr>
{{/items}}

Operators: == != > >= < <=

If blocks, math, and money

{{#if item.discount > 0}}
  <td>{{item.discount}}</td>
{{else}}
  <td>-</td>
{{/if}}

Rules:

  • Single-level {{#if}} only (do not nest if blocks)
  • Math inside one expression: {{item.qty * item.price}}
  • Money: {{format_money(item.line_total, document.coin)}}
  • Do not write {{item.qty}}*{{item.price}}

Custom fields

Normalized:

{{customer.cf_name}}
{{product.cf_color}}
{{category.cf_floor_type}}

Exact helpers:

{{customer_cf("cf-name")}}
{{product_cf("color")}}
{{category_cf("floor-type")}}

Images and CSS

<img src="{{crm_logo}}" width="48" />
<img src="{{product.image}}" width="80" />
<img src="{{category.image}}" width="80" />

Images must come from trusted Biz1 hosts / upload paths.

Playwright renders modern CSS (grid, @page, etc.). Prefer print-friendly rules such as break-inside: avoid on line rows.

Blocked for safety: <script>, <iframe>, <object>, <embed>, <form>, JS event attributes, javascript: URLs, PHP tags, unknown external image hosts, external CSS imports, remote fonts that may fail to load.

Invoice Israel

{{document.invoice_israel_code}}
{{company.invoice_israel}}
{{company.force_tz}}
{{company.israel_default_data}}

Signatures (current limit)

There is no template placeholder for digital signatures / approval signature images. Custom HTML PDFs do not currently use the legacy TCPDF certificate-signing step.

Quick troubleshooting

ProblemCheck
Template not usedOwnership, matching document_type, status active, default set, or pdf_template_id passed
Missing fields errorAll required placeholders including {{crm_logo}} and {{crm_credit}}
Payment link emptyProposal not approved; invoice unpaid; payment button enabled
Header/footer not repeatingUse {{header}} / {{footer}} or block syntax; page tokens only inside them
Hebrew not RTLdocument_lang=he + RTL CSS on .document-pdf-template-root

Full technical reference: document-pdf-template-module-help.md


2. App SDK and realtime options

Build web, React, Vue, Flutter WebView, or Android WebView screens with the Biz1 JavaScript SDK. Define the domain once, login once, then reuse the same client for REST and realtime.

Live API help (every route): https://bull36.com/app/help

Setup

  1. Include https://{domain}/app/sdk/biz1-sdk.js
  2. Create one Biz1SDK.Biz1Client at startup with domain and a storage object
  3. Call client.login() — the SDK stores the bearer token in biz1_sdk_bearer_token
  4. After login, call client.account.basic() and cache user, organization, folders, statuses, team members, and field settings
  5. Call routes with helpers (client.customers.list()), dynamic helpers (client.routes.Customer.List()), or client.request()
  6. Keep list page size at 25 or less (length / limit / per_page)
  7. Pass JavaScript Date objects or local datetime strings — the SDK posts UTC Y-m-d H:i:s
  8. Store ids from list/add responses and reuse them for get / update / remove / related calls
  9. On 401, the SDK clears the token — show login again

Include SDK

<script src="https://{domain}/app/sdk/biz1-sdk.js"></script>

Basic flow

const client = new Biz1SDK.Biz1Client({
  domain: 'https://{user}.bull36.com',
  storage: localStorage
});

await client.login({
  username: 'USER EMAIL',
  password: 'USER PASSWORD'
});

const user = await client.account.basic();
const customers = await client.customers.list({ folder_id: 1, length: 25 });
const total = await client.customers.count({ folder_id: 1 });
const single = await client.customers.get(
  customers.rows[0].customer_id || customers.rows[0].id
);

Any route

const result = await client.request('Customer.List', {
  folder_id: 1,
  length: 25
});

const sameResult = await client.routes.Customer.List({
  folder_id: 1,
  length: 25
});

Date / time fields

await client.customers.add({
  name: 'John Demo',
  phone: '0500000000',
  followup: new Date(2026, 6, 20, 10, 0, 0)
});

// Explicit UTC conversion helper:
const utcValue = Biz1SDK.toUtcDateTime(new Date(2026, 6, 20, 10, 0, 0));

How to call without the SDK helpers

POST /app/{Route.Name}
POST https://{user}.bull36.com/app/{Route.Name}
Authorization: Bearer YOUR TOKEN

Instructions:

  1. Login via /app/Login (handle OTP if required)
  2. Save the bearer token in secure app state
  3. Send Authorization: Bearer … on every protected call
  4. Send parameters as POST body fields
  5. Lists: max 25 rows; use the matching .Count route for totals
  6. On create, store returned ids (customer_id, mission_id, …) for later actions
  7. On 401, clear token and return to login

Realtime (socket)

Use the socket after login to refresh open screens without polling. The same bearer token registers the socket device.

<script src="https://{domain}/realtime/socket.io/socket.io.js"></script>
<script src="https://{domain}/app/sdk/biz1-sdk.js"></script>
const socket = client.realtime.connect({
  platform: 'web',
  path: '/realtime/socket.io'
});

client.realtime.on('biz1:ready', function (payload) {
  console.log('socket ready', payload.userId, payload.events);
});

client.realtime.on('*', function (event) {
  console.log('realtime event', event.key, event.payload);
});

Prefer specific listeners for performance:

client.realtime.on('crm.lead.created', function (event) {
  refreshCustomerList();
});

client.realtime.on('mission.created', function (event) {
  refreshMissionList({ customer_id: event.payload.customer_id });
});

client.realtime.on('message.created', function (event) {
  refreshCustomerMessages(event.payload.customer_id);
});

client.realtime.on('customer.followup', function (event) {
  refreshCustomer(event.payload.customer_id);
});

Main event groups

  • Customers / follow-up: crm.lead.created, customer.updated, customer.followup, customer.restored
  • Messages / email: chat.message.received, message.created, mission.message.created, project.message.created, message.mark_read, message.replied, message.forwarded, message.deleted, email.created, email.deleted
  • Missions / reminders: mission.created, mission.updated, mission.done, mission.reopened, mission.deleted, mission.reminder, mission.open_popup, automation.reminder.popup
  • Meetings / appointments: meeting.created, meeting.updated, meeting.deleted, automation.appointment.open_popup, appointment.created, appointment.updated, appointment.deleted
  • Calls: call.incoming, call.status.popup
  • Tabs / statuses: entries.created, entries.updated, entries.deleted, statuses.add.created, statuses.edit.updated, statuses.delete.deleted
  • Internal status: use statuses.* when payload.data.type is internal_status

When a route response includes socket_event, use the same key in client.realtime.on(socket_event, handler).

Example realtime payloads

New customer

{
  "key": "crm.lead.created",
  "payload": {
    "customer_id": 13745935,
    "customer": {
      "id": 13745935,
      "name": "John Demo",
      "email": "[email protected]",
      "mobile": "0500000000"
    }
  }
}

Mission

{
  "key": "mission.created",
  "payload": {
    "mission_id": 500,
    "customer_id": 13745935
  }
}

Building UIs with AI / prompts

Point builders at https://{domain}/app/help. Remind them that every date/time field must be sent as UTC Y-m-d H:i:s (the SDK converts local Date values automatically). Follow each route’s required/optional parameters and sample JSON from the help panel.

← Back to Knowledge Base

WhatsApp