strapi-plugin-hubspot 0.16.7 → 0.16.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/_chunks/{Forms-DyV6KyiO.mjs → Forms-CUNHDvhI.mjs} +52 -6
  3. package/dist/_chunks/{Forms-uUN4XjhW.js → Forms-CylJ0nD3.js} +52 -6
  4. package/dist/_chunks/{HubspotFormInput-BkWat_wu.js → HubspotFormInput-D2H2u9fZ.js} +1 -1
  5. package/dist/_chunks/{HubspotFormInput-DEXPX-6j.mjs → HubspotFormInput-Dppgr_rd.mjs} +1 -1
  6. package/dist/_chunks/{HubspotObjectInput-CFDYW-TM.js → HubspotObjectInput-BjRCaqXr.js} +3 -3
  7. package/dist/_chunks/{HubspotObjectInput-DL8fYK2x.mjs → HubspotObjectInput-CWFddNNw.mjs} +3 -3
  8. package/dist/_chunks/{HubspotPropertyInput-CFMkbQeT.js → HubspotPropertyInput-BlDarkcj.js} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-WU5FMJj3.mjs → HubspotPropertyInput-TGG9ozTa.mjs} +3 -3
  10. package/dist/_chunks/{Settings-B_hhdpHa.js → Settings-DOx7YSqA.js} +2 -2
  11. package/dist/_chunks/{Settings-CiXEps_C.mjs → Settings-tct49GYU.mjs} +2 -2
  12. package/dist/_chunks/{index-BeBn7bJa.mjs → index-Au3W85GX.mjs} +5 -5
  13. package/dist/_chunks/{index-CG_WQzSa.js → index-DhGVA3ZL.js} +5 -5
  14. package/dist/_chunks/{objectLabels-CS_0QCNz.mjs → objectLabels-3rnJ0WUc.mjs} +1 -1
  15. package/dist/_chunks/{objectLabels-BGq-Ypgn.js → objectLabels-Bx7x8fFO.js} +1 -1
  16. package/dist/_chunks/{useHubspotSchema-CP7LRdzO.mjs → useHubspotSchema-BlVXfwju.mjs} +1 -1
  17. package/dist/_chunks/{useHubspotSchema-BZP2qkAu.js → useHubspotSchema-DrGeidXC.js} +1 -1
  18. package/dist/admin/index.js +1 -1
  19. package/dist/admin/index.mjs +1 -1
  20. package/dist/admin/src/builder/types.d.ts +2 -1
  21. package/dist/server/index.js +243 -41
  22. package/dist/server/index.mjs +243 -41
  23. package/dist/server/src/conditions.d.ts +1 -0
  24. package/dist/server/src/formSync.d.ts +20 -0
  25. package/dist/server/src/formsAdmin.d.ts +1 -0
  26. package/dist/server/src/hsforms.d.ts +14 -1
  27. package/dist/server/src/importHubspot.d.ts +2 -1
  28. package/dist/server/src/index.d.ts +1 -0
  29. package/package.json +1 -1
@@ -1599,6 +1599,48 @@ function missingMappedFields(definition, existingNames) {
1599
1599
  const existing = new Set(existingNames);
1600
1600
  return mappedContactFields(definition).filter((field) => !existing.has(field.property));
1601
1601
  }
