<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matteo Barbero</title>
    <description>The latest articles on DEV Community by Matteo Barbero (@maiobarbero).</description>
    <link>https://dev.to/maiobarbero</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F976354%2F830142ac-fbe9-4f2e-8df8-21c4817ce327.png</url>
      <title>DEV Community: Matteo Barbero</title>
      <link>https://dev.to/maiobarbero</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maiobarbero"/>
    <language>en</language>
    <item>
      <title>AI Skills for Project Management</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Fri, 08 May 2026 05:29:00 +0000</pubDate>
      <link>https://dev.to/maiobarbero/ai-skills-for-project-management-4clg</link>
      <guid>https://dev.to/maiobarbero/ai-skills-for-project-management-4clg</guid>
      <description>&lt;p&gt;After completing the DelftX project management course in TU Delft's Engineering Project Management series, the idea that stayed with me was not a template, a checklist, or a new way to make a Gantt chart look more impressive.&lt;br&gt;
It was simpler than that: before you manage a project, you need to understand what kind of complexity you are actually dealing with.&lt;/p&gt;

&lt;p&gt;That sounds obvious until you look at how many projects are managed as if all complexity were the same thing.&lt;br&gt;
More tasks? Add more planning.&lt;br&gt;
More uncertainty? Add more meetings.&lt;br&gt;
More stakeholders? Add a RACI and hope it survives contact with reality.&lt;/p&gt;

&lt;p&gt;Sometimes that works. Often it does not.&lt;br&gt;
The problem is that "this project is complex" is not a diagnosis. It is only a warning light.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://ocw.tudelft.nl/course-lectures/2-1-1-toe-framework/" rel="noopener noreferrer"&gt;TOE framework&lt;/a&gt; developed by TU Delft researchers gave me a better way to think about that warning light. TOE stands for &lt;strong&gt;Technical&lt;/strong&gt;, &lt;strong&gt;Organizational&lt;/strong&gt;, and &lt;strong&gt;External&lt;/strong&gt; complexity.&lt;/p&gt;

&lt;p&gt;The framework was developed to help project teams grasp complexity in large engineering projects and adapt the front-end development phase accordingly. The published paper behind it is &lt;a href="https://research.tudelft.nl/en/publications/grasping-project-complexity-in-large-engineering-projects-the-toe/" rel="noopener noreferrer"&gt;Grasping project complexity in large engineering projects: the TOE framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I kept thinking about how useful this would be inside an AI-assisted workflow.&lt;br&gt;
Not because AI should manage the project for you.&lt;br&gt;
Because AI is useful when it is forced to structure messy thinking, and project complexity is usually very messy thinking.&lt;/p&gt;

&lt;p&gt;So I built two new skills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/maiobarbero/my-ai-workflow/tree/main/skills/project-complexity-mapper" rel="noopener noreferrer"&gt;&lt;code&gt;project-complexity-mapper&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/maiobarbero/my-ai-workflow/tree/main/skills/project-complexity-action-planner" rel="noopener noreferrer"&gt;&lt;code&gt;project-complexity-action-planner&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They live in the same &lt;a href="https://github.com/maiobarbero/my-ai-workflow" rel="noopener noreferrer"&gt;my-ai-workflow&lt;/a&gt; repository as the rest of my AI workflow skills.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: not all complexity asks for the same response
&lt;/h2&gt;

&lt;p&gt;The mistake I see often, including in my own thinking, is treating complexity as a single number.&lt;br&gt;
We say a project is "simple", "complicated", or "very complex", then we jump straight to a management response.&lt;/p&gt;

&lt;p&gt;That loses the interesting part.&lt;/p&gt;

&lt;p&gt;A project can be technically complicated but organizationally stable.&lt;br&gt;
Another project can be technically simple but politically unpredictable.&lt;br&gt;
Another one can have both: many technical interfaces and stakeholders who are still negotiating what success even means.&lt;/p&gt;

&lt;p&gt;Those are not the same project.&lt;br&gt;
They should not be managed in the same way.&lt;/p&gt;

&lt;p&gt;The TOE framework helps by separating complexity into three families.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical complexity&lt;/strong&gt; is about the content of the project: goals, scope, technology, tasks, dependencies, quality requirements, disciplines involved, and technical risks.&lt;br&gt;
In software terms, this is where we find things like legacy integrations, unclear architecture boundaries, strict performance requirements, many work packages, unfamiliar infrastructure, or unknown delivery methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organizational complexity&lt;/strong&gt; is about the internal project organization: team size, resource availability, contracts, trust, roles, methods, tools, funding sources, schedules, and coordination across disciplines or locations.&lt;br&gt;
In software teams, this often shows up as unclear ownership, unavailable specialists, misaligned delivery methods between teams, pressure to hit a date that was chosen before discovery, or contract structures that reward the wrong behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External complexity&lt;/strong&gt; is about the context around the project: external stakeholders, approvals, political influence, market instability, regulatory pressure, dependencies outside the team, strategic pressure, public trust, and other external risks.&lt;br&gt;
In a product or platform context, this may be customers with conflicting expectations, compliance deadlines, procurement constraints, partner APIs, legal uncertainty, or leadership changes that alter priorities halfway through the work.&lt;/p&gt;

&lt;p&gt;The useful part is not the labels themselves.&lt;br&gt;
The useful part is that they stop you from pretending a technical solution can fix an organizational problem, or that a weekly status report can resolve an unstable external context.&lt;/p&gt;
&lt;h2&gt;
  
  
  Detail complexity and dynamic complexity
&lt;/h2&gt;

&lt;p&gt;The other distinction I wanted the skills to preserve is the difference between &lt;strong&gt;detail complexity&lt;/strong&gt; and &lt;strong&gt;dynamic complexity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Detail complexity is about the number of parts and the structure between them.&lt;br&gt;
Many components, many interfaces, many work packages, many actors, many dependencies, many contracts.&lt;br&gt;
This kind of complexity often benefits from control: decomposition, interface registers, baselines, dependency maps, quality criteria, clear ownership, decision gates.&lt;/p&gt;

&lt;p&gt;Dynamic complexity is about change, uncertainty, and unpredictability.&lt;br&gt;
Stakeholders shift position.&lt;br&gt;
Scope moves.&lt;br&gt;
The market changes.&lt;br&gt;
The regulator clarifies something late.&lt;br&gt;
The team learns that the original solution path does not work.&lt;br&gt;
The facts are incomplete and the project cannot simply be decomposed into stable pieces.&lt;/p&gt;

&lt;p&gt;Dynamic complexity needs more than control.&lt;br&gt;
It needs interaction: shared problem framing, short feedback cycles, scenario exploration, visible assumptions, alignment sessions, escalation paths, and repeated reassessment.&lt;/p&gt;

&lt;p&gt;This creates four broad management postures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low detail and low dynamic complexity: keep it simple.&lt;/li&gt;
&lt;li&gt;High detail and low dynamic complexity: use systems management and control.&lt;/li&gt;
&lt;li&gt;Low detail and high dynamic complexity: use interactive management and connecting work.&lt;/li&gt;
&lt;li&gt;High detail and high dynamic complexity: use dynamic management, combining control with interaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category is where many real projects live.&lt;br&gt;
The risk is overcorrecting in one direction.&lt;br&gt;
Too much control, and the project becomes rigid exactly when it needs to learn.&lt;br&gt;
Too much interaction, and everyone keeps talking while decisions never land.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this belongs in an AI workflow
&lt;/h2&gt;

&lt;p&gt;AI-generated project plans are often too tidy.&lt;br&gt;
They look coherent because they are formatted well, not because the underlying diagnosis is correct.&lt;/p&gt;

&lt;p&gt;That is the danger.&lt;br&gt;
A model can produce a confident plan from a weak description.&lt;br&gt;
It can turn uncertainty into bullet points so smoothly that you forget the uncertainty was still there.&lt;/p&gt;

&lt;p&gt;The TOE structure gives the model friction.&lt;br&gt;
It forces the conversation to separate the type of complexity from the action taken in response to it.&lt;br&gt;
It also makes confidence visible. A low-confidence hotspot is not ignored just because the model cannot fully prove it. If it could affect approval, trust, compliance, delivery feasibility, or project value, it deserves attention.&lt;/p&gt;

&lt;p&gt;This is the same principle behind my earlier &lt;a href="https://dev.to/articles/ai-assisted-workflow/"&gt;AI-assisted workflow&lt;/a&gt;: the AI can accelerate the production of artifacts, but the judgment remains yours.&lt;br&gt;
For project management, the artifact is not code, a PRD, or an issue list.&lt;br&gt;
It is a clearer picture of the project before the plan hardens.&lt;/p&gt;
&lt;h2&gt;
  
  
  Skill 1: &lt;code&gt;project-complexity-mapper&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first skill is diagnostic.&lt;br&gt;
It takes a rough project description, messy notes, or a partially formed idea and turns it into a structured complexity assessment.&lt;/p&gt;

&lt;p&gt;By default it runs as a fast complexity scan.&lt;br&gt;
That means it does not walk through every TOE element one by one. It reads the description, infers likely hotspots, asks only the few questions that would materially change the diagnosis, and marks confidence where evidence is thin.&lt;/p&gt;

&lt;p&gt;If the project is high-risk, expensive, politically sensitive, or already showing signs of failure, it can switch to a complete TOE assessment.&lt;br&gt;
That mode uses the full 47-element TOE checklist and scores each element from 1 to 5: none, little, some, substantial, or very much.&lt;/p&gt;

&lt;p&gt;The mapper is deliberately not an action planner.&lt;br&gt;
It produces a diagnosis, not a backlog.&lt;/p&gt;

