-
-
Notifications
You must be signed in to change notification settings - Fork 0
Projects
Manage projects in Teamleader Focus β the current ("nextgen") project system.
The Projects resource is the central hub of the v2 project management system. Projects contain groups, tasks, and materials, and can be linked to deals, quotations, customers, and owners.
Access via Teamleader::projects(), or Teamleader::nextgenProjects() β see below.
Read Which system, and what it's called first if you are new to this resource. Teamleader uses three different names for this one thing and they do not line up. Getting it backwards is easy and expensive.
| SDK method |
Teamleader::projects() β alias Teamleader::nextgenProjects()
|
| API path | projects-v2/projects.* |
| Webhook events |
nextgenProject.created, nextgenProject.updated, nextgenProject.closed, nextgenProject.deleted
|
Reading the webhook names, it is natural to assume projects() must be the legacy resource and that some nextgenProjects() covers the new one. It is the other way round. This class is what Teamleader calls "nextgen"; nextgenProjects() was added in v2.2.0 as an alias precisely because reasoning from the event names leads people here looking for a method that didn't exist.
The old system is Legacy Projects, on the bare projects.* path with project.* events. So the class names and the endpoint paths run in opposite directions: Projects is the newer class on the longer path, LegacyProjects the older class on the shorter one. Teamleader chose the projects-v2 prefix for the new module to avoid colliding with the existing projects.* endpoints.
Do not infer which system an account is on from whether a list call returns rows. Both endpoints answer. Querying this resource and getting twenty real projects back does not mean the account is on nextgen. Ask directly:
Teamleader::accounts()->getProjectsVersion(); // 'projects-v2' or 'legacy'
Teamleader::accounts()->isUsingProjectsV2(); // boolSee Accounts.
projects-v2/projects
| Capability | Supported |
|---|---|
| Pagination | β Supported |
| Filtering | β Supported |
| Sorting | β Supported (many fields) |
| Sideloading | β
Supported (legacy_project, custom_fields) |
| Creation | β Supported |
| Update | β Supported |
| Deletion | β Supported |
delete()requires a strategy β defaults tounlink_tasks_and_time_trackings.
close()requires a strategy β defaults tonone.Customer filter key is
customers(plural array), notcustomer(singular object). This differs from Deals and Invoices, which use the singular form.Sideload via
options['includes'](plural) inlist().
use McoreServices\TeamleaderSDK\Facades\Teamleader;
$projects = Teamleader::projects()->list();
$projects = Teamleader::projects()->list(
['status' => 'open'],
['sort' => [['field' => 'title', 'order' => 'asc']], 'page_size' => 50]
);
// With sideloading
$projects = Teamleader::projects()->list([], ['includes' => 'legacy_project,custom_fields']);$project = Teamleader::projects()->info('project-uuid');
$project = Teamleader::projects()->info('project-uuid', ['legacy_project', 'custom_fields']);
$project = Teamleader::projects()->with('custom_fields')->info('project-uuid');The fluent
with()form only works from v2.2.0. The method did not exist in any earlier release, so->with(...)raisedError: Call to undefined methodon every resource that offered it.
Only title is required. billing_method and color are validated when provided.
| Required | Notes |
|---|---|
title |
Project title |
Billing methods: time_and_materials, fixed_price, non_billable
$project = Teamleader::projects()->create([
'title' => 'Website Redesign',
'billing_method' => 'time_and_materials',
'color' => '#00B2B2',
'start_date' => '2025-05-01',
'end_date' => '2025-08-31',
'customers' => [['type' => 'company', 'id' => 'company-uuid']],
]);Injects id into the request body.
Teamleader::projects()->update('project-uuid', ['title' => 'Website Redesign v2']);Delete strategies (validated):
| Strategy | Behaviour |
|---|---|
unlink_tasks_and_time_trackings |
Default β keeps tasks and time entries, removes project link |
delete_tasks_and_time_trackings |
Deletes tasks and all time entries |
delete_tasks_unlink_time_trackings |
Deletes tasks, keeps time entries unlinked |
Teamleader::projects()->delete('project-uuid');
Teamleader::projects()->delete('project-uuid', 'delete_tasks_and_time_trackings');The default is the conservative one.
delete_tasks_and_time_trackingsdestroys tracked time permanently β worth being deliberate about in any automated cleanup.
Creates a copy of the project with a new title.
$copy = Teamleader::projects()->duplicate('project-uuid', 'Website Redesign β Phase 2');Closing strategies (validated):
| Strategy | Behaviour |
|---|---|
none |
Default β just closes the project |
mark_tasks_and_materials_as_done |
Also marks all tasks and materials as done |
Teamleader::projects()->close('project-uuid');
Teamleader::projects()->close('project-uuid', 'mark_tasks_and_materials_as_done');Closing fires the nextgenProject.closed webhook β note that there is no equivalent project.closed event on the legacy system.
Teamleader::projects()->reopen('project-uuid');Assignee type validated: user, team.
Teamleader::projects()->assign('project-uuid', 'user', 'user-uuid');
Teamleader::projects()->unassign('project-uuid', 'team', 'team-uuid');Teamleader::projects()->addDeal('project-uuid', 'deal-uuid');
Teamleader::projects()->removeDeal('project-uuid', 'deal-uuid');
Teamleader::projects()->addQuotation('project-uuid', 'quotation-uuid');
Teamleader::projects()->removeQuotation('project-uuid', 'quotation-uuid');
Teamleader::projects()->addOwner('project-uuid', 'user-uuid');
Teamleader::projects()->removeOwner('project-uuid', 'user-uuid');Teamleader::projects()->open();
Teamleader::projects()->closed();
Teamleader::projects()->running();
Teamleader::projects()->overdue();
Teamleader::projects()->overBudget();Teamleader::projects()->search('website');
Teamleader::projects()->byIds(['uuid-1', 'uuid-2']);
Teamleader::projects()->forCustomer('company', 'company-uuid'); // type first, then id
Teamleader::projects()->forDeal('deal-uuid');
Teamleader::projects()->forQuotation('quotation-uuid');
forCustomer()takes type first, unlikeLegacyProjects::forCustomer(), which takes id first. An easy one to get wrong when supporting both systems.
Verified against @teamleader/focus-api-specification. This is the complete set.
| Filter | Type | Description |
|---|---|---|
ids |
array | Filter by project UUIDs |
status |
string |
open, planned, running, overdue, over_budget, closed
|
customers |
array |
[{type: contact|company, id: uuid}] β plural, array of objects |
deal_ids |
array | Filter by deal UUIDs |
quotation_ids |
array | Filter by quotation UUIDs |
term |
string | Search project number, title, customer/assignee/owner names |
Supported sort fields: amount_billed, amount_paid, amount_unbilled, cost, customer, end_date, external_budget_spent, external_budget, internal_budget, margin, price, project_key, start_date, status, time_budget, time_estimated, time_tracked, title
$projects = Teamleader::projects()->list([], [
'sort' => [['field' => 'end_date', 'order' => 'asc']],
]);| Include | Description |
|---|---|
legacy_project |
Linked legacy project reference (if migrated from legacy) |
custom_fields |
Custom field values on the project |
legacy_projectis the bridge between the two systems: on a migrated account it points at the Legacy Projects record the project came from.Custom field definitions for projects use
context: projectβ see Custom Fields.
This resource returns pagination metadata. It is one of the few that does, so meta.matches gives you a real total count rather than requiring you to page until a short page.
If your integration has to support accounts on either system, branch once at the top rather than guessing:
if (Teamleader::accounts()->isUsingProjectsV2()) {
$projects = Teamleader::projects()->list(['status' => 'open']);
} else {
$projects = Teamleader::legacyProjects()->active();
}Things that differ between the two beyond the endpoint:
| Projects (nextgen) | Legacy Projects | |
|---|---|---|
| Status values |
open, planned, running, overdue, over_budget, closed
|
active, on_hold, done, cancelled
|
| Customer filter |
customers β plural array |
customer β singular object |
forCustomer() |
(type, id) |
(id, type) |
| Webhooks | nextgenProject.* |
project.* |
| Contains | groups, tasks, materials | milestones, participants |
use InvalidArgumentException;
// Missing title
try {
Teamleader::projects()->create(['billing_method' => 'time_and_materials']);
} catch (InvalidArgumentException $e) {
// 'Title is required for creating a project'
}
// Invalid delete strategy
try {
Teamleader::projects()->delete('uuid', 'archive');
} catch (InvalidArgumentException $e) {
// 'Invalid delete strategy. Must be one of: ...'
}
// Wrong project system β the call succeeds but returns nothing useful
// There is no exception for this. Check the account first.-
Accounts β
getProjectsVersion()tells you which system an account uses -
Legacy Projects β The older system, on the bare
projects.*path - Groups β Organise tasks and materials within a project
- Project Tasks β Tasks inside a project
- Materials β Materials inside a project
- Project Lines β Unified view of all project line items
- External-Parties β External stakeholders on a project
-
Custom Fields β Definitions use
context: project -
Time Tracking β Filter project time via
relates_towithnextgenProject -
Webhooks β This resource fires
nextgenProject.*events -
Files β Use
files()->forProject(), which maps tonextgenProject
Last Updated: August 2026 β’ SDK Version: 2.2.2 β’ Made with β€οΈ by MCore Services
- Departments
- Users
- Teams
- Custom Fields
- Work Types
- Document Templates
- Currencies
- Notes
- Email Tracking
- Closing Days
- Day Off Types
- Days Off
- User Schedules
- Invoices
- Credit Notes
- Subscriptions
- Payment Methods
- Payment Terms
- Tax Rates
- Withholding Tax Rates
- Commercial Discounts
Next Gen Projects
Legacy Projects