1602
+ const OBJECT_TYPE_BY_NAME = {
1603
+ contact: "0-1",
1604
+ company: "0-2"
1605
+ };
1606
+ function collectedHubspotKeys(definition) {
1607
+ const keys = /* @__PURE__ */ new Set();
1608
+ const add = (object, property) => {
1609
+ const typeId = OBJECT_TYPE_BY_NAME[object] || object;
1610
+ if (property) keys.add(`${typeId}:${property}`);
1611
+ };
1612
+ for (const step of definition.steps ?? []) {
1613
+ for (const fld of step.fields ?? []) {
1614
+ if (fld.type === "company") {
1615
+ const map = fld.companyMap ?? {};
1616
+ for (const slot of Object.values(map)) {
1617
+ if (slot?.property?.trim()) add(slot.object?.trim() || "company", slot.property.trim());
1618
+ }
1619
+ continue;
1620
+ }
1621
+ const mapping = fld.hubspot ?? {};
1622
+ const object = mapping.object?.trim() || "contact";
1623
+ const property = mapping.property?.trim() || fld.name;
1624
+ if (property) add(object, property);
1625
+ }
1626
+ }
1627
+ return keys;
1628
+ }
1629
+ function missingRequiredHubspotFields(definition, required) {
1630
+ const collected = collectedHubspotKeys(definition);
1631
+ const missing = [];
1632
+ for (const field of required) {
1633
+ if (!field.name) continue;
1634
+ const objectTypeId = field.objectTypeId?.trim() || "0-1";
1635
+ if (collected.has(`${objectTypeId}:${field.name}`)) continue;
1636
+ missing.push({
1637
+ name: field.name,
1638
+ objectTypeId,
1639
+ object: objectTypeId === "0-2" ? "company" : "contact"
1640
+ });
1641
+ }
1642
+ return missing;
1643
+ }
1602
1644
  function hsFieldType(type, options) {
1603
1645
  switch (type) {
1604
1646
  case "email":
@@ -1649,6 +1691,74 @@ function appendFieldGroups(existing, extra) {
1649
1691
  }))
1650
1692
  ];
1651
1693
  }
1694
+ function conditionToHubspot(condition, parentId) {
1695
+ const rules = (condition.rules ?? []).filter((rule) => rule.field === parentId);
1696
+ if (!rules.length || rules.length !== (condition.rules ?? []).length) return null;
1697
+ const op = rules[0]?.operator;
1698
+ if (rules.some((rule) => rule.operator !== op)) return null;
1699
+ const values = rules.map((rule) => rule.value).filter((value) => Boolean(value));
1700
+ switch (op) {
1701
+ case "eq":
1702
+ return { operator: values.length > 1 ? "SET_ANY" : "EQ", values };
1703
+ case "neq":
1704
+ return { operator: values.length > 1 ? "NOT_SET_ANY" : "NEQ", values };
1705
+ case "contains":
1706
+ return { operator: "CONTAINS", values };
1707
+ case "gt":
1708
+ return { operator: "GT", values };
1709
+ case "lt":
1710
+ return { operator: "LT", values };
1711
+ case "notEmpty":
1712
+ return { operator: "SET", values: [] };
1713
+ case "empty":
1714
+ return { operator: "NOT_SET", values: [] };
1715
+ default:
1716
+ return null;
1717
+ }
1718
+ }
1719
+ function applyVisibleIfToFieldGroups(groups, definition) {
1720
+ const byId = /* @__PURE__ */ new Map();
1721
+ for (const step of definition.steps ?? []) {
1722
+ for (const fld of step.fields ?? []) {
1723
+ const mapping = fld.hubspot ?? {};
1724
+ byId.set(fld.id, { name: fld.name, property: mapping.property?.trim() || fld.name });
1725
+ }
1726
+ }
1727
+ const moves = /* @__PURE__ */ new Map();
1728
+ for (const step of definition.steps ?? []) {
1729
+ for (const fld of step.fields ?? []) {
1730
+ const visibleIf = fld.visibleIf;
1731
+ const parentId = visibleIf?.rules?.[0]?.field;
1732
+ if (!visibleIf || !parentId) continue;
1733
+ const converted = conditionToHubspot(visibleIf, parentId);
1734
+ const parent = byId.get(parentId);
1735
+ const mapping = fld.hubspot ?? {};
1736
+ const property = mapping.property?.trim() || fld.name;
1737
+ if (!converted || !parent) continue;
1738
+ moves.set(property, { parent: parent.property, ...converted });
1739
+ }
1740
+ }
1741
+ if (!moves.size) return groups;
1742
+ const cloned = JSON.parse(JSON.stringify(groups));
1743
+ const flat = [];
1744
+ for (const group of cloned) {
1745
+ (group.fields ?? []).forEach((field, index2) => flat.push({ group, index: index2, field }));
1746
+ }
1747
+ for (const [property, move] of moves) {
1748
+ const child = flat.find((item) => item.field.name === property);
1749
+ const parent = flat.find((item) => item.field.name === move.parent);
1750
+ if (!child || !parent || child === parent) continue;
1751
+ parent.field.dependentFields = [
1752
+ ...parent.field.dependentFields ?? [],
1753
+ {
1754
+ dependentCondition: { operator: move.operator, values: move.values },
1755
+ field: child.field
1756
+ }
1757
+ ];
1758
+ child.group.fields?.splice(child.index, 1);
1759
+ }
1760
+ return cloned;
1761
+ }
1652
1762
  const HS_BASE$2 = "https://api.hubapi.com";