&lt;p&gt;The output includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A summary of the assessment mode and top complexity hotspots.&lt;/li&gt;
&lt;li&gt;Findings by Technical, Organizational, and External category.&lt;/li&gt;
&lt;li&gt;Scores for TOE contribution, detail complexity, dynamic complexity, severity, impact, and confidence.&lt;/li&gt;
&lt;li&gt;An overall quadrant for the project.&lt;/li&gt;
&lt;li&gt;Separate hotspot quadrants, because one average score hides too much.&lt;/li&gt;
&lt;li&gt;A Mermaid quadrant chart to visualize where the main hotspots sit.&lt;/li&gt;
&lt;li&gt;Management-fit guidance.&lt;/li&gt;
&lt;li&gt;A handoff table that another skill can consume.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The handoff table matters.&lt;br&gt;
It makes the next step traceable.&lt;br&gt;
The action planner should not invent a generic project-management checklist. It should act on the diagnosed hotspots.&lt;/p&gt;
&lt;h2&gt;
  
  
  Skill 2: &lt;code&gt;project-complexity-action-planner&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The second skill starts where the mapper stops.&lt;br&gt;
It reads the diagnosis, especially the handoff table, management fit, hotspot quadrants, and findings by category. Then it converts those hotspots into a practical intervention plan.&lt;/p&gt;

&lt;p&gt;The important word is &lt;strong&gt;intervention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not a full project schedule.&lt;br&gt;
It is not a Gantt chart.&lt;br&gt;
It is not a ticket breakdown.&lt;/p&gt;

&lt;p&gt;An intervention is a management action chosen because a specific hotspot needs it.&lt;br&gt;
If a project has high technical detail complexity around integrations, an intervention might be an interface register, a prototype for the riskiest API, or a dependency map with clear owners.&lt;br&gt;
If the hotspot is external and dynamic, the intervention might be a stakeholder alignment session, a scenario review, or an approval map.&lt;br&gt;
If the hotspot is both detailed and dynamic, the intervention should combine control and connection, for example a requirements baseline with a controlled discovery window, or decision gates paired with assumption reviews.&lt;/p&gt;

&lt;p&gt;The action planner sequences work into three horizons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Now&lt;/strong&gt;: actions that clarify uncertainty, prevent blockage, or create needed control immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next&lt;/strong&gt;: actions that depend on first facts, decisions, or alignment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Later&lt;/strong&gt;: actions to reassess after a phase change, pilot, procurement decision, design freeze, or other major shift.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also assigns owner roles, not names, unless names are provided.&lt;br&gt;
That keeps the output useful across teams: project manager, sponsor, product owner, technical lead, integration lead, contract manager, legal or compliance lead, stakeholder lead, steering committee.&lt;/p&gt;

&lt;p&gt;The final output includes an intervention backlog, sequencing logic, decision gates, reassessment triggers, risks in the plan, and a short handoff for execution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I split it into two skills
&lt;/h2&gt;

&lt;p&gt;I could have made one skill that diagnoses complexity and creates a plan in one pass.&lt;br&gt;
That would have been faster.&lt;br&gt;
It would also have been worse.&lt;/p&gt;

&lt;p&gt;Diagnosis and action are different mental moves.&lt;br&gt;
When they happen at the same time, the model tends to rush toward advice.&lt;br&gt;
The format becomes a polished recommendation before the project has been understood.&lt;/p&gt;

&lt;p&gt;Splitting the workflow keeps the first skill honest.&lt;br&gt;
Its job is to say, "Here is where complexity appears to be coming from, here is how severe it looks, here is how confident we are, and here is the management posture that seems to fit."&lt;/p&gt;

&lt;p&gt;Only after that does the second skill ask, "Given that diagnosis, what should we do first?"&lt;/p&gt;

&lt;p&gt;That separation mirrors how I want to work with AI in general.&lt;br&gt;
Do not let the model skip the thinking artifact.&lt;br&gt;
Make the thinking artifact reviewable.&lt;br&gt;
Then use it as input to the next step.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I would use it
&lt;/h2&gt;

&lt;p&gt;I would start with a project description written in normal language.&lt;br&gt;
No template.&lt;br&gt;
No forced structure.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We need to migrate a legacy reporting system into a new platform.
The old system is used by three departments, each with different reporting definitions.
The source data comes from two internal APIs and one external vendor.
The deadline is connected to a contract renewal, but the scope is still moving.
The engineering team understands the new platform, but nobody fully owns the old reporting logic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I would run the mapper and read the output critically.&lt;br&gt;
The important questions are not "Do I like this report?" but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it classify the real hotspots?&lt;/li&gt;
&lt;li&gt;Did it confuse technical complexity with organizational complexity?&lt;/li&gt;
&lt;li&gt;Did it miss an external dependency?&lt;/li&gt;
&lt;li&gt;Are the low-confidence findings actually important?&lt;/li&gt;
&lt;li&gt;Are we averaging away a hotspot that needs separate attention?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the diagnosis looks useful, I would pass the handoff into the action planner in a new session with clear context window.&lt;br&gt;
Then I would review the intervention backlog with the same skepticism.&lt;/p&gt;

&lt;p&gt;Every action should trace back to a hotspot.&lt;br&gt;
If an action does not trace back to a hotspot, it is probably generic advice.&lt;br&gt;
If a high-severity hotspot has no action, the plan is probably incomplete.&lt;br&gt;
If a dynamic hotspot only gets control artifacts, the plan is probably too rigid.&lt;br&gt;
If a high-detail hotspot only gets workshops, the plan is probably too vague.&lt;/p&gt;

&lt;p&gt;The output should become a conversation artifact for the team, not a decree from the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this is useful
&lt;/h2&gt;

&lt;p&gt;I see this being useful before a project execution plan, before a large PRD, before a replatforming effort, before a vendor integration, before a compliance-heavy feature, or when a project already feels stuck but the reason is not obvious.&lt;/p&gt;

&lt;p&gt;It is especially useful when people disagree about what the problem is.&lt;br&gt;
One person says the project is technically hard.&lt;br&gt;
Another says the scope keeps changing.&lt;br&gt;
Another says the real blocker is approvals.&lt;br&gt;
Another says the team does not have the right people.&lt;/p&gt;

&lt;p&gt;All of those can be true.&lt;br&gt;
The TOE framework gives you a way to hold those truths separately instead of collapsing them into one vague complaint.&lt;/p&gt;

&lt;p&gt;It also helps with AI-assisted development because it sits upstream of implementation.&lt;br&gt;
If the project is organizationally unstable, breaking work into better tickets will not fix it.&lt;br&gt;
If the external context is volatile, a perfect architecture diagram will not make the decision process predictable.&lt;br&gt;
If the technical interfaces are unclear, more stakeholder alignment will not define the API contract by itself.&lt;/p&gt;

&lt;p&gt;Different complexity asks for different management behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I would not use it
&lt;/h2&gt;

&lt;p&gt;I would not use this for every task.&lt;/p&gt;

&lt;p&gt;If the work is small, local, and well understood, this is overhead.&lt;br&gt;
If one person can explain the scope, implement it, test it, and ship it without meaningful dependencies, a TOE scan is probably unnecessary.&lt;/p&gt;

&lt;p&gt;I also would not use it to justify a decision that has already been made.&lt;br&gt;
That is one of the easiest ways to misuse AI: ask for a framework, feed it selective context, and receive a professional-looking confirmation.&lt;/p&gt;

&lt;p&gt;And I would be careful using it without the people who actually understand the project.&lt;br&gt;
Complexity is subjective and dynamic.&lt;br&gt;
Different actors see different risks because they sit in different parts of the system.&lt;br&gt;
The skill can structure the conversation, but it cannot replace the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human part
&lt;/h2&gt;

&lt;p&gt;The most valuable output of these skills is not the table.&lt;br&gt;
It is the disagreement the table makes visible.&lt;/p&gt;

&lt;p&gt;If a stakeholder says a hotspot is scored too high, good.&lt;br&gt;
Ask why.&lt;br&gt;
If the technical lead says the integration risk is understated, good.&lt;br&gt;
Update the diagnosis.&lt;br&gt;
If the sponsor thinks the external pressure is a value opportunity rather than only a risk, good.&lt;br&gt;
That belongs in the management fit.&lt;/p&gt;

&lt;p&gt;The goal is not to make project complexity disappear.&lt;br&gt;
Some complexity cannot be removed.&lt;br&gt;
Some complexity should not be removed, because it is tied to project value.&lt;/p&gt;

&lt;p&gt;The goal is to stop managing every project with the same reflex.&lt;/p&gt;

&lt;p&gt;Control where the project needs control.&lt;br&gt;
Connect where the project needs interaction.&lt;br&gt;
Reassess when the context changes.&lt;br&gt;
And keep the judgment with the humans who are accountable for the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skills
&lt;/h2&gt;

&lt;p&gt;The two skills are available in my &lt;a href="https://github.com/maiobarbero/my-ai-workflow" rel="noopener noreferrer"&gt;AI workflow repository&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/maiobarbero/my-ai-workflow/tree/main/skills/project-complexity-mapper" rel="noopener noreferrer"&gt;&lt;code&gt;project-complexity-mapper&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/maiobarbero/my-ai-workflow/tree/main/skills/project-complexity-action-planner" rel="noopener noreferrer"&gt;&lt;code&gt;project-complexity-action-planner&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not meant to sell a methodology.&lt;br&gt;
They are my attempt to encode a useful way of thinking so I can reuse it, challenge it, and improve it over time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>management</category>
      <category>productivity</category>
      <category>claude</category>
    </item>
    <item>
      <title>My AI-Assisted workflow</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Wed, 15 Apr 2026 07:17:46 +0000</pubDate>
      <link>https://dev.to/maiobarbero/my-ai-assisted-workflow-20ke</link>
      <guid>https://dev.to/maiobarbero/my-ai-assisted-workflow-20ke</guid>
      <description>&lt;p&gt;You open a chat, describe what you want, iterate on the output, and ship something that more or less works. It feels fast.&lt;br&gt;
