Status: Implemented (structure kit in scripts/espocrm/) · Date: 2026-08-07 · Applies to: EspoCRM v10 (live at crm.supplyshoreltd.com)
This document has three parts:
EspoCRM is a metadata-driven, self-hosted open-source CRM (PHP 8.3+/Slim 4, MariaDB/MySQL/PostgreSQL, SPA frontend). Everything — entities, fields, relationships, layouts, ACL — is defined as JSON metadata, which makes it an application platform, not a fixed-feature product. License: AGPLv3 (free for internal use; obligations apply only if modified/distributed). Lightweight (~300–400 MB) — the reason it was chosen over Twenty and ERPNext (see 06-decisions/adr.md ADR-002, 03-product/crm.md).
| Area | Features |
|---|---|
| Sales core | Accounts, Contacts, Leads (web-to-lead forms), Opportunities (pipeline + Kanban), Cases, Activities |
| Customization | Entity Manager (custom entity types, fields, relationships), Layout Manager (detail/list/kanban views), Dynamic Logic (show/require/lock fields by conditions), Formula (server-side calculated fields & actions), Streams (record activity feeds), Themes |
| Field types | varchar, text (markdown), enum, multi-enum, checklist, array, int, float, decimal, currency (+currency code, conversion), bool, date, datetime, address, url, url-multiple, wysiwyg, file, image, attachment-multiple, number (auto-incrementing with prefix, e.g. RFQ-000001), auto-increment, barcode, foreign; link types: link (belongs-to), link-multiple, link-parent |
| Relationship types | one-to-many, many-to-one, many-to-many (with extra columns), one-to-one (left/right), children-to-parent (polymorphic); same-entity links allowed; parameters: link-multiple field, audited, read-only, cascade removal & transactional save (v10) |
| Communications | Email accounts (IMAP sync, auto-linking to records), outbound SMTP, email templates, mass email/campaigns, calendar (meetings/calls/tasks, working-time calendars), VoIP (extension) |
| Access control | Users, Teams, Roles (entity-level + field-level ACL: read/write per field), record-level assignment, portal users |
| Data | Import (CSV/XLSX with mapping & dedupe), Export (CSV/Excel), Print-to-PDF templates, dashboards, global search + full-text search, audit log, GDPR data-privacy tooling |
| Portal | Branded customer self-service portal (cases, knowledge base, account data) |
| API | Full REST API for every entity (CRUD, relationships, search, mass actions), auth via session, API keys (plain/HMAC), OAuth2; webhooks; every custom entity is immediately API-accessible |
| Admin | Users/roles/teams, authentication (2FA, SSO via extension), email admin, currency rates, system settings, extensions installer, backup tooling guidance, Rebuild / Clear Cache |
| Extension | Price (2026, approx.) | What it adds |
|---|---|---|
| Advanced Pack | ~$395 | Reports (list/grid/summary, charts, dashboards), Workflows (rule-based automation), BPM (visual process designer), CSV/Excel export add-ons |
| Sales Pack | ~$260/yr | Products, price lists, quotes, sales orders, invoices, purchases, inventory, payments, subscriptions |
| Project Management | ~$230/yr | Projects, milestones, tasks, Gantt |
| VoIP / Google / Outlook / MailChimp integrations | $190–390 | telephony, calendar/mail sync, campaigns |
Strategy (per
03-product/crm.md§4.3): Advanced Pack + Sales Pack (~$655) is the single highest-leverage paid investment when we need BPM/Reports and native quote/order flows. Our custom structure (below) is deliberately built so those packs remain optional: we use custom entities for the procurement flow (Supplier, RFQ, Quote, Order…), so Sales Pack is not required for v1. Caveat: if Sales Pack is installed later, its built-inProduct,CategoryandQuoteentities would overlap with our custom ones — see ADR-014 for the decision and the mitigation.
custom/Espo/Custom/Resources/metadata/{entityDefs,scopes,clientDefs,selectDefs,recordDefs,logicDefs}/… + layout files under custom/Espo/Custom/Resources/layouts/{Entity}/… + i18n files, then triggers a rebuild (creates DB tables).Espo\Tools\EntityManager\EntityManager (create/update/delete), Espo\Tools\FieldManager\FieldManager (create/update), Espo\Tools\LinkManager\LinkManager (create/update/delete) — the exact code path the UI uses. Our installer (scripts/espocrm/apply_structure.php) drives these services directly, so the result is byte-identical to doing it by hand in the UI, but scripted, versioned and idempotent.C and custom fields/links on core entities with c (e.g. field legalName on Account → cLegalName) to avoid collisions with future core features. This can be disabled with the config flag customPrefixDisabled. We disable it (clean API names like /api/v1/Supplier) — see ADR-014.Base, BasePlus (adds Activities/History/Tasks panels), Company, Person, Event, CategoryTree (hierarchical, with path table; creatable only via forceCreate, which the installer passes).| # | Requested group | EspoCRM implementation | Type |
|---|---|---|---|
| 1 | Organizations (master) | Account — master table for every company; type field: Factory / Supplier / Buyer / Logistics Provider / QC & Verification Provider / Partner / Other |
core + custom fields |
| 2 | Contacts (people) | Contact — linked to Account (organization) | core |
| 3 | Suppliers | Supplier — profile + verification + scoring (links to master Account) | custom (BasePlus) |
| 4 | Factories | Factory — production facilities, capabilities, capacity, certifications, audit state (links to Supplier + Account) | custom (BasePlus) |
| 5 | Buyers / Customers | Customer — buyer profile, sourcing preferences (links to master Account) | custom (BasePlus) |
| 6 | Products / Categories | Product + Category (tree: 4 industry verticals → sub-categories; PLCs, Sensors, Circuit Breakers, Solar Inverters…) | custom (Base / CategoryTree) |
| 7 | Supplier Leads | Lead (core) — source options: Alibaba, 1688, Trade Show, LinkedIn, Website…; convertible to Account/Contact/Supplier | core + custom fields |
| 8 | RFQs | RFQ header + RFQItem lines; links: buyer, products, suppliers contacted, quotes received | custom (BasePlus / Base) |
| 9 | Quotes / Offers | Quote header + QuoteItem lines; price, MOQ, lead time, incoterms, validity | custom (BasePlus / Base) |
| 10 | Orders / Projects | Order header + OrderItem lines; PO number, supplier, buyer, factory, shipment status, ports, tracking | custom (BasePlus / Base) |
| 11 | Logistics Providers | LogisticsProvider — freight forwarders, shipping lines, customs brokers, warehouses | custom (BasePlus) |
| 12 | Quality & Verification Providers | QCProvider — inspection companies, labs, certification bodies, auditors | custom (BasePlus) |
| 13 | Partners | Partner — local agents, consultants, technology partners | custom (BasePlus) |
| 14 | Documents | Document (core) + custom docType + many-to-many links to every business entity |
core + custom fields |
| 15 | Activities / Communications | Core Emails / Calls / Meetings / Tasks / Notes + record Stream (enabled on all business entities) | core |
| 16 | Locations | Location — countries, cities, industrial zones, ports (UN/LOCODE), factory sites; linked to Accounts, Factories, Orders (ports), Logistics Providers | custom (Base) |
03-product/crm.md §4.4 and 03-product/supplier-intelligence.md.)RFQ-000001, QT-000001, PO-000001) via the built-in number field type.Legend — type: field type · req required · def default · SF = status field (Kanban).
Custom fields added: legalName (varchar), registrationNumber (varchar), yearEstablished (int), companyStatus (enum: Active/Inactive/Under Review/Blacklisted, def Active), source (enum: Alibaba/1688/Made-in-China/Global Sources/IndiaMart/Trade Show/LinkedIn/Website/Referral/Customs Data/Directory/Other, def Other), socialProfileUrl (url).
type options replaced with: Factory, Supplier, Buyer / Customer, Logistics Provider, Quality & Verification Provider, Partner, Other (def Other).
Core fields already used as-is: name, website, emailAddress, phoneNumber, billingAddress (country/city/street), shippingAddress, industry, employeesNumber, annualRevenue, description, assignedUser, teams.
Links (panels on Account): Suppliers, Customers, Factories, Logistics Providers, QC & Verification Providers, Partners, RFQs (as Buyer), Quotes (as Buyer), Orders (as Buyer), Locations (M2M).
People inside organizations: name, title (Sales Manager, Factory Owner, Procurement Manager, Logistics Coordinator…), email, phone, account (M2O), address. Used for both buyer-side and supplier-side contacts.
status, Kanban)| Field | Type | Notes |
|---|---|---|
| name | varchar | req — display name (company name) |
| account | link (Account) | the master organization record |
| status | enum | Discovered / Researched / Desk Verified / Audited / Active / Inactive / Blacklisted (def Discovered) |
| supplierType | enum req | Manufacturer / Trading Company / Distributor |
| legalName, registrationNumber | varchar | legal identity |
| website | url | |
| country, city | varchar | |
| yearEstablished, employeesNumber | int | |
| verificationScore | int | 0–100 (see supplier-intelligence.md §5.7) |
| verificationStatus | enum | Not Verified / Desk Verified / Audited / Verified / Expired |
| lastVerifiedAt, verificationExpiry | date | re-verification cadence |
| verificationMethod | text | desk check, 3rd-party audit, customs data… |
| certifications | multi-enum | ISO 9001, ISO 14001, ISO 45001, CE, UL, RoHS, REACH, CCC, TÜV, SGS, Other |
| exportMarkets | multi-enum | Europe, North America, MEA, Central Asia, SEA, South Asia, East Asia, Oceania, South America, Global |
| moq | varchar | e.g. "500 pcs" |
| paymentTerms | enum | T/T 30% Advance, T/T 50/50, T/T 100% Advance, L/C at Sight, L/C 30-90 Days, O/A, Escrow, Other |
| incotermsOffered | multi-enum | EXW, FOB, CIF, CFR, DAP, DDP, FAS, FCA, CPT, CIP, Other |
| leadTimeDays | int | |
| rating | int | 1–5 |
| notes | text |
Links: Factories (1:N), Products (M2M), Categories (M2M), RFQs (M2M, "suppliers contacted"), Quotes (1:N), Orders (1:N), Documents (M2M), Account (M2O).
status)| Field | Type | Notes |
|---|---|---|
| name | varchar req | e.g. "Ningbo Plant 2" |
| supplier | link (Supplier) | owning supplier profile |
| account | link (Account) | owning organization |
| status | enum | Planned / Active / Inactive / Closed (def Active) |
| ownership | enum | Owned / Rented / Unknown |
| facilityType | multi-enum | Manufacturing, Assembly, Warehouse, R&D, Showroom, Office |
| employeesNumber, yearEstablished | int | |
| productionCapacity | varchar | e.g. "50,000 units/month" |
| monthlyOutputUnits | int | |
| capabilities, equipment | text | |
| certifications | multi-enum | same catalog as Supplier |
| auditStatus | enum | Not Audited / Audit Scheduled / Audited / Audit Failed / Re-audit Required (def Not Audited) |
| lastAuditDate | date | |
| auditReportUrl | url | |
| isVerified | bool | |
| country, city, industrialZone, streetAddress, postalCode | varchar/text | |
| location | link (Location) | linked geographic record |
Links: Supplier (M2O), Account (M2O), Location (M2O), Categories (M2M), QC Providers (M2M — who audited), Orders (1:N), Documents (M2M).
status, Kanban)name, account (link Account), status (Prospect/Active/Inactive/Former, def Prospect), customerType (multi-enum: Importer, Wholesaler, Industrial Company, Contractor, EPC Integrator, OEM, Retailer, Other), sourcingBudget (currency), preferredIncoterms (multi-enum), targetRegions (multi-enum), contractType (enum: Project-based/Retainer/Subscription/One-off), referralSource, annualVolume (varchar), sourcingFocus (text), notes. Links: Account, Categories (M2M — what they buy), Documents.
name (req), parent/children (tree), code (varchar), categoryType (enum: Product Category / Industry Vertical / Market Segment, def Product Category), order, description. Seeded with the 4 verticals + example sub-categories (see §7.4). Links: Products, Suppliers, RFQs, Leads, Factories, Customers (all M2M).
name (req), category (link Category), sku, manufacturer, model, unit (enum: Piece/Set/Pair/Kilogram/Meter/Roll/Carton/Liter/Square Meter/Other), listPrice (currency), moq (int), leadTimeDays (int), certifications (multi-enum), specification (text), status (enum: Active/Discontinued/Draft), image. Links: Category, Suppliers (M2M), RFQItems/QuoteItems/OrderItems (1:N), Documents.
status, Kanban)| Field | Type | Notes |
|---|---|---|
| name | varchar req | e.g. "RFQ — 500x VFD 15kW, China→Tunis" |
| number | number | auto RFQ-000001 |
| buyer | link (Account) req | the buyer organization |
| status | enum | Draft / Open / In Progress / Quotes Received / Shortlisted / Awarded / Closed / Cancelled (def Draft) |
| requestType | enum | Standard RFQ / Custom Specification / Sample Request / Reorder |
| targetBudget | currency | |
| incotermsRequested | enum | |
| deliveryTargetDate | date | |
| destinationCountry, destinationPort | varchar | |
| validityDays | int | |
| specification, internalNotes | text |
Links: Buyer (Account), Suppliers contacted (M2M), Categories (M2M), Items (1:N RFQItem), Quotes received (1:N), Orders (1:N), Documents.
name (req), rfq (link RFQ req), product (link Product), quantity (int, def 1), unit (enum), specification (text), targetPrice (currency), requiredCertifications (multi-enum), notes.
status, Kanban)| Field | Type | Notes |
|---|---|---|
| name | varchar req | |
| quoteNumber | number | auto QT-000001 |
| rfq | link (RFQ) | originating RFQ |
| supplier | link (Supplier) | who quoted |
| buyer | link (Account) | buyer organization |
| status | enum | Draft / Submitted / Under Review / Shortlisted / Accepted / Declined / Expired / Withdrawn (def Draft) |
| totalAmount | currency | |
| moq | varchar | |
| leadTimeDays | int | |
| incoterms | enum | |
| paymentTerms | enum | |
| validityUntil | date | |
| deliveryPort | varchar | |
| notes | text |
Links: RFQ (M2O), Supplier (M2O), Buyer (Account), Items (1:N QuoteItem), Orders (1:N — the accepted quote's order), Documents.
name, quote (link req), product (link), quantity (int def 1), unitPrice (currency), moq (int), leadTimeDays (int), incoterms (enum), notes.
status, Kanban)| Field | Type | Notes |
|---|---|---|
| name | varchar req | |
| orderNumber | number | auto PO-000001 |
| orderType | enum | Purchase Order / Sourcing Project / Sample Order / Trial Order (def Purchase Order) |
| buyer | link (Account) | buyer organization |
| supplier | link (Supplier) | |
| factory | link (Factory) | producing facility |
| rfq / quote | link (RFQ / Quote) | traceability |
| status | enum | Draft / Confirmed / In Production / Ready for Shipment / Shipped / In Transit / Delivered / Completed / Cancelled / On Hold (def Draft) |
| paymentStatus | enum | Unpaid / Deposit Paid / Partially Paid / Paid / Overdue / Refunded (def Unpaid) |
| paymentTerms, incoterms | enum | |
| totalAmount | currency | |
| orderDate | date | |
| expectedShipDate / actualShipDate | date | |
| expectedDeliveryDate / actualDeliveryDate | date | |
| shipmentStatus | enum | Not Shipped / Partially Shipped / Shipped / In Transit / Customs Clearance / Delivered (def Not Shipped) |
| trackingNumber | varchar | |
| logisticsProvider | link (LogisticsProvider) | |
| portOfLoading / portOfDischarge | link (Location) | |
| notes | text |
Links: Buyer, Supplier, Factory, RFQ, Quote, Items (1:N OrderItem), Logistics Provider, Ports (Location), QC Providers (M2M — inspections), Documents.
name, order (link req), product (link), quantity (int def 1), unitPrice (currency), deliveryDate (date), notes.
status)name, account (link Account), status (Prospective/Active/Preferred/Suspended/Inactive), providerType (multi-enum: Freight Forwarder, Shipping Company, Customs Broker, Warehouse, Last-Mile Courier, Other), services (multi-enum: Ocean FCL/LCL, Air Freight, Rail, Road Freight, Customs Clearance, Warehousing, Insurance, Consolidation, Other), tradeLanes (text), incotermsHandled (multi-enum), rating (int 1–5), notes. Links: Account, Orders (1:N), Locations (M2M — offices/warehouses), Documents.
status)name, account (link Account), status (Prospective/Active/Preferred/Suspended/Inactive), providerType (multi-enum: Inspection Company, Testing Lab, Certification Body, Auditor, Calibration Service, Other), accreditations (multi-enum: ISO/IEC 17020, ISO/IEC 17025, CNAS, ILAC, A2LA, Other), services (multi-enum: Pre-Shipment Inspection, During Production Inspection, Container Loading Supervision, Factory Audit, Lab Testing, Certification, Sample Testing, Other), serviceRegions (multi-enum: China, SEA, South Asia, Europe, North America, Global, Other), rating, notes. Links: Account, Factories (M2M — audited facilities), Orders (M2M — inspections), Documents.
status)name, account (link Account), status (Prospective/Active/Preferred/Suspended/Inactive), partnerType (multi-enum: Local Agent, Consultant, Technology Partner, Industry Association, Government Liaison, Media, Other), territory (varchar), agreementStart/agreementEnd (date), commissionRate (float %), notes. Links: Account, Documents.
name (req), locationType (enum: Country / City / Industrial Zone / Port / Free Trade Zone / Factory Site / Warehouse / Office / Other, def City), country, city, industrialZone, portCode (varchar — UN/LOCODE like CNSHA), address (text), postalCode, latitude, longitude (float), timezone, notes. Links: Accounts (M2M), Factories (1:N), Orders as loading/discharge port (1:N), Logistics Providers (M2M).
Core fields (name, file, folder, status, publish/expiration date, description) + custom docType (enum: Certificate, Contract, Quotation, Invoice, Inspection Report, Audit Report, Business License, Product Specification, Catalogue, Payment, Other). M2M links with a Documents link-multiple field on: Supplier, Factory, Customer, Product, RFQ, Quote, Order, LogisticsProvider, QCProvider, Partner.
source options: Alibaba, 1688, Made-in-China, Global Sources, IndiaMart, Trade Show, LinkedIn, Website, Referral, Customs Data, Directory, Other.status: New, Contacted, Working, Qualified, Unqualified, Converted.Full link catalog (48 relationships) is machine-readable in scripts/espocrm/structure.json → links.
Defined once in structure.json → optionSets and reused across fields (keeps vocabularies consistent):
scripts/espocrm/
├── structure.json # machine-readable spec: entities, fields, links, options (source of truth)
├── apply_structure.php # installer — runs INSIDE the espocrm container (php CLI)
├── apply.sh # copies + runs the installer (run on the node)
├── verify_structure.sh # REST smoke-test of all 19 entities (run anywhere with CRM access)
├── seed_categories.php # seeds the Category tree (4 verticals + examples)
└── seed_categories.sh # wrapper for the seed
# from your workstation: push the scripts to the node
scp -r ~/supplyshore-stack/scripts/espocrm root@139.162.176.234:/opt/supplyshore/scripts-espocrm/
# on the node
cd /opt/supplyshore/scripts-espocrm
./apply.sh # container "espocrm" (set ESPOCRM_CONTAINER if different)
What happens: config flag customPrefixDisabled=true is set, 15 custom entities are created (Suppliers, Factories, Customers, Categories, Products, RFQs + items, Quotes + items, Orders + items, Logistics Providers, QC & Verification Providers, Partners, Locations) with ~170 custom fields, 48 relationships (incl. 10 Document M2M links and a Documents field on every business entity), Account/Lead/Document are customized, Kanban + auto-numbers are configured, the Supplier lead-conversion target is enabled, and fields/relationship panels are placed on Detail/List layouts. Idempotent — re-running is safe.
ESPOCRM_URL=https://crm.supplyshoreltd.com \
ESPOCRM_USER=admin \
ESPOCRM_PASSWORD='<password>' \
./verify_structure.sh
# → RESULT: PASS — all 19 entities present
./seed_categories.sh
# creates: Electrical Components / Industrial Automation / Energy Systems / Smart Factories
# + Circuit Breakers, Contactors, Relays, Connectors, Protection Devices, Power Distribution,
# Surge Protection, PLCs, Sensors, HMIs, VFDs, Servo & Motion Control, IO Modules, Actuators,
# Solar Inverters, BMS, Energy Storage, Mounting Systems, Monitoring & Control,
# IoT Gateways, SCADA, MES, Industrial Robotics, Edge Computing
Login to EspoCRM → Administration → Entity Manager (entities/fields/relationships), Layout Manager (drag fields if any layout polish is desired), Lead Conversion (Supplier target now available). All new tabs appear in the top navigation automatically.
# Remove one entity via the UI: Entity Manager → {Entity} → Remove (cascades its fields/links).
# Or via the same tooling: a small PHP snippet calling
# Espo\Tools\EntityManager\EntityManager::delete('Supplier')
# after dropping its data. Removal does NOT delete DB data automatically — empty the
# tables (Administration → Rebuild won't drop them; drop via the DB) before removing,
# or keep data and disable the entity instead (Entity Manager → Disabled).
GET/POST/PATCH/DELETE /api/v1/{Entity} — e.g. https://crm.supplyshoreltd.com/api/v1/Supplier, /api/v1/RFQ, /api/v1/Order.POST /api/v1/Supplier + POST /api/v1/RFQ/{id}/suppliers (body {"ids":["<supplierId>"]}) to link related records. EspoCRM v10 routes are /{entity}/{id}/{link}; the legacy /{id}/relation with {"link":...} is gone (404).GET /api/v1/RFQ?select=number,name,buyerName&where[0][type]=equals&where[0][attribute]=status&where[0][value]=Open — list with filters. v10 uses an array of where items (where[0][type], where[0][attribute], where[0][value]); flat where[status]=Open throws Item::fromRaw() must be of type array.RFQ-…, QT-…, PO-… are generated server-side by the number field type — no action needed.RFQ.suppliers (M2M), Order.items, Supplier.quotes.Webhook entity can notify n8n on record events.internalNotes and verificationScore if needed.Quote.supplier → Order.supplier, hide verification* fields until status > Discovered, set Order.incoterms default from the quote.03-product/automation.md now has concrete API targets: W2 supplier discovery sync → /api/v1/Supplier, W4 RFQ parsing → /api/v1/RFQ + items, W5 enrichment → update verification* fields.Product/Category/Quote entities overlap with ours; decide then whether to keep ours (procurement-specific) or migrate (see ADR-014).Sources: espocrm.com/features · docs.espocrm.com (entity-manager, fields, custom-entity-type, link-multiple-with-primary) · EspoCRM v10 source (application/Espo/Tools/{EntityManager,FieldManager,LinkManager}, Core/Templates, Resources/metadata) — verified 2026-08-07.