1653
1763
  const OBJECT_BY_TYPE_ID = {
1654
1764
  "0-1": "contact",
@@ -1730,7 +1840,7 @@ function convertField(raw, ctx) {
1730
1840
  helpText: raw.description || void 0,
1731
1841
  ...options.length ? { options } : {},
1732
1842
  hubspot: { object, property: name },
1733
- ...ctx.parentCondition ? { visibleIf: ctx.parentCondition } : {}
1843
+ ...ctx.parentCondition ? { visibleIf: ctx.parentCondition, visibleIfSource: "hubspot" } : {}
1734
1844
  };
1735
1845
  const out = [field];
1736
1846
  for (const dependent of raw.dependentFields ?? []) {
@@ -1753,6 +1863,12 @@ function convertHubspotForm(raw) {
1753
1863
  const usedNames = /* @__PURE__ */ new Set();
1754
1864
  const fields = [];
1755
1865
  for (const group of raw.fieldGroups ?? []) {
1866
+ if (group.groupType === "progressive" || group.groupType === "queued") {
1867
+ skipped.push({
1868
+ code: "progressive",
1869
+ detail: group.groupType
1870
+ });
1871
+ }
1756
1872
  if (group.richText && !(group.fields ?? []).length) {
1757
1873
  skipped.push({ code: "rich-text", detail: group.richText.slice(0, 80) });
1758
1874
  continue;
@@ -1824,7 +1940,10 @@ function mergeHubspotImport(current, incoming) {
1824
1940
  existing.helpText = next.helpText;
1825
1941
  if (next.options) existing.options = next.options;
1826
1942
  else delete existing.options;
1827
- if (!existing.visibleIf && next.visibleIf) existing.visibleIf = next.visibleIf;
1943
+ if (next.visibleIf && (existing.visibleIfSource === "hubspot" || !existing.visibleIf)) {
1944
+ existing.visibleIf = next.visibleIf;
1945
+ if (existing.visibleIfSource !== "editor") existing.visibleIfSource = "hubspot";
1946
+ }
1828
1947
  updated.push(key);
1829
1948
  continue;
1830
1949
  }
@@ -2047,24 +2166,20 @@ async function loadFormShape(apiKey, formGuid) {
2047
2166
  return null;
2048
2167
  }
2049
2168
  }
2050
- async function findContactIdByEmail(apiKey, email) {
2051
- const res = await fetch("https://api.hubapi.com/crm/v3/objects/contacts/search", {
2052
- method: "POST",
2053
- headers: {
2054
- Authorization: `Bearer ${apiKey}`,
2055
- "Content-Type": "application/json"
2056
- },
2057
- body: JSON.stringify({
2058
- filterGroups: [
2059
- { filters: [{ propertyName: "email", operator: "EQ", value: email }] }
2060
- ],
2061
- properties: ["email"],
2062
- limit: 1
2063
- })
2064
- });
2065
- const body = await res.json().catch(() => ({}));
2066
- if (!res.ok) return void 0;
2067
- return body.results?.[0]?.id;
2169
+ async function findContactIdByEmail(apiKey, email, retryDelaysMs = [400, 1200]) {
2170
+ for (const delay of [0, ...retryDelaysMs]) {
2171
+ if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
2172
+ const res = await fetch(
2173
+ `https://api.hubapi.com/crm/v3/objects/contacts/${encodeURIComponent(email)}?idProperty=email`,
2174
+ { headers: { Authorization: `Bearer ${apiKey}` } }
2175
+ );
2176
+ if (res.ok) {
2177
+ const body = await res.json().catch(() => ({}));
2178
+ return body.id || void 0;
2179
+ }
2180
+ if (res.status !== 404) return void 0;
2181
+ }
2182
+ return void 0;
2068
2183
  }
2069
2184
  function contextPayload(ctx) {
2070
2185
  const out = {};
@@ -2145,13 +2260,62 @@ async function submitMarketingForm(input) {
2145
2260
  async function syncFieldsToHubspotForm(apiKey, formGuid, definition) {
2146
2261
  const raw = await fetchHubspotForm(apiKey, formGuid);
2147
2262
  const extra = missingMappedFields(definition, formFieldNames(raw));
2148
- if (!extra.length) return [];
2149
- await patchHubspotForm(apiKey, formGuid, {
2150
- fieldGroups: appendFieldGroups(raw.fieldGroups ?? [], extra)
2151
- });
2263
+ const fieldGroups = applyVisibleIfToFieldGroups(
2264
+ appendFieldGroups(raw.fieldGroups ?? [], extra),
2265
+ definition
2266
+ );
2267
+ if (!extra.length && JSON.stringify(fieldGroups) === JSON.stringify(appendFieldGroups(raw.fieldGroups ?? [], []))) {
2268
+ return [];
2269
+ }
2270
+ await patchHubspotForm(apiKey, formGuid, { fieldGroups });
2152
2271
  clearFormShapeCache(formGuid);
2153
2272
  return extra.map((field) => field.property);
2154
2273
  }
2274
+ async function createMarketingForm(apiKey, name, definition) {
2275
+ const fields = mappedContactFields(definition).map((field) => toHsFormField(field));
2276
+ if (!fields.length) {
2277
+ throw new Error("Add at least one mapped contact field before creating a HubSpot form");
2278
+ }
2279
+ const res = await fetch("https://api.hubapi.com/marketing/v3/forms", {
2280
+ method: "POST",
2281
+ headers: {
2282
+ Authorization: `Bearer ${apiKey}`,
2283
+ "Content-Type": "application/json"
2284
+ },
2285
+ body: JSON.stringify({
2286
+ name,
2287
+ formType: "hubspot",
2288
+ fieldGroups: applyVisibleIfToFieldGroups(
2289
+ fields.map((field) => ({
2290
+ groupType: "default_group",
2291
+ richTextType: "text",
2292
+ fields: [field]
2293
+ })),
2294
+ definition
2295
+ ),
2296
+ configuration: {
2297
+ language: "fr",
2298
+ recaptchaEnabled: false,
2299
+ createNewContactForNewEmail: true,
2300
+ prePopulateKnownValues: true,
2301
+ notifyContactOwner: false,
2302
+ notifyRecipients: [],
2303
+ editable: true,
2304
+ archivable: true,
2305
+ cloneable: true,
2306
+ allowLinkToResetKnownValues: false,
2307
+ postSubmitAction: { type: "thank_you", value: "Merci." }
2308
+ },
2309
+ displayOptions: { submitButtonText: "Envoyer", style: { submitButtonText: "Envoyer" } },
2310
+ legalConsentOptions: { type: "none" }
2311
+ })
2312
+ });
2313
+ const body = await res.json().catch(() => ({}));
2314
+ if (!res.ok || !body.id) {
2315
+ throw new Error(body.message || `HubSpot ${res.status}`);
2316
+ }
2317
+ return body.id;
2318
+ }
2155
2319
  function publicForm(entry) {
2156
2320
  return {
2157
2321
  slug: entry.slug,
@@ -2165,7 +2329,9 @@ function publicForm(entry) {
2165
2329
  steps: (entry.definition?.steps ?? []).map((step) => ({
2166
2330
  ...step,
2167
2331
  // The CRM mappings are the server's business — never the browser's.
2168
- fields: (step.fields ?? []).map(({ hubspot: _hubspot, companyMap: _companyMap, ...fld }) => fld)
2332
+ fields: (step.fields ?? []).map(
2333
+ ({ hubspot: _hubspot, companyMap: _companyMap, visibleIfSource: _source, ...fld }) => fld
2334
+ )
2169
2335
  }))
2170
2336
  };
2171
2337
  }
@@ -2909,6 +3075,22 @@ function createFormsAdminController(strapi) {
2909
3075
  ctx.body = { errors: structural, problems: mappings };
2910
3076
  return;
2911
3077
  }
3078
+ const { apiKey } = await resolveApiKey(strapi);
3079
+ const account = await resolveAccount(strapi);
3080
+ const ownGuid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
3081
+ const formGuid = isFormGuid(ownGuid) ? ownGuid : isFormGuid(account.defaultFormId) ? account.defaultFormId.trim() : "";
3082
+ if (apiKey && formGuid) {
3083
+ const shape = await loadFormShape(apiKey, formGuid);
3084
+ const missingHubspot = shape ? missingRequiredHubspotFields(
3085
+ entry.definition ?? EMPTY_DEFINITION,
3086
+ [...shape.fields.filter((field) => field.required)]
3087
+ ) : [];
3088
+ if (missingHubspot.length) {
3089
+ ctx.status = 400;
3090
+ ctx.body = { errors: structural, problems: mappings, missingHubspot };
3091
+ return;
3092
+ }
3093
+ }
2912
3094
  await documents().publish({
2913
3095
  documentId: ctx.params.documentId,
2914
3096
  locale: ctx.query.locale
@@ -2916,22 +3098,16 @@ function createFormsAdminController(strapi) {
2916
3098
  const policy = await resolvePolicy(strapi);
2917
3099
  let addedFields = [];
2918
3100
  let syncWarning;
2919
- if (policy.syncFieldsOnPublish) {
2920
- const { apiKey } = await resolveApiKey(strapi);
2921
- const account = await resolveAccount(strapi);
2922
- const ownGuid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
2923
- const formGuid = isFormGuid(ownGuid) ? ownGuid : isFormGuid(account.defaultFormId) ? account.defaultFormId.trim() : "";
2924
- if (apiKey && formGuid) {
2925
- try {
2926
- addedFields = await syncFieldsToHubspotForm(
2927
- apiKey,
2928
- formGuid,
2929
- entry.definition ?? EMPTY_DEFINITION
2930
- );
2931
- } catch (err) {
2932
- syncWarning = err.message;
2933
- strapi.log.warn(`[hubspot] field sync on publish failed — ${syncWarning}`);
2934
- }
3101
+ if (policy.syncFieldsOnPublish && apiKey && formGuid) {
3102
+ try {
3103
+ addedFields = await syncFieldsToHubspotForm(
3104
+ apiKey,
3105
+ formGuid,
3106
+ entry.definition ?? EMPTY_DEFINITION
3107
+ );
3108
+ } catch (err) {
3109
+ syncWarning = err.message;
3110
+ strapi.log.warn(`[hubspot] field sync on publish failed — ${syncWarning}`);
2935
3111
  }
2936
3112
  }
2937
3113
  ctx.body = {
@@ -2940,6 +3116,31 @@ function createFormsAdminController(strapi) {
2940
3116
  ...syncWarning ? { syncWarning } : {}
2941
3117
  };
2942
3118
  },
3119
+ async createInHubspot(ctx) {
3120
+ const entry = await documents().findOne({
3121
+ documentId: ctx.params.documentId,
3122
+ locale: ctx.query.locale,
3123
+ status: "draft"
3124
+ });
3125
+ if (!entry) ctx.throw(404, "Form not found");
3126
+ const { apiKey } = await resolveApiKey(strapi);
3127
+ if (!apiKey) ctx.throw(400, "HubSpot API key is missing");
3128
+ try {
3129
+ const id = await createMarketingForm(
3130
+ apiKey,
3131
+ entry.name || entry.slug || "Formulaire",
3132
+ entry.definition ?? EMPTY_DEFINITION
3133
+ );
3134
+ const updated = await documents().update({
3135
+ documentId: ctx.params.documentId,
3136
+ locale: ctx.query.locale,
3137
+ data: { hubspotFormId: id }
3138
+ });
3139
+ ctx.body = { form: updated, hubspotFormId: id };
3140
+ } catch (err) {
3141
+ ctx.throw(400, err.message);
3142
+ }
3143
+ },
2943
3144
  async unpublish(ctx) {
2944
3145
  await documents().unpublish({
2945
3146
  documentId: ctx.params.documentId,
@@ -3706,6 +3907,7 @@ const routes = {
3706
3907
  adminRoute("GET", "/builder/forms/:documentId", "formsAdmin.findOne", [FORMS_ACTION]),
3707
3908
  adminRoute("PUT", "/builder/forms/:documentId", "formsAdmin.update", [FORMS_ACTION]),
3708
3909
  adminRoute("POST", "/builder/forms/:documentId/publish", "formsAdmin.publish", [FORMS_ACTION]),
3910
+ adminRoute("POST", "/builder/forms/:documentId/create-hubspot", "formsAdmin.createInHubspot", [FORMS_ACTION]),
3709
3911
  adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
3710
3912
  adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
3711
3913
  adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),
@@ -22,6 +22,7 @@ export interface FormFieldDef {
22
22
  type: string;
23
23
  required?: boolean;
24
24
  visibleIf?: Condition | null;
25
+ visibleIfSource?: "hubspot" | "editor" | null;
25
26
  [key: string]: unknown;
26
27
  }
27
28
  export interface FormStepDef {
@@ -26,6 +26,21 @@ export interface MappedFormField {
26
26
  export declare function leftoverContactProps(contact: Record<string, string>, sentFieldNames: Iterable<string>): Record<string, string>;
27
27
  export declare function mappedContactFields(definition: FormDefinition): MappedFormField[];
28
28
  export declare function missingMappedFields(definition: FormDefinition, existingNames: Iterable<string>): MappedFormField[];
29
+ export interface MissingHubspotRequired {
30
+ name: string;
31
+ objectTypeId: string;
32
+ object: string;
33
+ }
34
+ /** Properties the Strapi form can actually send (`objectTypeId:property`). */
35
+ export declare function collectedHubspotKeys(definition: FormDefinition): Set<string>;
36
+ /**
37
+ * Required marketing-form fields the Strapi form never collects. HubSpot
38
+ * rejects the whole conversion when one of these is absent.
39
+ */
40
+ export declare function missingRequiredHubspotFields(definition: FormDefinition, required: {
41
+ name: string;
42
+ objectTypeId?: string;
43
+ }[]): MissingHubspotRequired[];
29
44
  export declare function hsFieldType(type: string, options?: unknown[]): string;
30
45
  export declare function toHsFormField(field: MappedFormField): Record<string, unknown>;
31
46
  export interface RawFieldGroupLike {
@@ -35,3 +50,8 @@ export interface RawFieldGroupLike {
35
50
  fields?: unknown[];
36
51
  }
37
52
  export declare function appendFieldGroups(existing: RawFieldGroupLike[], extra: MappedFormField[]): Record<string, unknown>[];
53
+ /**
54
+ * Nest Strapi `visibleIf` fields under their HubSpot parent as dependentFields.
55
+ * Conditions edited in Strapi are included; unsupported multi-parent rules stay flat.
56
+ */
57
+ export declare function applyVisibleIfToFieldGroups(groups: Record<string, unknown>[], definition: FormDefinition): Record<string, unknown>[];
@@ -26,6 +26,7 @@ export declare function createFormsAdminController(strapi: Core.Strapi): {
26
26
  create(ctx: Ctx): Promise<void>;
27
27
  update(ctx: Ctx): Promise<void>;
28
28
  publish(ctx: Ctx): Promise<void>;
29
+ createInHubspot(ctx: Ctx): Promise<void>;
29
30
  unpublish(ctx: Ctx): Promise<void>;
30
31
  /**
31
32
  * Feeds the `hubspot.form-picker` custom field: every form of the builder
@@ -133,7 +133,18 @@ export interface FormSubmitInput {
133
133
  }
134
134
  export declare function clearFormShapeCache(formGuid?: string): void;
135
135
  export declare function loadFormShape(apiKey: string, formGuid: string): Promise<FormShape | null>;
136
- export declare function findContactIdByEmail(apiKey: string, email: string): Promise<string | undefined>;
136
+ /**
137
+ * The contact's id right after a Forms API conversion, via the DIRECT object
138
+ * lookup (`idProperty=email`) — never the Search API: the search index lags by
139
+ * seconds to minutes on freshly created records, so a contact the conversion
140
+ * itself just created was invisible, silently costing brand-new leads their
141
+ * timeline note and company association. The object store has no such lag.
142
+ *
143
+ * The conversion is processed asynchronously on HubSpot's side, so a 404 is
144
+ * retried briefly (the delays are injectable for tests). Any other error
145
+ * (auth, 5xx) gives up immediately — retrying wouldn't change it.
146
+ */
147
+ export declare function findContactIdByEmail(apiKey: string, email: string, retryDelaysMs?: readonly number[]): Promise<string | undefined>;
137
148
  /**
138
149
  * Submit a marketing form. Extra fields HubSpot doesn't know on that form
139
150
  * 400 the whole request, so callers should filter first. A GDPR block on the
@@ -153,3 +164,5 @@ export declare function submitMarketingForm(input: FormSubmitInput): Promise<{
153
164
  * that already exist. Returns the property names that were added.
154
165
  */
155
166
  export declare function syncFieldsToHubspotForm(apiKey: string, formGuid: string, definition: FormDefinition): Promise<string[]>;
167
+ /** Create a marketing form from the Strapi definition and return its GUID. */
168
+ export declare function createMarketingForm(apiKey: string, name: string, definition: FormDefinition): Promise<string>;
@@ -21,7 +21,7 @@ export interface HubspotFormSummary {
21
21
  }
22
22
  /** Something the converter could not express in the builder's model. */
23
23
  export interface SkippedItem {
24
- code: "field-type" | "hidden-field" | "object" | "duplicate" | "condition" | "rich-text" | "legal-consent";
24
+ code: "field-type" | "hidden-field" | "object" | "duplicate" | "condition" | "rich-text" | "legal-consent" | "progressive";
25
25
  /** Field label or name, when the item is a field. */
26
26
  label?: string;
27
27
  /** Raw detail (the fieldType, the operator…) for the report. */
@@ -58,6 +58,7 @@ interface RawField {
58
58
  dependentFields?: RawDependent[];
59
59
  }
60
60
  interface RawFieldGroup {
61
+ groupType?: string;
61
62
  richText?: string;
62
63
  fields?: RawField[];
63
64
  }
@@ -274,6 +274,7 @@ declare const _default: {
274
274
  create(ctx: import("./formsAdmin").Ctx): Promise<void>;
275
275
  update(ctx: import("./formsAdmin").Ctx): Promise<void>;
276
276
  publish(ctx: import("./formsAdmin").Ctx): Promise<void>;
277
+ createInHubspot(ctx: import("./formsAdmin").Ctx): Promise<void>;
277
278
  unpublish(ctx: import("./formsAdmin").Ctx): Promise<void>;
278
279
  options(ctx: import("./formsAdmin").Ctx): Promise<void>;
279
280
  listSources(ctx: import("./formsAdmin").Ctx): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-hubspot",
3
- "version": "0.16.7",
3
+ "version": "0.16.9",
4
4
  "description": "The form builder for HubSpot in Strapi — multi-step forms with conditional fields mapped to real CRM properties, a public submit pipeline with retries, and save-time mapping validation.",
5
5
  "keywords": [
6
6
  "strapi",