The features work, technically. But nobody, &lt;strong&gt;including me&lt;/strong&gt;, fully understood what is there. Edge cases nobody thought to handle, architecture that made sense in the moment but didn't survive contact with the next feature. A growing sense that I was building faster and understanding less.&lt;/p&gt;

&lt;p&gt;The problem I kept running into was this: how do you get the speed benefits of AI assistance without losing the clarity and intentionality that makes software maintainable?&lt;br&gt;
The short answer is that the real work happens before the coding starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: thinking in writing, not in code
&lt;/h2&gt;

&lt;p&gt;What is AI actually good at? Implementation. What is it genuinely bad at? Figuring out what you actually want, catching the assumptions you forgot to make explicit, and telling you when your mental model of the problem is wrong.&lt;br&gt;
&lt;strong&gt;That's your job. It will always be your job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The single most valuable shift I made was treating every feature as a thinking problem first and an implementation problem second. The workflow is designed to force that thinking to happen before any code is written, and to use AI to stress-test it, not to skip it.&lt;/p&gt;

&lt;p&gt;This workflow has been adapted to work with me from Mark Pocock's &lt;a href="https://github.com/mattpocock/skills/tree/main" rel="noopener noreferrer"&gt;skills&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Free-form plan
&lt;/h3&gt;

&lt;p&gt;Everything starts with a document I write myself, in plain language, with no required structure. I describe the problem, my initial thinking about the solution, the constraints I'm aware of, and the things I'm uncertain about. This is not a deliverable, nobody reads it but me. Its only purpose is to get the thinking out of my head and into a form I can examine.&lt;/p&gt;

&lt;p&gt;The quality of everything downstream depends entirely on the quality of this step. A vague plan produces a vague &lt;strong&gt;&lt;a href="https://www.maiobarbero.dev/glossary/#prd" rel="noopener noreferrer"&gt;PRD&lt;/a&gt;&lt;/strong&gt;, which produces vague issues, which produces code that technically runs but doesn't do what you meant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: PRD via &lt;code&gt;write-a-prd&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The free-form plan becomes the input to a structured interview process. The skill explores the codebase to understand the current state of things, then interviews me relentlessly about every aspect of the plan, walking down each branch of the design tree, resolving dependencies between decisions one by one.&lt;br&gt;
This is the step where bad ideas get caught. Not because the AI is smarter than me, but because being forced to answer specific questions about your own plan reveals the places where you were hand-waving. "How does this behave when the user isn't authenticated?" "What happens if this operation partially fails?" "You said this replaces the existing feature, what happens to users who depend on the current behavior?"&lt;/p&gt;

&lt;p&gt;The output is a structured &lt;strong&gt;PRD&lt;/strong&gt; file containing a problem statement, a solution description, an extensive list of user stories, implementation decisions (modules, interfaces, schema changes, API contracts), module design, testing decisions, and explicit out-of-scope items. Everything is &lt;strong&gt;explicit&lt;/strong&gt;.&lt;br&gt;
The user stories are the backbone of everything that follows. They need to be specific enough that acceptance criteria can be derived from them unambiguously downstream, not at this stage, but when scope is defined at the issue level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Issues via &lt;code&gt;prd-to-issues&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;PRD&lt;/strong&gt; becomes a set of issues using &lt;strong&gt;&lt;a href="https://www.maiobarbero.dev//glossary/#vertical-slice" rel="noopener noreferrer"&gt;vertical slices&lt;/a&gt;&lt;/strong&gt;, tracer bullets that cut through every integration layer end-to-end rather than horizontal slices of a single layer. A slice that only touches the database, or only touches the UI, is not a valid slice. Each issue should deliver a narrow but complete path that is demoable or verifiable on its own.&lt;br&gt;
Each issue is classified as either &lt;strong&gt;AFK&lt;/strong&gt; (the AI can implement and the change can be merged without human interaction) or &lt;strong&gt;&lt;a href="https://www.maiobarbero.dev//glossary/#hitl" rel="noopener noreferrer"&gt;HITL&lt;/a&gt;&lt;/strong&gt; (a human decision is required at some point during implementation). Preferring AFK over HITL wherever possible keeps the work moving without becoming a bottleneck on my attention.&lt;/p&gt;

&lt;p&gt;Before anything is written, the skill presents the proposed breakdown and asks: does the granularity feel right, are the dependency relationships correct, should anything be merged or split? Issues are written in dependency order so that cross-references between them use real numbers.&lt;/p&gt;

&lt;p&gt;Each issue contains a concise description of the end-to-end behaviour, a "how to verify" section describing exactly how to confirm the slice is complete, acceptance criteria in Given/When/Then format including error cases, a list of blockers, and references back to the user stories it addresses.&lt;br&gt;
Everything lives in files. I work across different platforms, sometimes GitHub, sometimes GitLab, and keeping the workflow file-based means it doesn't depend on any particular tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Tasks via &lt;code&gt;issues-to-tasks&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Each issue is broken down into concrete, ordered tasks, one task per focused AI session. The constraint is deliberate: if a task can't be completed in a single session, it's too large.&lt;br&gt;
The skill explores the specific parts of the codebase touched by the issue, identifies existing patterns to follow, and produces a task list with types (WRITE, TEST, MIGRATE, CONFIG, REVIEW), explicit outputs, and dependency order. Schema before logic, logic before API, API before UI, tests interleaved rather than batched at the end.&lt;/p&gt;

&lt;p&gt;The key design decision in the task descriptions: they are written as instructions to the AI that will execute them, not as notes to a human developer. Each task specifies which files to touch, which existing patterns to follow, and what the output looks like when done. No code snippets, just intent, not implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: The handoff to code
&lt;/h3&gt;

&lt;p&gt;Each task description is a self-contained prompt. When I'm ready to implement a task, I open a fresh session and paste the task description along with the parent issue for context. The task description was written for this purpose, it specifies scope, references the right files and patterns, and defines what done looks like.&lt;br&gt;
Fresh context per task is intentional. Long sessions with accumulated context tend to drift: the model starts making decisions based on what it already did rather than what the task requires. Starting clean with a well-scoped task consistently produces better output than continuing a long session.&lt;br&gt;
For REVIEW tasks, the ones flagged as requiring a human decision, I stop, make the decision, update the task file with the outcome, and continue. These are the moments where the workflow earns its keep: the decision is made deliberately, in context, not buried in a long generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Code review via &lt;code&gt;code-review&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Every PR goes through a structured six-pass review before merge. The passes cover logic errors, operation ordering, bad practices, security, magic strings and values, and pattern improvements.&lt;/p&gt;

&lt;p&gt;Operation ordering deserves particular attention in AI-generated code. Models tend to produce code that does the right things but sometimes in the wrong sequence: sending a notification before committing a transaction, writing an audit log after the action it should record, mutating state before validating input. These bugs are easy to miss in review because the code looks correct at a glance.&lt;/p&gt;

&lt;p&gt;The review runs on a file or diff, not on the whole feature. Scope is deliberately narrow, catching issues at the PR level is far cheaper than finding them later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Final audit via &lt;code&gt;final-audit&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;At the end of a feature, a cross-cutting audit looks at things that can only be evaluated across the whole implementation. Not individual bugs, those should have been caught per PR, but systemic issues: inconsistencies between modules, patterns that were introduced early and replicated incorrectly everywhere, security assumptions that hold in isolation but break down across the full surface area.&lt;/p&gt;

&lt;p&gt;The audit reads the full implementation before flagging anything, which is the point. It groups findings by severity, gives an explicit overall verdict on whether the feature is safe to leave in production, and asks for approval before making any changes. Unsupervised fixes on already-merged code are riskier than fixes made during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this workflow is not
&lt;/h2&gt;

&lt;p&gt;It is not fast to set up. The planning and &lt;strong&gt;PRD&lt;/strong&gt; steps take real time, and the temptation to skip them in favour of going straight to code is constant. The workflow only pays off if you genuinely believe that thinking time before coding is cheaper than debugging time after.&lt;/p&gt;

&lt;p&gt;It is also not a replacement for engineering judgement. The AI will suggest reasonable things at every step that are wrong for your specific situation. The review steps, where you evaluate the breakdown before anything is created, exist precisely because the AI's output needs to be validated against knowledge it doesn't have: your team's conventions, your users' actual behavior, the parts of the codebase that have hidden complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underlying principle
&lt;/h2&gt;

&lt;p&gt;Every step in this workflow has the same structure: AI produces something, you review it with full context, then it gets created. The AI accelerates the production. &lt;strong&gt;The review is yours, always&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The workflow is designed to make that review as effective as possible, by ensuring that when you're evaluating an issue, you have a &lt;strong&gt;PRD&lt;/strong&gt; to check it against; when you're evaluating a task, you have an issue to check it against; and when you're reviewing code, you have acceptance criteria to check it against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skills
&lt;/h2&gt;

