../courses.php
CRM
CRM101
90
Notes, Calls, And Follow-Ups
Use CRM notes to remember what happened and what needs to happen next.
Storing a contact's notes as
"notes": "Called Tuesday, interested"
works fine until a second call happens, and now there's nowhere to put the second note without erasing the first. Why does one flat text field run out of room so quickly?
A single string field can only ever hold one value at a time — writing a second note means either overwriting the first one or awkwardly concatenating text onto the end of an ever-growing single string, with no way to tell where one note ends and the next begins. What's actually needed is an array of separate notes, the same nested-array-inside-an-object structure earlier JSON lessons covered, applied here to something that genuinely needs to grow over time.
A contact isn't just static information — name, email, status — it accumulates history: calls made, things discussed, what was promised. That history needs to grow over time, which a single flat field can't do gracefully.
Adding a
"notes": []
array to each contact, instead of a single
"notes": ""
string, means a new note can always be appended without touching or losing any of the previous ones.
Each note benefits from its own small structure — the text itself, and when it was added — rather than being just a bare string, the same reasoning that led earlier JSON lessons to model real data as objects instead of plain values.
Appending a note follows the identical read-modify-write pattern from lecture 2 — read the JSON, find the right contact, push onto its notes array, write the whole file back — just reaching one level deeper into the data than before.
This lesson adds a notes array to each contact and a small form that appends a new, timestamped note to a specific contact — the first time this course reaches inside one contact's own nested data, rather than just adding or filtering whole contacts.
The CRM, stage four: notes that actually accumulate
<?php
$contactsFile = __DIR__ . '/contacts.json';
$contacts = json_decode(file_get_contents($contactsFile), true);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$index = $_POST['contact_index'];
$contacts[$index]['notes'][] = [
'text' => $_POST['note_text'],
'date' => date('Y-m-d')
];
file_put_contents($contactsFile, json_encode($contacts));
}
?>
<?php foreach ($contacts as $i => $contact) { ?>
<h3><?php echo $contact['name']; ?></h3>
<?php foreach ($contact['notes'] as $note) { ?>
<p><?php echo $note['date']; ?> — <?php echo $note['text']; ?></p>
<?php } ?>
<form method="POST">
<input type="hidden" name="contact_index" value="<?php echo $i; ?>">
<input name="note_text" placeholder="Add a note">
<button type="submit">Add Note</button>
</form>
<?php } ?>
foreach ($contacts as $i => $contact) — Captures each contact's actual array index, not just the contact itself — that index is what lets a specific note get attached to the right contact later.
<input type="hidden" name="contact_index" value="<?php echo $i; ?>"> — A hidden field carrying the contact's index along with the form submission — invisible to whoever's filling out the form, but exactly how PHP later knows which contact this specific note belongs to.
$contacts[$index]['notes'][] = [...] — Reaches into one specific contact by index, into its
notes
array specifically, and appends a new note object onto it — every other contact's notes stay completely untouched.
'date' => date('Y-m-d') — PHP's
date()
function generates today's date automatically — nobody has to type it into the form, and it's recorded the moment the note is actually added.
The inner
foreach loop, nested inside the outer one — For every contact, this loops over that contact's own notes array — a loop inside a loop, directly mirroring the nested array-inside-object structure the data itself actually has.
A flat string field forces an impossible choice between losing data and an unreadable mess
A single
notes
string, appended to repeatedly, eventually becomes one long unreadable block of text with no clear separation between entries — an array of separate note objects avoids that entirely, since each one stays its own distinct, readable entry.
The hidden index field is what makes a multi-contact page with multiple forms actually work
Without it, a submitted note would have no way to specify which contact it belongs to — every contact's form looks identical except for this one hidden value, which is exactly what lets PHP route the note correctly.
A contact missing the notes array entirely would crash this code
If an older contact from an earlier lecture never had a
notes
key added, the inner
foreach ($contact['notes'] as $note)
loop would fail on that specific contact — exactly the same "old data doesn't automatically match a newer shape" risk covered in the previous lesson, now showing up one level deeper.
Automatically generating the date avoids a whole category of bad data
If the date were a manually typed field instead, it could be left blank, typed in the wrong format, or simply wrong — generating it with
date()
at the moment the note is added removes that entire category of mistake.
This nested read-modify-write pattern is exactly what the next lecture builds further on
Tasks and reminders, covered next, follow this same shape — reach into one specific contact, modify one specific nested piece of its data, write the whole file back — just with a different kind of nested array than notes.
A single, flat notes string — avoid
{ "name": "Maria", "notes": "Called Tuesday, interested" }
A notes array that can actually grow — use this
{
"name": "Maria",
"notes": [
{ "text": "Called Tuesday, interested", "date": "2026-06-29" }
]
}
Paste your contacts.json into ChatGPT and ask: "Does every contact here have a notes array, even an empty one, or could any of them be missing the field entirely?" It's good at spotting a missing field across several records at once, faster than checking each one by hand.
Add an empty
"notes": []
array to every contact in your JSON file.
Add the per-contact note form from this lesson and confirm a new note appears under the correct contact only.
Add a second note to the same contact and confirm both notes display, in the order they were added.
Remove the
notes
field from one contact by hand and confirm the page breaks specifically on that contact.
Add the field back as an empty array and confirm the page works again for that contact.
Storing notes as one flat string field instead of an array, making it impossible to cleanly add a second note without losing or mangling the first.
Forgetting the hidden index field in a per-contact form, leaving no way to tell which contact a submitted note actually belongs to.
Adding a contact without an empty notes array already in place, causing the display loop to fail the moment that specific contact is reached.
Asking someone to manually type a date instead of generating it automatically, introducing an unnecessary source of bad or missing data.
Confusing the outer loop over contacts with the inner loop over one contact's notes, and writing logic that runs at the wrong level of nesting.
Model a growing piece of history as an array nested inside a record, append to it with a hidden index identifying the right parent record, and explain why a single flat field can't safely hold more than one entry over time.
Login - Jit4All
Login
Welcome back to Jit4All