1- # Migration Guide: v0.x → v1.0
1+ # Migration Guide: v0.3 → v0.4
22
3- This guide helps you migrate from Twenty CRM PHP Client v0.x (hardcoded entities) to v1.0 (dynamic entity system with code generation).
3+ This guide helps you migrate from Twenty CRM PHP Client v0.3 and earlier (hardcoded entities) to v0.4 (dynamic entity system with code generation).
44
55## Overview of Changes
66
7- ### What Changed in v1.0
7+ ### What Changed in v0.4
88
9- ** v1.0 introduces a fundamental architectural shift:**
9+ ** v0.4 introduces a fundamental architectural shift:**
1010
1111- ** Removed** : Hardcoded ` Contact ` , ` Company ` DTOs and their services
1212- ** Added** : Dynamic entity system that works with ANY Twenty CRM entity
@@ -29,7 +29,7 @@ The following classes and methods have been **removed**:
2929#### Removed Classes
3030
3131``` php
32- // ❌ REMOVED in v1.0
32+ // ❌ REMOVED in v0.4
3333use Factorial\TwentyCrm\DTO\Contact;
3434use Factorial\TwentyCrm\DTO\ContactCollection;
3535use Factorial\TwentyCrm\DTO\ContactSearchFilter;
@@ -46,17 +46,17 @@ use Factorial\TwentyCrm\Services\CompanyServiceInterface;
4646#### Removed Client Methods
4747
4848``` php
49- // ❌ REMOVED in v1.0
49+ // ❌ REMOVED in v0.4
5050$client->contacts(); // ContactService
5151$client->companies(); // CompanyService
5252```
5353
5454### Kept Classes
5555
56- These helper classes are ** still available** in v1.0 :
56+ These helper classes are ** still available** in v0.4 :
5757
5858``` php
59- // ✅ KEPT in v1.0 (used by field handlers)
59+ // ✅ KEPT in v0.4 (used by field handlers)
6060use Factorial\TwentyCrm\DTO\Phone;
6161use Factorial\TwentyCrm\DTO\PhoneCollection;
6262use Factorial\TwentyCrm\DTO\Link;
@@ -71,7 +71,7 @@ use Factorial\TwentyCrm\DTO\CustomFilter;
7171
7272## Migration Paths
7373
74- You have ** two options** for migrating to v1.0 :
74+ You have ** two options** for migrating to v0.4 :
7575
7676### Option 1: Use Code Generation (Recommended)
7777
@@ -80,13 +80,13 @@ Generate typed entities for your specific Twenty CRM instance.
8080** Advantages:**
8181- ✅ Full IDE autocomplete support
8282- ✅ Type safety with PHPStan/Psalm
83- - ✅ Familiar API similar to v0.x
83+ - ✅ Familiar API similar to v0.3 and earlier
8484- ✅ Commit generated code to your repository
8585- ✅ Works with custom entities and custom fields
8686
8787** Steps:**
8888
89- 1 . ** Install v1.0 ** :
89+ 1 . ** Install v0.4 ** :
9090 ``` bash
9191 composer require factorial-io/twenty-crm-php-client:^1.0
9292 ```
@@ -98,7 +98,7 @@ Generate typed entities for your specific Twenty CRM instance.
9898 api_url : https://your-twenty.example.com/rest/
9999 api_token : ${TWENTY_API_TOKEN}
100100 entities :
101- - person # Was "contact" in v0.x
101+ - person # Was "contact" in v0.3 and earlier
102102 - company
103103 options :
104104 overwrite : true
@@ -111,7 +111,7 @@ Generate typed entities for your specific Twenty CRM instance.
111111
1121124 . ** Update your code** :
113113
114- ** Before (v0.x ):**
114+ ** Before (v0.3 ):**
115115 ``` php
116116 use Factorial\TwentyCrm\DTO\Contact;
117117 use Factorial\TwentyCrm\DTO\ContactSearchFilter;
@@ -124,7 +124,7 @@ Generate typed entities for your specific Twenty CRM instance.
124124 }
125125 ```
126126
127- ** After (v1.0 with generated code):**
127+ ** After (v0.4 with generated code):**
128128 ``` php
129129 use MyApp\TwentyCrm\Entities\Person;
130130 use MyApp\TwentyCrm\Entities\PersonService;
@@ -147,7 +147,7 @@ Generate typed entities for your specific Twenty CRM instance.
1471475 . ** Commit generated code** :
148148 ``` bash
149149 git add src/TwentyCrm/Entities/
150- git commit -m " Add generated Twenty CRM entities for v1.0 "
150+ git commit -m " Add generated Twenty CRM entities for v0.4 "
151151 ```
152152
153153### Option 2: Use DynamicEntity (Flexible)
@@ -167,7 +167,7 @@ Use the dynamic entity system without code generation.
167167
168168** Example:**
169169
170- ** Before (v0.x ):**
170+ ** Before (v0.3 ):**
171171``` php
172172use Factorial\TwentyCrm\DTO\Contact;
173173
@@ -178,7 +178,7 @@ $contact->setLastName('Doe');
178178$created = $client->contacts()->create($contact);
179179```
180180
181- ** After (v1.0 with DynamicEntity):**
181+ ** After (v0.4 with DynamicEntity):**
182182``` php
183183use Factorial\TwentyCrm\DTO\DynamicEntity;
184184use Factorial\TwentyCrm\DTO\Name;
@@ -196,7 +196,7 @@ $created = $client->entity('person')->create($person);
196196
197197Twenty CRM's default entity is ** "person"** , not "contact". Here's how fields map:
198198
199- | v0.x (Contact) | v1.0 (Person) | Type | Notes |
199+ | v0.3 and earlier (Contact) | v0.4 (Person) | Type | Notes |
200200| ----------------| ---------------| ------| -------|
201201| ` getEmail() ` | ` getEmail() ` | ` string ` | Primary email extracted from ` emails ` object |
202202| ` getFirstName() ` | ` getName()->firstName ` | ` string ` | Part of ` name ` object |
@@ -211,12 +211,12 @@ Twenty CRM's default entity is **"person"**, not "contact". Here's how fields ma
211211
212212#### Emails (Simplified)
213213
214- ** v0.x :**
214+ ** v0.3 and earlier :**
215215``` php
216216$email = $contact->getEmail(); // Direct string
217217```
218218
219- ** v1.0 :**
219+ ** v0.4 :**
220220``` php
221221// Option 1: Field handler extracts primary email
222222$email = $person->getEmail(); // string
@@ -227,13 +227,13 @@ $emails = $person->get('emails'); // ['primaryEmail' => '
[email protected] ', ...]
227227
228228#### Name (Structured)
229229
230- ** v0.x :**
230+ ** v0.3 and earlier :**
231231``` php
232232$firstName = $contact->getFirstName();
233233$lastName = $contact->getLastName();
234234```
235235
236- ** v1.0 :**
236+ ** v0.4 :**
237237``` php
238238use Factorial\TwentyCrm\DTO\Name;
239239
@@ -251,29 +251,29 @@ $fullName = $name->getFullName();
251251
252252#### Phones (Collection)
253253
254- ** v0.x :**
254+ ** v0.3 and earlier :**
255255``` php
256256$phones = $contact->getPhones(); // PhoneCollection
257257$primary = $phones->getPrimaryNumber();
258258```
259259
260- ** v1.0 :**
260+ ** v0.4 :**
261261``` php
262262$phones = $person->getPhones(); // PhoneCollection (same!)
263263$primary = $phones->getPrimaryNumber();
264264```
265265
266266## Entity Relations
267267
268- Relations work differently in v1.0 :
268+ Relations work differently in v0.4 :
269269
270- ** v0.x (hardcoded):**
270+ ** v0.3 and earlier (hardcoded):**
271271``` php
272272// Relations were not explicitly supported
273273$companyId = $contact->getCompanyId();
274274```
275275
276- ** v1.0 (with RelationLoader):**
276+ ** v0.4 (with RelationLoader):**
277277``` php
278278// Lazy loading
279279$company = $person->loadRelation('company');
@@ -292,7 +292,7 @@ foreach ($persons as $person) {
292292
293293### ContactSearchFilter → CustomFilter
294294
295- ** v0.x :**
295+ ** v0.3 and earlier :**
296296``` php
297297use Factorial\TwentyCrm\DTO\ContactSearchFilter;
298298
@@ -302,7 +302,7 @@ $filter = new ContactSearchFilter(
302302);
303303```
304304
305- ** v1.0 :**
305+ ** v0.4 :**
306306``` php
307307use Factorial\TwentyCrm\DTO\CustomFilter;
308308
@@ -335,28 +335,28 @@ $options = new SearchOptions(
335335
336336### Pattern 1: Finding Contacts/Persons
337337
338- ** Before (v0.x ):**
338+ ** Before (v0.3 ):**
339339``` php
340340$filter = new ContactSearchFilter(email: '
[email protected] ');
341341$contacts = $client->contacts()->find($filter);
342342```
343343
344- ** After (v1.0 - Generated):**
344+ ** After (v0.4 - Generated):**
345345``` php
346346$filter = new CustomFilter('emails.primaryEmail eq "
[email protected] "');
347347$options = new SearchOptions(limit: 50);
348348$persons = $personService->find($filter, $options);
349349```
350350
351- ** After (v1.0 - Dynamic):**
351+ ** After (v0.4 - Dynamic):**
352352``` php
353353$filter = new CustomFilter('emails.primaryEmail eq "
[email protected] "');
354354$persons = $client->entity('person')->find($filter);
355355```
356356
357357### Pattern 2: Creating Contacts/Persons
358358
359- ** Before (v0.x ):**
359+ ** Before (v0.3 ):**
360360``` php
361361$contact = new Contact();
362362$contact->setEmail('
[email protected] ');
@@ -365,7 +365,7 @@ $contact->setLastName('Smith');
365365$created = $client->contacts()->create($contact);
366366```
367367
368- ** After (v1.0 - Generated):**
368+ ** After (v0.4 - Generated):**
369369``` php
370370use MyApp\TwentyCrm\Entities\Person;
371371use Factorial\TwentyCrm\DTO\Name;
@@ -376,7 +376,7 @@ $person->setName(new Name('Jane', 'Smith'));
376376$created = $personService->create($person);
377377```
378378
379- ** After (v1.0 - Dynamic):**
379+ ** After (v0.4 - Dynamic):**
380380``` php
381381$person = new DynamicEntity($definition, [
382382 'emails' => ['primaryEmail' => '
[email protected] '],
@@ -387,14 +387,14 @@ $created = $client->entity('person')->create($person);
387387
388388### Pattern 3: Updating Contacts/Persons
389389
390- ** Before (v0.x ):**
390+ ** Before (v0.3 ):**
391391``` php
392392$contact = $client->contacts()->getById($id);
393393$contact->setEmail('
[email protected] ');
394394$client->contacts()->update($contact);
395395```
396396
397- ** After (v1.0 ):**
397+ ** After (v0.4 ):**
398398``` php
399399$person = $personService->getById($id);
400400$person->setEmail('
[email protected] ');
@@ -403,13 +403,13 @@ $personService->update($person);
403403
404404### Pattern 4: Batch Operations
405405
406- ** Before (v0.x ):**
406+ ** Before (v0.3 ):**
407407``` php
408408$contacts = [$contact1, $contact2, $contact3];
409409$client->contacts()->batchUpsert($contacts);
410410```
411411
412- ** After (v1.0 ):**
412+ ** After (v0.4 ):**
413413``` php
414414$persons = [$person1, $person2, $person3];
415415$personService->batchUpsert($persons);
@@ -419,11 +419,11 @@ $personService->batchUpsert($persons);
419419
420420### Q: Why was Contact renamed to Person?
421421
422- ** A:** Twenty CRM's default entity is "person", not "contact". The v0.x library used "contact" for familiarity, but v1.0 follows Twenty CRM's actual schema.
422+ ** A:** Twenty CRM's default entity is "person", not "contact". The v0.3 and earlier library used "contact" for familiarity, but v0.4 follows Twenty CRM's actual schema.
423423
424424### Q: Can I still use the old Contact class?
425425
426- ** A:** No. Contact, Company, and their services have been removed in v1.0 . This is a breaking change requiring migration.
426+ ** A:** No. Contact, Company, and their services have been removed in v0.4 . This is a breaking change requiring migration.
427427
428428### Q: Do I have to use code generation?
429429
@@ -435,9 +435,9 @@ $personService->batchUpsert($persons);
435435
436436### Q: How do I work with custom entities (like Campaign)?
437437
438- ** v0.x :** Not possible without library code changes.
438+ ** v0.3 and earlier :** Not possible without library code changes.
439439
440- ** v1.0 :** Works immediately:
440+ ** v0.4 :** Works immediately:
441441
442442``` php
443443// With code generation
@@ -453,9 +453,9 @@ $client->entity('campaign')->create($campaign);
453453
454454### Q: What if my Twenty instance has custom fields on Person/Company?
455455
456- ** v0.x :** Custom fields were accessible but not type-safe.
456+ ** v0.3 and earlier :** Custom fields were accessible but not type-safe.
457457
458- ** v1.0 :** Generated code includes ALL fields (standard + custom) with proper types.
458+ ** v0.4 :** Generated code includes ALL fields (standard + custom) with proper types.
459459
460460``` bash
461461# Generate entities matching YOUR exact schema
@@ -473,14 +473,14 @@ use Factorial\TwentyCrm\Exception\ApiException;
473473try {
474474 $person = $personService->getById($id);
475475} catch (ApiException $e) {
476- // Same exception hierarchy as v0.x
476+ // Same exception hierarchy as v0.3 and earlier
477477 error_log('API error: ' . $e->getMessage());
478478}
479479```
480480
481481### Q: Is there a performance difference?
482482
483- No significant performance difference. The dynamic entity system uses the same HTTP client and request patterns as v0.x .
483+ No significant performance difference. The dynamic entity system uses the same HTTP client and request patterns as v0.3 and earlier .
484484
485485Code generation may be slightly faster due to static property access vs array lookups, but the difference is negligible in real-world usage.
486486
@@ -521,7 +521,7 @@ Code generation may be slightly faster due to static property access vs array lo
521521
522522Here's a complete before/after example:
523523
524- ### Before (v0.x )
524+ ### Before (v0.3 )
525525
526526``` php
527527<?php
552552$client->contacts()->update($contact);
553553```
554554
555- ### After (v1.0 with Generated Code)
555+ ### After (v0.4 with Generated Code)
556556
557557``` php
558558<?php
590590$personService->update($person);
591591```
592592
593- ### After (v1.0 with DynamicEntity)
593+ ### After (v0.4 with DynamicEntity)
594594
595595``` php
596596<?php
@@ -624,6 +624,6 @@ $client->entity('person')->update($person);
624624
625625---
626626
627- ** Version:** 1.0
627+ ** Version:** 0.4
628628** Last Updated:** 2025-10-12
629- ** Target Audience:** Users migrating from v0.x to v1.0
629+ ** Target Audience:** Users migrating from v0.3 and earlier to v0.4
0 commit comments