&lt;p&gt;Now if you read this article to the end you at least deserve a link with my skills.&lt;br&gt;
Check my &lt;a href="https://github.com/maiobarbero/my-ai-workflow" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>programming</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>How to test Filament resources | Laravel Personal Finance Dashboard</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Thu, 19 Feb 2026 09:41:59 +0000</pubDate>
      <link>https://dev.to/maiobarbero/how-to-test-filament-resources-laravel-personal-finance-dashboard-eei</link>
      <guid>https://dev.to/maiobarbero/how-to-test-filament-resources-laravel-personal-finance-dashboard-eei</guid>
      <description>&lt;p&gt;Now that we have our resources set up, it's time to ensure they work as expected—and stay that way. In this lesson, we're going to write feature tests for our &lt;code&gt;BankAccount&lt;/code&gt;, &lt;code&gt;Budget&lt;/code&gt;, and &lt;code&gt;Category&lt;/code&gt; resources. We'll focus particularly on ensuring that users can only see &lt;em&gt;their own&lt;/em&gt; data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Pest
&lt;/h2&gt;

&lt;p&gt;First, we need to add &lt;strong&gt;Pest&lt;/strong&gt; to our project. Pest is a testing framework with a focus on simplicity and elegance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require pestphp/pest &lt;span class="nt"&gt;--dev&lt;/span&gt; &lt;span class="nt"&gt;--with-all-dependencies&lt;/span&gt;
php artisan pest:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to make sure our &lt;code&gt;User&lt;/code&gt; model is ready for Filament testing. To properly simulate a Filament user in our tests, our &lt;code&gt;User&lt;/code&gt; model should implement the &lt;code&gt;FilamentUser&lt;/code&gt; contract. This ensures that when we use &lt;code&gt;actingAs&lt;/code&gt;, the user is correctly recognized as having access to the panel.&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;app/Models/User.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Models\Contracts\FilamentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Authenticatable&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;FilamentUser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;canAccessPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Filament\Panel&lt;/span&gt; &lt;span class="nv"&gt;$panel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view the setup and model changes in &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/b7df8af" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Bank Account Resource
&lt;/h2&gt;

&lt;p&gt;Let's start with the &lt;code&gt;BankAccountResource&lt;/code&gt;. We want to verify that we can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render the page.&lt;/li&gt;
&lt;li&gt;List bank accounts belonging to the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NOT&lt;/strong&gt; list bank accounts belonging to other users.&lt;/li&gt;
&lt;li&gt;Create, edit, and delete accounts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a new test file: &lt;code&gt;tests/Feature/Filament/Resources/BankAccountResourceTest.php&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering and Listing
&lt;/h3&gt;

&lt;p&gt;We use &lt;code&gt;actingAs($user)&lt;/code&gt; to sign in as a user, and then access the page URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Filament\Resources\BankAccounts\Pages\ManageBankAccounts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\BankAccount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Livewire\Livewire&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Pest\Laravel\actingAs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can render page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ManageBankAccounts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSuccessful&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can list bank accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$bankAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BankAccount&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Livewire&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ManageBankAccounts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertCanSeeTableRecords&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$bankAccount&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Scope (The "Other User" Test)
&lt;/h3&gt;

&lt;p&gt;This is a critical test. We create a user and &lt;em&gt;another&lt;/em&gt; user. We expect the current user effectively &lt;em&gt;not&lt;/em&gt; to see the other user's record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cannot see other users bank accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$otherUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$otherBankAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BankAccount&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$otherUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Livewire&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ManageBankAccounts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertCanNotSeeTableRecords&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$otherBankAccount&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Actions: Create, Edit, Delete
&lt;/h3&gt;

&lt;p&gt;Filament provides excellent helpers to test actions directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can create bank account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Livewire&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ManageBankAccounts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mountAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'create'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setActionData&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'My Main Bank'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'balance'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'1000.50'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;callMountedAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertHasNoActionErrors&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bank_accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'My Main Bank'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'balance'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100050&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Value as integer thanks to our Caster&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we assert the database has the value &lt;code&gt;100050&lt;/code&gt; because of our logic handling money as integers!&lt;/p&gt;

&lt;p&gt;You can see the full test suite for the Bank Account resource in &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/26ca59e" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget and Category Resources
&lt;/h2&gt;

&lt;p&gt;The tests for &lt;code&gt;BudgetResource&lt;/code&gt; and &lt;code&gt;CategoryResource&lt;/code&gt; follow the exact same pattern. We need to ensure that detailed actions like creating and editing work, but most importantly, we must verify the scoping rules.&lt;/p&gt;

&lt;p&gt;For example, the Category tests ensure that a user can only edit their own categories.&lt;/p&gt;

&lt;p&gt;You can check the implementation for the &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/9f3d821" rel="noopener noreferrer"&gt;Budget resource tests here&lt;/a&gt; and the &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/fb1ac7a" rel="noopener noreferrer"&gt;Category resource tests here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Tests
&lt;/h2&gt;

&lt;p&gt;Finally, let's run our test suite to see everything green!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an output confirming that all resource tests are passing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASS Tests\Feature\Filament\Resources\BankAccountResourceTest
✓ it can render page
✓ it can list bank accounts
✓ it cannot see other users bank accounts
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎓 Build this App &amp;amp; Get Certified (Free)
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful, you can build the entire application from scratch with my free course. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏗️ Advanced Laravel Architecture&lt;/li&gt;
&lt;li&gt;📊 Building Dashboards with FilamentPHP&lt;/li&gt;
&lt;li&gt;💰 Handling Money professionally in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;→ Join other students on maiobarbero.dev&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to build a Voice-to-Text App with Laravel AI SDK</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Wed, 11 Feb 2026 08:39:27 +0000</pubDate>
      <link>https://dev.to/maiobarbero/how-to-build-a-voice-to-text-app-with-laravel-ai-sdk-1ida</link>
      <guid>https://dev.to/maiobarbero/how-to-build-a-voice-to-text-app-with-laravel-ai-sdk-1ida</guid>
      <description>&lt;h1&gt;
  
  
  Stop Writing Boilerplate: Build a Voice-to-Text App with Laravel AI SDK
&lt;/h1&gt;

&lt;p&gt;If you are a PHP developer, you have likely looked at AI integration with a mix of excitement and fatigue. Excitement for the possibilities, but fatigue from WRITING THE SAME BOILERPLATE code to handle multipart requests, API keys, and error handling for OpenAI or Anthropic.&lt;/p&gt;

&lt;p&gt;The new &lt;strong&gt;Laravel AI SDK&lt;/strong&gt; changes this. It turns complex AI interactions into fluid, Laravel-style syntax.&lt;/p&gt;

&lt;p&gt;In this guide, we won't just talk about it—we will build a functional &lt;strong&gt;Voice-to-Text&lt;/strong&gt; application in under 10 minutes. I call mine "Dettami", but you can call yours whatever you want.&lt;/p&gt;

&lt;p&gt;Let's write some code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Laravel 10+ (or the latest version)&lt;/li&gt;
&lt;li&gt;PHP 8.2+&lt;/li&gt;
&lt;li&gt;An OpenAI API Key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install the SDK
&lt;/h2&gt;

&lt;p&gt;First, forget about &lt;code&gt;guzzlehttp/guzzle&lt;/code&gt; manual calls. Pull in the first-party package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, publish the configuration file. This is where you will define your "drivers" (OpenAI, Gemini, Mistral, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan &lt;span class="nb"&gt;install&lt;/span&gt;:ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your key to your &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Define the Route
&lt;/h2&gt;

&lt;p&gt;We need a single endpoint to accept the audio file. In your &lt;code&gt;routes/web.php&lt;/code&gt; or &lt;code&gt;routes/api.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Http\Controllers\TranscribeAudioController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/transcribe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TranscribeAudioController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'transcribe'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: The Logic (Where the Magic Happens)
&lt;/h2&gt;

&lt;p&gt;This is the part that usually hurts. Handling file uploads + external API calls is often messy.&lt;/p&gt;

&lt;p&gt;Create your controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:controller TranscribeAudioController &lt;span class="nt"&gt;--invokable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, look at how clean this solution is using the &lt;code&gt;Transcription&lt;/code&gt; facade. We don't need to manually construct a POST request to &lt;code&gt;https://api.openai.com/v1/audio/transcriptions&lt;/code&gt;. The SDK does it for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\AI\Facades\Transcription&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TranscribeAudioController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'audio'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|file|mimes:webm,mp3,wav'&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'audio'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Save the file temporarily&lt;/span&gt;
        &lt;span class="c1"&gt;// We need a physical path to pass to the SDK&lt;/span&gt;
        &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'temp_recordings'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 2. Transcribe with one fluent method chain&lt;/span&gt;
            &lt;span class="c1"&gt;// The SDK automatically uses your configured default driver&lt;/span&gt;
            &lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Transcription&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fullPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMimeType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// 3. Cleanup&lt;/span&gt;
            &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Log the error and return a user-friendly message&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Transcription failed'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;Notice line 27: &lt;code&gt;Transcription::fromPath(...)&lt;/code&gt;. &lt;br&gt;
That is the abstraction we have been waiting for. It removes the cognitive load of remembering specific API parameters for every different provider. Today you use OpenAI, tomorrow you switch to Gemini—the code stays strictly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: The Frontend (The "Recorder")
&lt;/h2&gt;

&lt;p&gt;For the frontend, you don't need a heavy React app. A simple Blade view with vanilla JS covers it perfectly.&lt;/p&gt;

&lt;p&gt;The core requirement is the browser's &lt;code&gt;MediaRecorder&lt;/code&gt; API. Here is the pseudo-code for what your JavaScript needs to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request Microphone access (&lt;code&gt;navigator.mediaDevices.getUserMedia&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Record chunks of audio into a &lt;code&gt;Blob&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Send that Blob to our Laravel endpoint via &lt;code&gt;FormData&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a glimpse of the UI I built for this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3r22zuh95pfwlgdy152.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3r22zuh95pfwlgdy152.png" alt="Dettami Interface" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We just built a fully functional Voice-to-Text backend in &lt;strong&gt;about 20 lines of code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Laravel AI SDK respects your time. It handles the "plumbing" so you can focus on building the actual product.&lt;/p&gt;

&lt;p&gt;If you want to inspect the full source code for the demo project "Dettami", check the repository here:&lt;br&gt;
&lt;a href="https://github.com/maiobarbero/dettami" rel="noopener noreferrer"&gt;GitHub: maiobarbero/dettami&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep moving forward.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to create a resource in Filament v5 | Laravel Personal Finance Dashboard</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Tue, 10 Feb 2026 07:15:52 +0000</pubDate>
      <link>https://dev.to/maiobarbero/laravel-personal-finance-dashboard-add-filament-v5-resources-37lh</link>
      <guid>https://dev.to/maiobarbero/laravel-personal-finance-dashboard-add-filament-v5-resources-37lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;👋 Note:&lt;/strong&gt; This article is an excerpt from my &lt;strong&gt;Free Laravel &amp;amp; Filament Finance Course&lt;/strong&gt;.&lt;br&gt;
The full course includes code-along lessons, quizzes, and a &lt;strong&gt;Completion Certificate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;Start the Free Course &amp;amp; Get Certified&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  The First Filament Admin Panel
&lt;/h1&gt;

&lt;p&gt;We have our database structure. It's solid, typed, and waiting for data. But a database without an interface is like a library without doors, secure, but useless.&lt;/p&gt;

&lt;p&gt;Today, we build the doors.&lt;/p&gt;

&lt;p&gt;We are going to create our first &lt;strong&gt;Filament Resources&lt;/strong&gt;. In Filament, a "Resource" is the static representation of an entity in your admin panel. It describes how your model looks in a table, how it behaves in a form, and how it relates to the rest of your application.&lt;/p&gt;

&lt;p&gt;We will start with something fundamental: &lt;strong&gt;Categories&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Creating the Category Resource
&lt;/h2&gt;

&lt;p&gt;Filament provides a powerful artisan command to generate resources. We want our categories to be manageable quickly, without navigating away from the list. We want a "Simple Resource", one that handles Creating, Reading, Updating, and Deleting (CRUD) all on a single page using modals.&lt;/p&gt;

&lt;p&gt;Run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-resource Category &lt;span class="nt"&gt;--simple&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates two key files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;app/Filament/Resources/CategoryResource.php&lt;/code&gt;: The definition of the table and form.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/Filament/Resources/CategoryResource/Pages/ManageCategories.php&lt;/code&gt;: The page controller that handles the actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can see the initial structure in &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/4935d64" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you visit &lt;code&gt;/admin/categories&lt;/code&gt; now, you might see a form with a &lt;code&gt;User&lt;/code&gt; dropdown. This is technically correct, a category belongs to a user, but practically wrong. When I create a category, it should be &lt;em&gt;mine&lt;/em&gt;. I shouldn't have to select myself from a list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnp3zz98yxvis0n9hpl8b.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnp3zz98yxvis0n9hpl8b.webp" alt="Category Resource Initial" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Scoping and Context
&lt;/h2&gt;

&lt;p&gt;We have two problems to solve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Visibility&lt;/strong&gt;: I can see categories belonging to other users.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Creation&lt;/strong&gt;: I have to manually select the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Fixing Visibility
&lt;/h3&gt;

&lt;p&gt;To ensure I only see &lt;em&gt;my&lt;/em&gt; records, we strictly scope the Eloquent query used by the resource. We override the &lt;code&gt;getEloquentQuery&lt;/code&gt; method in &lt;code&gt;CategoryResource.php&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getEloquentQuery&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Builder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getEloquentQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the table will only ever load records where &lt;code&gt;user_id&lt;/code&gt; matches the logged-in user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing Creation
&lt;/h3&gt;

&lt;p&gt;Next, we clean up the form. In &lt;code&gt;CategoryResource::form()&lt;/code&gt;, remove the &lt;code&gt;user_id&lt;/code&gt; input completely. We don't want the user to worry about it.&lt;/p&gt;

&lt;p&gt;But the database &lt;em&gt;needs&lt;/em&gt; that ID. So, we inject it programmatically.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;app/Filament/Resources/CategoryResource/Pages/ManageCategories.php&lt;/code&gt;, we modify the &lt;code&gt;CreateAction&lt;/code&gt;. We use &lt;code&gt;mutateFormDataUsing&lt;/code&gt; to intercept the form submission and add the current user's ID before it hits the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getHeaderActions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;CreateAction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mutateDataUsing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;--- This implies the user&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also remove the &lt;code&gt;user&lt;/code&gt; column from the table to keep it clean.&lt;/p&gt;

&lt;p&gt;You can view the specific changes to scope the data in &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/481115e" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrt408gfsxba4fcfjls0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrt408gfsxba4fcfjls0.webp" alt="Category Resource Scoped" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Bank Accounts
&lt;/h2&gt;

&lt;p&gt;Money needs a home. Let's apply the exact same logic to &lt;strong&gt;Bank Accounts&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Generate the resource: &lt;code&gt;php artisan make:filament-resource BankAccount --simple&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Scope the query in &lt;code&gt;BankAccountResource&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Inject the &lt;code&gt;user_id&lt;/code&gt; in &lt;code&gt;ManageBankAccounts&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you check your database or use artisan tinker, you'll notice our &lt;code&gt;MoneyCast&lt;/code&gt; is working silently in the background. When you type "1000" in the balance field, it's stored as &lt;code&gt;100000&lt;/code&gt; (cents) in the database, assuming you treated the input as a distinct number.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wait, there's a nuance here.&lt;/em&gt; Our form is currently generic. Later, we'll want to ensure we are handling decimals correctly in the UI so the user types "10.00" and we store "1000". For now, we are trusting the raw input.&lt;/p&gt;

&lt;p&gt;Check the implementation of the Bank Account resource &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/388d79e" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Budgets and Presentation
&lt;/h2&gt;

&lt;p&gt;Finally, let's tackle &lt;strong&gt;Budgets&lt;/strong&gt;. This is where Filament's UI components really shine.&lt;/p&gt;

&lt;p&gt;Generate the resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-resource Budget &lt;span class="nt"&gt;--simple&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Budgets have a &lt;code&gt;type&lt;/code&gt; (Reset or Rollover). In our database, this is an Enum string. In Filament, we want to visualize this instantly using colors.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Badge Column
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;BudgetResource.php&lt;/code&gt;, we can use a &lt;code&gt;TextColumn&lt;/code&gt; but format it as a &lt;code&gt;badge&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;BudgetType&lt;/span&gt; &lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;BudgetType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Reset&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;BudgetType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Rollover&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'warning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;searchable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple chaining transforms a boring text string into a vibrant, communicative UI element.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1zvib5f9i6kqha3s2rm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1zvib5f9i6kqha3s2rm.webp" alt="Budget Resource Badge" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatting Money
&lt;/h3&gt;

&lt;p&gt;We also want to display the budget amount as actual currency, not just a raw number. Filament has a dedicated &lt;code&gt;money&lt;/code&gt; formatter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;money&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'EUR'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a polished, professional look immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs7xetjfmf173uy3jv2e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs7xetjfmf173uy3jv2e.webp" alt="Budget Resource Money" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can explore the Budget resource implementation in &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/8c43685" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;, and see how we refined the badge colors &lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/b9a292c" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We now have three functional pillars of our personal finance application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Categories&lt;/strong&gt; to label our spending.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Bank Accounts&lt;/strong&gt; to store our wealth.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Budgets&lt;/strong&gt; to control our impulses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And importantly, they are completely private to the logged-in user.&lt;/p&gt;

&lt;p&gt;In the next lesson, we will test our first Resources. Keep building.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Build this App &amp;amp; Get Certified (Free)
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful, you can build the entire application from scratch with my free course. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏗️ Advanced Laravel Architecture&lt;/li&gt;
&lt;li&gt;📊 Building Dashboards with FilamentPHP&lt;/li&gt;
&lt;li&gt;💰 Handling Money professionally in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;→ Join other students on maiobarbero.dev&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Testing the New Laravel AI SDK: Building 'Dettami' a voice-to-text app</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Sat, 07 Feb 2026 13:46:06 +0000</pubDate>
      <link>https://dev.to/maiobarbero/testing-the-new-laravel-ai-sdk-building-dettami-a-voice-to-text-app-8mf</link>
      <guid>https://dev.to/maiobarbero/testing-the-new-laravel-ai-sdk-building-dettami-a-voice-to-text-app-8mf</guid>
      <description>&lt;p&gt;The landscape of AI integration in PHP has just shifted. With the release of the &lt;strong&gt;Laravel AI SDK&lt;/strong&gt;, what used to require complex API wrangling or third-party packages is now a first-party citizen of the framework we love.&lt;/p&gt;

&lt;p&gt;I couldn't resist. I had to take it for a spin.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;Dettami&lt;/strong&gt; (Italian for "Dictate to me"), a simple web app that records your voice and transcribes it instantly. And when I say "simple," I mean it. The speed at which I went from &lt;code&gt;laravel new&lt;/code&gt; to a working prototype was exhilarating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal: Frictionless Audio Transcription
&lt;/h2&gt;

&lt;p&gt;I wanted to build something tangible. Not just a "Hello World" for AI, but a feature I would actually use: a quick way to record thoughts and get them as text.&lt;/p&gt;

&lt;p&gt;Here is what the interface looks like. Clean, focused, and ready to listen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub618yiz7u605sdwox0s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub618yiz7u605sdwox0s.webp" alt="Dettami Homepage" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;Installing the SDK was the first step, and as you'd expect from the Laravel ecosystem, it was seamless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the real magic happened in the controller. In the past, handling audio uploads, converting formats, and sending them to an API like OpenAI's Whisper or Google's Speech-to-Text involved a fair bit of boilerplate. You had to manage the file, ensure the format was correct, build the multipart request, handle the response...&lt;/p&gt;

&lt;p&gt;With the Laravel AI SDK, the complexity vanishes. It abstracts the "how" and lets you focus on the "what."&lt;/p&gt;

&lt;p&gt;Here is the core logic from my &lt;code&gt;Dettami&lt;/code&gt; controller. Look at how expressive it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'audio'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'audio'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Store the file temporarily&lt;/span&gt;
        &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;storeAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dettami'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'recording.webm'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// The magic happens here. One fluents line.&lt;/span&gt;
            &lt;span class="nv"&gt;$transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Transcription&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fullPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'audio/webm'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Clean up&lt;/span&gt;
            &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'transcription'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$transcription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... error handling&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;Transcription::fromPath(...) -&amp;gt; generate()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The SDK handles the heavy lifting of communicating with the configured AI provider. It feels native. It feels right.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Frontend Experience
&lt;/h3&gt;

&lt;p&gt;For the frontend, I kept it simple using Blade and vanilla JavaScript with the &lt;code&gt;MediaRecorder&lt;/code&gt; API. The goal was to provide immediate feedback. You click record, you speak, you get text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdo3zp99o2dnenmpjt276.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdo3zp99o2dnenmpjt276.webp" alt="Recording in progress" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Transcription: Translation
&lt;/h2&gt;

&lt;p&gt;The power of this SDK doesn't stop at transcription. Once you have the text, the possibilities are endless. You can summarize it, rewrite it, or—as I experimented with—translate it.&lt;/p&gt;

&lt;p&gt;Because the SDK provides a unified interface for these operations, adding a translation step is just as trivial as the transcription itself. Whether you are using OpenAI, Anthropic, or Gemini, the code remains consistent and clean.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztk5ixkwd0lzb58dev60.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztk5ixkwd0lzb58dev60.webp" alt="Translated Output" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflections
&lt;/h2&gt;

&lt;p&gt;Building &lt;strong&gt;Dettami&lt;/strong&gt; was a reminder of why I love this framework. It respects my time. The &lt;strong&gt;Laravel AI SDK&lt;/strong&gt; isn't just a wrapper; it's an opinionated, elegant way to bring intelligence into your applications without the headache.&lt;/p&gt;

&lt;p&gt;For architects and developers, this means we can stop worrying about &lt;em&gt;connecting&lt;/em&gt; to AI and start worrying about &lt;em&gt;what we can build&lt;/em&gt; with it. Ideally, the barrier to entry has been lowered to the floor.&lt;/p&gt;

&lt;p&gt;If you haven't tried it yet, fire up a terminal.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;composer require laravel/ai&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And build something amazing. You can find the full source code for Dettami on &lt;a href="https://github.com/maiobarbero/dettami" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. I recommend starting with &lt;a href="https://github.com/maiobarbero/dettami/commit/33f3380" rel="noopener noreferrer"&gt;this commit&lt;/a&gt; to see the transcription logic in isolation.&lt;/p&gt;

&lt;p&gt;Keep moving forward.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>ai</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Laravel Personal Finance Dashboard: creating the database</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Thu, 05 Feb 2026 07:59:55 +0000</pubDate>
      <link>https://dev.to/maiobarbero/laravel-personal-finance-dashboard-creating-the-database-644</link>
      <guid>https://dev.to/maiobarbero/laravel-personal-finance-dashboard-creating-the-database-644</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;👋 Note:&lt;/strong&gt; This article is an excerpt from my &lt;strong&gt;Free Laravel &amp;amp; Filament Finance Course&lt;/strong&gt;.&lt;br&gt;
The full course includes code-along lessons, quizzes, and a &lt;strong&gt;Completion Certificate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;Start the Free Course &amp;amp; Get Certified&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Creating the Database
&lt;/h1&gt;

&lt;p&gt;Every great application stands on the shoulders of a solid data model. If the database schema isn't right, everything else, from your eloquently written controllers to your beautiful Filament resources, will feel like fighting gravity.&lt;/p&gt;

&lt;p&gt;In this lesson, we are going to define the core entities of our Personal Finance application. We aren't just creating tables; we are defining the &lt;em&gt;vocabulary&lt;/em&gt; of our domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Entities
&lt;/h2&gt;

&lt;p&gt;Our application needs to track money moving in and out. To do that effectively, we need four primary concepts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Bank Accounts&lt;/strong&gt;: Where the money lives.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Categories&lt;/strong&gt;: How we classify the money (e.g., "Utilities", "Dining Out").&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Budgets&lt;/strong&gt;: Our financial goals or limits.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Transactions&lt;/strong&gt;: The heart of the system, the actual record of spending or earning.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Generating the Artifacts
&lt;/h2&gt;

&lt;p&gt;Laravel makes scaffolding these incredible easy. We need a Model, a Migration, a Factory, and a Seeder for each of our entities. Instead of running four commands per entity, we can use the &lt;code&gt;-mfs&lt;/code&gt; flags.&lt;/p&gt;

&lt;p&gt;Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:model Category &lt;span class="nt"&gt;-mfs&lt;/span&gt;
php artisan make:model Budget &lt;span class="nt"&gt;-mfs&lt;/span&gt;
php artisan make:model BankAccount &lt;span class="nt"&gt;-mfs&lt;/span&gt;
php artisan make:model Transaction &lt;span class="nt"&gt;-mfs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a flurry of green success messages. We now have our files ready to be sculpted.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on "Down" Methods
&lt;/h2&gt;

&lt;p&gt;Open up one of your new migration files. You'll see an &lt;code&gt;up&lt;/code&gt; method and a &lt;code&gt;down&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;I'm going to ask you to do something that might feel rebellious: &lt;strong&gt;delete the &lt;code&gt;down&lt;/code&gt; method.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why? In this project, we are adopting a &lt;strong&gt;Fix Forward&lt;/strong&gt; strategy. In a production environment with real data, rolling back a migration (especially one that drops columns or tables) is destructive and risky. If we make a mistake in a migration that has already run, we don't roll it back, we create a &lt;em&gt;new&lt;/em&gt; migration to fix the issue. This keeps our database history linear and truthful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, go ahead and remove &lt;code&gt;public function down(): void&lt;/code&gt; from all your new migration files. It clarifies our intent: we only move forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Schema
&lt;/h2&gt;

&lt;p&gt;Let's define the structure of our tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Enums
&lt;/h3&gt;

&lt;p&gt;Before we get to the tables, we need a way to define the type of a Budget. Is it a fixed budget that we reset every month? Or is it a rolling budget that we keep track of over time? Let's use a PHP Enum for this to ensure type safety.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:enum Enums/BudgetType
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;app/Enums/BudgetType.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Enums&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;BudgetType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'reset'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Rollover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rollover'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getLabel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Reset&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Reset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Rollover&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Rollover'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Migrations
&lt;/h3&gt;

&lt;p&gt;Now, let's fill in our &lt;code&gt;up&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;create_categories_table.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Categories are simple. They have a name and belong to a user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'categories'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;create_budgets_table.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
A budget tracks a limit for a specific period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'budgets'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fixed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;create_bank_accounts_table.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Represents a physical or digital account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bank_accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unsignedBigInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'balance'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;create_transactions_table.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The center of our universe. Links everything together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'transactions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bank_account_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'budget_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'note'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unsignedInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Notice we store money as &lt;code&gt;integers&lt;/code&gt; (cents) rather than &lt;code&gt;floats&lt;/code&gt; or &lt;code&gt;decimals&lt;/code&gt;. Floating point math can be imprecise. storing $10.00 as &lt;code&gt;1000&lt;/code&gt; is deeply robust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. The Money Cast
&lt;/h2&gt;

&lt;p&gt;Since we are storing money as integers (cents) but want to work with it as standard units (dollars/euros) in our code, let's create a custom Cast. This encourages consistency across our application.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:cast MoneyCast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;app/Casts/MoneyCast.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Casts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Database\Eloquent\CastsAttributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MoneyCast&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CastsAttributes&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Cast the given value.
     *
     * @param  array&amp;lt;string, mixed&amp;gt;  $attributes
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Prepare the given value for storage.
     *
     * @param  array&amp;lt;string, mixed&amp;gt;  $attributes
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The Models
&lt;/h2&gt;

&lt;p&gt;Now we breathe life into our schemas by defining relationships and behaviors in our Models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;User.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The user owns everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... imports&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Relations\HasMany&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Authenticatable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... traits&lt;/span&gt;

    &lt;span class="c1"&gt;// ... fillable &amp;amp; hidden&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bankAccounts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BankAccount&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;budgets&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Budget&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;Category.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @use HasFactory&amp;lt;\Database\Factories\CategoryFactory&amp;gt; */&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;Budget.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Here we treat our &lt;code&gt;type&lt;/code&gt; as an Enum and &lt;code&gt;amount&lt;/code&gt; with our new MoneyCast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Budget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @use HasFactory&amp;lt;\Database\Factories\BudgetFactory&amp;gt; */&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\App\Enums\BudgetType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\App\Casts\MoneyCast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;BankAccount.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankAccount&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @use HasFactory&amp;lt;\Database\Factories\BankAccountFactory&amp;gt; */&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'balance'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'balance'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\App\Casts\MoneyCast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\HasMany&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;Transaction.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The nexus. Note the mass assignment protection and casting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @use HasFactory&amp;lt;\Database\Factories\TransactionFactory&amp;gt; */&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'bank_account_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'budget_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'note'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\App\Casts\MoneyCast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bankAccount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BankAccount&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;\Illuminate\Database\Eloquent\Relations\BelongsTo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;belongsTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Budget&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Seeding and Verification
&lt;/h2&gt;

&lt;p&gt;With our structure defined, let's spin up the database.&lt;/p&gt;

&lt;p&gt;First, update your &lt;code&gt;DatabaseSeeder.php&lt;/code&gt; to call the new seeders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseSeeder&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Seeder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;WithoutModelEvents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Seed the application's database.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserSeeder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CategorySeeder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BudgetSeeder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BankAccountSeeder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransactionSeeder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(We'll cover Factories in depth in a future lesson, but for now, ensure your factories create dummy data).&lt;/p&gt;

&lt;p&gt;Then, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate:fresh &lt;span class="nt"&gt;--seeder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's verify our work using Tinker, the best tool for checking your data reality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan tinker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try fetching a user and their transactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\App\Models\User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see a Collection of Transaction models, congratulations. You have successfully mapped the physical world of finance into your digital domain.&lt;/p&gt;

&lt;p&gt;Finally, before we commit, let's make sure our code style is impeccable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./vendor/bin/pint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep moving forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Build this App &amp;amp; Get Certified (Free)
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful, you can build the entire application from scratch with my free course. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏗️ Advanced Laravel Architecture&lt;/li&gt;
&lt;li&gt;📊 Building Dashboards with FilamentPHP&lt;/li&gt;
&lt;li&gt;💰 Handling Money professionally in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;→ Join other students on maiobarbero.dev&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Laravel Personal Finance Dashboard: setting up the project with Filament v5</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Tue, 03 Feb 2026 08:33:26 +0000</pubDate>
      <link>https://dev.to/maiobarbero/setting-up-laravel-and-filament-for-personal-finance-5om</link>
      <guid>https://dev.to/maiobarbero/setting-up-laravel-and-filament-for-personal-finance-5om</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;👋 Note:&lt;/strong&gt; This article is an excerpt from my &lt;strong&gt;Free Laravel &amp;amp; Filament Finance Course&lt;/strong&gt;.&lt;br&gt;
The full course includes code-along lessons, quizzes, and a &lt;strong&gt;Completion Certificate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;Start the Free Course &amp;amp; Get Certified&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Starting a new project is always a mix of excitement and anxiety. The blank slate. Infinite possibilities. But today, we're not just writing code; we're building a tool for financial freedom. A system that serves &lt;em&gt;us&lt;/em&gt;, tailored to our specific needs, without the bloat of generic SaaS products.&lt;/p&gt;

&lt;p&gt;In this first lesson, we will lay the foundation. We are going to build a robust, modern application using strictly typed &lt;strong&gt;Laravel 12&lt;/strong&gt; and the elegant &lt;strong&gt;Filament&lt;/strong&gt; admin panel.&lt;/p&gt;

&lt;p&gt;Let's get our hands dirty.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Foundation
&lt;/h2&gt;

&lt;p&gt;First, let's create our canvas. Open your terminal and breathe in that new project smell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;laravel new laravel_filament_personal_finance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want a clean slate, so when promoted, select:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;No starter kit&lt;/strong&gt;: We are building this artisan-style.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Pest&lt;/strong&gt;: For testing. It’s elegant and expressive.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SQLite&lt;/strong&gt;: Simplify the infrastructure. For a personal tool, it's fast, zero-conf, and easy to backup.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Init Git repo&lt;/strong&gt;: Yes. Version control is non-negotiable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the command finishes, navigate into your new fortress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;laravel_filament_personal_finance
composer &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the heart is beating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;http://localhost:8000&lt;/code&gt;. You should see the default Laravel splash screen. We are live.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/1e9aefa" rel="noopener noreferrer"&gt;View the initial setup commit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Supercharging with AI
&lt;/h2&gt;

&lt;p&gt;I believe in leveraging every tool available to maintain flow. We'll add &lt;strong&gt;Laravel Boost&lt;/strong&gt;, an MCP server that acts as a bridge between our app and AI agents. Think of it as giving your AI pair programmer direct sensory access to your codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/boost &lt;span class="nt"&gt;--dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan boost:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted, select your IDE or specific configuration. This will generate a configuration block like this (the below one is the Google Antigravity configuration):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"laravel-boost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"/home/&amp;lt;your-username&amp;gt;/&amp;lt;path-to-project&amp;gt;/artisan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"boost:mcp"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, your AI assistant can understand your project structure, run artisan commands, and debugging becomes a conversation rather than a struggle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/8d05ee9" rel="noopener noreferrer"&gt;View the Boost configuration commit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Admin Panel
&lt;/h2&gt;

&lt;p&gt;We don't want to spend weeks building authentication views and CRUD tables from scratch. We want to focus on the &lt;em&gt;logic&lt;/em&gt; of our finances. Enter &lt;strong&gt;Filament v5&lt;/strong&gt;. It’s the gold standard for TALL stack admin panels.&lt;/p&gt;

&lt;p&gt;Install it via composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require filament/filament:&lt;span class="s2"&gt;"^5.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, install the panel. We'll call ours &lt;code&gt;admin&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan filament:install &lt;span class="nt"&gt;--panels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Enter &lt;code&gt;admin&lt;/code&gt; when asked for the ID.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This scaffolds the entire admin infrastructure. It's almost magical how much time this saves us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/maiobarbero/laravel_filament_personal_finance/commit/18c5b5e" rel="noopener noreferrer"&gt;View the Filament setup commit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. First Access
&lt;/h2&gt;

&lt;p&gt;Reference without access is useless. Let's create an admin user to unlock the gates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the prompts to set your name, email, and password. Then, restart your server if it's not running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to &lt;code&gt;/admin&lt;/code&gt;. Login with your new credentials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmokf82ultop07a82iji.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmokf82ultop07a82iji.webp" alt="Admin Dashboard" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are now looking at the control center of your future financial system. It's empty now, but soon it will be the source of truth for your wealth.&lt;/p&gt;

&lt;p&gt;We have a running application, a powerful admin interface, and the AI tooling to help us build faster. The structure is set. In the next lesson, we will start modeling the domain.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Build this App &amp;amp; Get Certified (Free)
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful, you can build the entire application from scratch with my free course. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏗️ Advanced Laravel Architecture&lt;/li&gt;
&lt;li&gt;📊 Building Dashboards with FilamentPHP&lt;/li&gt;
&lt;li&gt;💰 Handling Money professionally in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.maiobarbero.dev/courses/personal-finance-with-laravel/" rel="noopener noreferrer"&gt;&lt;strong&gt;→ Join other students on maiobarbero.dev&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to set up a global .gitignore</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Wed, 07 May 2025 06:33:13 +0000</pubDate>
      <link>https://dev.to/maiobarbero/how-to-set-up-a-global-gitignore-4e09</link>
      <guid>https://dev.to/maiobarbero/how-to-set-up-a-global-gitignore-4e09</guid>
      <description>&lt;p&gt;If you work with multiple Git repositories, you’ve likely run into the frustration of ignoring the same files (like &lt;code&gt;.DS_Store&lt;/code&gt;, &lt;code&gt;Thumbs.db&lt;/code&gt;, or your IDE settings) over and over again. Fortunately, Git allows you to define a global &lt;code&gt;.gitignore&lt;/code&gt; file that applies to all repositories on your system. This is done through the &lt;code&gt;core.excludesfile&lt;/code&gt; configuration option.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk you through how to set up a global &lt;code&gt;.gitignore&lt;/code&gt; file using &lt;code&gt;git config&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Why Use a Global .gitignore?
&lt;/h2&gt;

&lt;p&gt;Some files are specific to your system or development environment and should never be committed to &lt;em&gt;any&lt;/em&gt; project. Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS system files (&lt;code&gt;.DS_Store&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Windows thumbnails (&lt;code&gt;Thumbs.db&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;IDE configs (&lt;code&gt;.idea/&lt;/code&gt;, &lt;code&gt;.vscode/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Log files (&lt;code&gt;*.log&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Local environment files (&lt;code&gt;.env&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of duplicating the same ignore rules in every repo, a global &lt;code&gt;.gitignore&lt;/code&gt; keeps things DRY (Don't Repeat Yourself).&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Step-by-Step Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create the Global .gitignore File
&lt;/h3&gt;

&lt;p&gt;Start by creating a &lt;code&gt;.gitignore_global&lt;/code&gt; file in your home directory (or anywhere you prefer):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; ~/.gitignore_global
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, edit it and add the rules you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
.DS_Store

&lt;span class="c"&gt;# Windows&lt;/span&gt;
Thumbs.db

&lt;span class="c"&gt;# IDEs&lt;/span&gt;
.vscode/
.idea/

&lt;span class="c"&gt;# Logs&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;.log

&lt;span class="c"&gt;# Env files&lt;/span&gt;
.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure Git to Use the Global File
&lt;/h3&gt;

&lt;p&gt;Tell Git to use this file globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; core.excludesfile ~/.gitignore_global
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify it worked with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git config --global core.excludesfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should output the path you just set.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introducing Astro Academia</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Mon, 03 Mar 2025 10:49:38 +0000</pubDate>
      <link>https://dev.to/maiobarbero/introducing-astro-academia-i6d</link>
      <guid>https://dev.to/maiobarbero/introducing-astro-academia-i6d</guid>
      <description>&lt;h2&gt;
  
  
  A Free Website Template for Academics
&lt;/h2&gt;

&lt;p&gt;Astro Academia is a website template built using Astro, a modern static site generator. The template is designed to be easy to use and customize, even for those who may not have extensive web development experience. It includes several features that are particularly useful for academics, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publication List: Easily showcase your research papers and publications.&lt;/li&gt;
&lt;li&gt;CV Page: Display your curriculum vitae in a clean and organized manner.&lt;/li&gt;
&lt;li&gt;Blog: Share your thoughts and updates with a built-in blog.&lt;/li&gt;
&lt;li&gt;Responsive Design: Ensure your website looks great on all devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Easy to Customize
&lt;/h3&gt;

&lt;p&gt;The template is built with simplicity in mind. You can easily customize the content and appearance of your website by editing a few configuration files and markdown files. The use of Tailwind CSS allows for easy styling adjustments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built with Astro
&lt;/h3&gt;

&lt;p&gt;Astro is a modern static site generator that offers excellent performance and flexibility. It allows you to use your favorite front-end frameworks like React, Vue, or Svelte, while still generating static HTML for fast load times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free and Open Source
&lt;/h3&gt;

&lt;p&gt;Astro Academia is completely free to use and open source. You can find the source code on GitHub. Contributions and feedback are welcome!&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Following the official documentation you will be able to edit the layout and to add data like your cv and your pubblications. Astro Academia also support blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I created Astro Academia to help academics easily create and maintain their personal websites. It is a free and open-source template that is easy to use and customize. I hope this project can help many academics showcase their work and achievements without the hassle of building a website from scratch.&lt;/p&gt;

&lt;p&gt;Feel free to check out the project on GitHub, or the official preview, and contribute if you have any improvements or suggestions!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/maiobarbero/astro_academia" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://maiobarbero.github.io/astro_academia/" rel="noopener noreferrer"&gt;Preview&lt;/a&gt;&lt;br&gt;
&lt;a href="https://astro.build/themes/details/astro-academia/" rel="noopener noreferrer"&gt;Astro Theme Page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Deploy a WordPress Theme with GitHub Actions</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Tue, 11 Jun 2024 08:56:07 +0000</pubDate>
      <link>https://dev.to/maiobarbero/deploy-a-wordpress-theme-with-github-actions-4kal</link>
      <guid>https://dev.to/maiobarbero/deploy-a-wordpress-theme-with-github-actions-4kal</guid>
      <description>&lt;h2&gt;
  
  
  Is it possible to deploy a WordPress theme directly from GitHub?
&lt;/h2&gt;

&lt;p&gt;Minified code, “bundler” of various types, package manager for JavaScript and PHP and so forth… These are just some of the reasons for choosing to &lt;strong&gt;automate the deployment of a WordPress Theme.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sure it’s always possible to upload files with ftp every time, but why do it manually when GitHub can take care of everything?&lt;/p&gt;

&lt;h2&gt;
  
  
  Welcome to the guide to automate the deployment
&lt;/h2&gt;

&lt;p&gt;First, the theme code must be in a GitHub repository.&lt;/p&gt;

&lt;p&gt;Create a .github folder inside the repo. Inside this folder create another folder called workflows and inside it a main.yml file. You can give this file any name you like, the important thing is to keep the .yml extension.&lt;/p&gt;

&lt;p&gt;The folder structure will look like this: &lt;strong&gt;.github / workflows / main.yml&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The .yml file
&lt;/h3&gt;

&lt;p&gt;Let’s start creating our workflow to automate the deployment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy via FTP
on:
  push:
    branches: [ main ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;name will simply be the name that we will display in the Actions tab of the repository&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2ggstdouvroj86my1t0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2ggstdouvroj86my1t0.png" alt=" " width="800" height="41"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;on: things get interesting here. We define our trigger here. In this case we want to deploy to the push, but we have added a filter: we are only interested in the push done to the main branch. In this way we can have a development branch, perhaps automating the deployment of this on a staging site as well.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we come to the actions, jobs, which will follow, by default in parallel, our workflow. build will be the name of our only job&lt;/p&gt;

&lt;p&gt;runs-on: where do we want to run our workflow? GitHub gives us several possibilities listed here.&lt;/p&gt;

&lt;p&gt;The last three lines are used to set a default shell for all runs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 2
    - name: FTP Deploy WP Theme
      uses: SamKirkland/FTP-Deploy-Action@4.3.2
      with:
        server: FTP SERVER
        username: FTP USERNAME
        password: ${{ secrets.FTP_PASSWORD }}
        server-dir: FTP DIR
        exclude: |
          **/.git*
          **/.git*/**
          **/node_modules/**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are at the final steps of our workflow. We just have to worry about writing our ftp server address and username, as well as the destination folder (our theme folder).&lt;/p&gt;

&lt;p&gt;Since this file will be accessible from the repository, we can keep some settings like our password secret. To do this we can set a new secret key from the &lt;strong&gt;Setting / Secrets / Actions tab&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9davfjlty9835munl22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9davfjlty9835munl22.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Secret key GitHub repository&lt;br&gt;
Are you tired of reading and just want to make a nice copy and paste? Find the .yml file and ready-made folder structure &lt;a href="https://github.com/maiobarbero/WordPress-Theme-Deploy-Action" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>wordpress</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploy a Grafana dashboard with Docker on AWS EC2</title>
      <dc:creator>Matteo Barbero</dc:creator>
      <pubDate>Tue, 07 May 2024 11:23:28 +0000</pubDate>
      <link>https://dev.to/maiobarbero/deploy-a-grafana-dashboard-with-docker-on-aws-ec2-5f6o</link>
      <guid>https://dev.to/maiobarbero/deploy-a-grafana-dashboard-with-docker-on-aws-ec2-5f6o</guid>
      <description>&lt;p&gt;Monitoring system performance and logs with Grafana is indeed straightforward, especially when working with Docker environments. Grafana seamlessly integrates with Docker, providing prebuilt dashboards that instantly visualize key metrics and logs from your containers and hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch EC2 instance
&lt;/h2&gt;

&lt;p&gt;To embark on your AWS journey, you'll need a server instance, and AWS offers the perfect playground with its free tier. Launch a Linux instance effortlessly, and let the experimentation begin!&lt;/p&gt;

&lt;p&gt;Click on launch EC2 instance and select &lt;strong&gt;t2.micro&lt;/strong&gt; to use the free tier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ggzmdk16wcf0cb6n71s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ggzmdk16wcf0cb6n71s.png" alt="Launch EC2 Dashboard" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we need some more configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a key pair (for the login) and download it in a safe place;&lt;/li&gt;
&lt;li&gt;Add a security group or create a new one;
-Allow traffic from both ssh and internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you can launch your EC2 instance &lt;/p&gt;

&lt;h3&gt;
  
  
  Open ports
&lt;/h3&gt;

&lt;p&gt;For these project we need to open these ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3000&lt;/strong&gt; for Grafana&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9090&lt;/strong&gt; for Prometheus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3100&lt;/strong&gt; for Loki&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9100&lt;/strong&gt; for node-exporter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7dprsbb1m9xhzn09wcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7dprsbb1m9xhzn09wcd.png" alt="EC2 port settings" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;navigate to the EC2 Dashboard and select your EC2 instance&lt;/li&gt;
&lt;li&gt;click on the security group associated and the click edit&lt;/li&gt;
&lt;li&gt;configure the inbound rule selecting &lt;strong&gt;Custom TCP Rule&lt;/strong&gt; as the type. Enter the port number and select &lt;strong&gt;Anywhere&lt;/strong&gt; as the source.&lt;/li&gt;
&lt;li&gt;Repeat the last step for all the four ports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install Docker
&lt;/h2&gt;

&lt;p&gt;With your Linux instance up and running, it's time to unlock the power of containerization by installing Docker. This game-changing platform will enable you to effortlessly deploy and manage your applications, including the highly coveted Grafana dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect via ssh
&lt;/h3&gt;

&lt;p&gt;In your terminal run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i "&amp;lt;path_to_your_key.pem&amp;gt;" ec2-user@&amp;lt;Public IPv4 DNS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type yes and you are inside your instance!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2pinneuqoixf3zi9tau.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2pinneuqoixf3zi9tau.png" alt="SSH Connection" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Docker and Docker Compose
&lt;/h3&gt;

&lt;p&gt;To install Docker run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum update
sudo yum install -y docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can install Docker Compose. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run Docker service
&lt;/h3&gt;

&lt;p&gt;If everything since here went fine you only need to start the Docker daemon. To do this run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl start docker
sudo systemctl enable docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Copy the project
&lt;/h2&gt;

&lt;p&gt;You can download the source code from the repo and copy it into the EC2 with &lt;strong&gt;scp&lt;/strong&gt;, or clone directly into the EC2 instance, and check if &lt;code&gt;git&lt;/code&gt; is installed. &lt;/p&gt;

&lt;p&gt;To clone the repo run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/maiobarbero/grafana-prometheus-loki.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Edit the configuration's files
&lt;/h3&gt;

&lt;p&gt;Now with &lt;strong&gt;vim&lt;/strong&gt; edit the configuration file, in particular if you are using &lt;a href="https://github.com/maiobarbero/grafana-prometheus-loki" rel="noopener noreferrer"&gt;this repository&lt;/a&gt; edit &lt;code&gt;config/prometheus/prometheus-config.yml&lt;/code&gt; and replace localhost with your private IP4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the application
&lt;/h2&gt;

&lt;p&gt;Here we are, the last step. Inside your project folder run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it.&lt;/p&gt;

&lt;p&gt;Go to your-public-ip:3000 and you have your Grafana instance!!!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cover image by &lt;a href="https://unsplash.com/@chrisliverani?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Chris Liverani&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/turned-on-flat-screen-monitor-dBI_My696Rk?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>docker</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
