[ Upcoming ] Enhanced custom type support for projects and portfolios, plus new custom_type filtering

Summary

We’re extending custom types beyond custom task types to also support custom projects and portfolios. With this release, you can:

  • Look up custom types across an entire workspace, not just within a single project

  • Identify Asana-created custom types via the new asana_created_type_identifier field (similar to the existing asana_created_field property on custom fields)

  • Filter GET /tasks, GET /projects, and GET /portfolios directly by custom_type

  • Read resource_subtype and custom_type on projects and portfolios, matching the pattern already used for tasks

  • Set a custom type when creating or updating projects and portfolios

Asana’s own built-in object types are represented using this same custom type framework. To avoid silently changing what your existing bulk queries return, we’re introducing a new opt-in flag, include_asana_created_custom_types, that controls whether Asana-created custom-typed objects are included in GET /tasks, GET /projects, and GET /portfolios responses when no explicit custom_type filter is provided.

Who is affected

  • If you call GET /tasks, GET /projects, or GET /portfolios without a custom_type filter and assume a fixed, closed set of objects or resource_subtype values comes back: this is a breaking change for you. Once Asana-created custom-typed objects begin appearing in these responses (after the default flips, see Timeline), clients that don’t handle unfamiliar resource_subtype/custom_type values could break.

  • If you call GET /custom_types today: the project parameter is no longer required on its own. Going forward, exactly one of project or workspace must be provided. Existing requests that pass project continue to work unchanged.

  • If you read projects or portfolios: these objects will now include resource_subtype and custom_type fields. This is additive and non-breaking — but if your integration validates response shapes strictly, be aware of the new fields.

  • If you fetch objects by GID (e.g., GET /tasks/{task_gid}): no change. Requests against specific objects are unaffected by the new change header.

All changes other than the bulk-query behavior (new fields, the workspace parameter, the custom_type filters, write support) are additive and non-breaking on their own.

Timeline

Phase Target date Description
General availability (opt-in) August 18, 2026 New endpoints, fields, and filters are live. include_asana_created_custom_types defaults to off — existing bulk-query behavior is unchanged unless you opt in with Asana-Enable.
Opt-out (activation) January 13, 2027 The default flips to on: Asana-created custom-typed objects appear in bulk queries by default. Send Asana-Disable: include_asana_created_custom_types to temporarily restore the previous behavior. We’ll confirm the exact date and announce it in this thread ahead of time.
End of deprecation TBD include_asana_created_custom_types is permanently enabled and the Asana-Disable header is no longer accepted.

Response headers

During the deprecation period, affected API responses will include an Asana-Change header:

Asana-Change: name=include_asana_created_custom_types;info=https://forum.asana.com/t/upcoming-enhanced-custom-type-support-for-projects-and-portfolios-plus-new-custom-type-filtering/1153530;affected=true

You can control behavior per-request with these headers:

  • Asana-Enable: include_asana_created_custom_types — opt in to the new behavior before the default flips

  • Asana-Disable: include_asana_created_custom_types — temporarily restore the previous behavior after the default flips

For more details on how deprecation headers work, see Deprecations.

What’s changing

GET /custom_types — workspace-wide lookups

Previously, you could only list custom types for a single project (project was required). Now, exactly one of project or workspace must be provided:

GET /custom_types?workspace={workspace_gid}
{

"data": [

  {

    "gid": "1201234567890111",
  
    "resource_type": "custom_type",
    
    "name": "Release",
    
    "asana_created_type_identifier": null

   }

 ]

}

New response fields on custom types:

Field Type Description
asana_created_type_identifier string (enum) Conditional. A unique identifier associating this custom type with an Asana-created type definition. null for custom types you create yourself.

resource_subtype and custom_type on projects and portfolios

Projects and portfolios now include:

Field Type Description
resource_subtype string (enum): default_project/default_portfolio, custom The subtype of this resource. Different subtypes share many of the same fields and behavior, but may render differently in Asana or represent resources with different semantic meaning.
custom_type object (CustomTypeCompact) Reference to the object’s custom type. Not returned if the project/portfolio doesn’t have a custom type.

OAuth scopes: reading custom_type on projects and portfolios requires the custom_types:read scope. Task-level custom type fields continue to accept the existing task_custom_types:read scope (and also accept custom_types:read). Personal access tokens are unaffected.

These fields appear on project and portfolio responses across the API, including GET /projects, GET /portfolios, GET /teams/{team_gid}/projects, GET /workspaces/{workspace_gid}/projects, GET /workspaces/{workspace_gid}/projects/search, GET /tasks/{task_gid}/projects, and GET /goals/{goal_gid}/supportingWork.

You can also now set custom_type and resource_subtype when creating or updating a project or portfolio (e.g., POST /projects, PUT /projects/{project_gid}, POST /portfolios, PUT /portfolios/{portfolio_gid}). Note that custom_type can only be set when the object’s resource_subtype is custom. Some Asana-created custom types cannot be set via the API and attempting to, returns an error explaining why. Also task custom types can only be assigned within projects that have that type installed. The set returned by GET /custom_types is therefore a superset of what any particular object can be assigned.

Filtering by custom_type

GET /tasks, GET /projects, and GET /portfolios gain an optional custom_type query parameter:

  • Provide a custom type GID to return only objects of that custom type. Only the GID is supported (not the name). You can find a custom type’s GID via GET /custom_types, or by looking up an object you already know to be the correct type.

  • Provide an empty string to return only objects with no custom type assigned. For tasks, this currently includes milestones and approvals, as they’re handled separately from the custom type framework.

  • Omit the parameter to return all objects regardless of custom type (subject to the include_asana_created_custom_types behavior described below).

GET /projects?custom_type=1201234567890111
{

  "data": [

    {

      "gid": "1201234567890222",

      "name": "Q3 Release",

      "resource_subtype": "custom",

      "custom_type": {

        "gid": "1201234567890111",

        "name": "Release"

      }

    }

  ]

}

Note: if you explicitly filter by a custom_type GID, matching objects are always returned — including Asana-created custom-typed objects — regardless of the include_asana_created_custom_types state. The change header only affects unfiltered bulk queries.

Opting in to see Asana-created custom-typed objects in bulk queries

GET /projects

Asana-Enable: include_asana_created_custom_types


{

  "data": [

    {

      "gid": "1201234567890222",

      "name": "Q3 Release",

      "resource_subtype": "default_project"

    },

    {

      "gid": "1201234567890333",

      "name": "My Release Tracker",

      "resource_subtype": "custom",

      "custom_type": { "gid": "1201234567890111", "name": "Release" }

    }

  ]

}

Migration steps

  1. Audit how your integration handles resource_subtype on tasks, projects, and portfolios. If you expect only Asana native subtypes (default_task/milestone/approval), make sure to properly handle custom types going forward.

  2. Test the new behavior during the opt-in period by sending Asana-Enable: include_asana_created_custom_types on your bulk queries.

  3. When the default flips (January 13, 2027), either take no action if your integration is ready, or send Asana-Disable: include_asana_created_custom_types to temporarily restore the previous behavior while you finish migrating.

  4. If you need to identify or exclude Asana-created types specifically, use the asana_created_type_identifier field on custom types (it’s null for user-created custom types).

Why we’re making this change

Custom types are becoming a core part of how objects are modeled in Asana, including object types that Asana itself creates. Exposing these objects through existing bulk endpoints without warning would be a breaking change for integrations that assume a closed set of object subtypes. The include_asana_created_custom_types change gives you a testing window and an escape hatch while you update your integration, and gets everyone to a consistent end state where these objects are fundamentally equivalent to any other object in the API.

Resources

Questions and feedback

If you have questions about migrating, or feedback on the custom type model for projects and portfolios, reply in this thread.

6 Likes

Hi @Darya_Selivanava (and nice to meet you),

Thanks for this info!

I’m a little confused about one aspect of the custom types architecture: it seems that a custom type is universal within a workspace and the same custom type can be applied to multiple different objects? That wasn’t what I would have expected; I figured there would be only task-specific custom types, and project-specific custom types, and portfolio-specific custom types.

If I’m right that this is not the case, and there is only one flavor of custom type, then it seems that could lead to confusion; i.e. I could set a task to have a custom type of SERVICE_TICKET and also set a portfolio to have the same SERVICE_TICKET custom type, is that correct? I guess that provides for ultimate flexibility, but also seems like it could lead to confusion as well?

4 Likes

Nice functionality and nicely documented; thanks!

One question:

I think that same flip moment is depicted with a different date in the Timeline table:

Maybe indicate the same date or date range in both places?

Thanks,

Larry

1 Like

Hey @Phil_Seeman

Great question!

Short answer: custom types are universal within a workspace by design; however, in practice, trying to assign a type to an object that is of an inappropriate kind (e.g. a task-type object to a project-type object) will return an error.

More detail below:

Custom types are workspace-level entities, but each type declares which kind of object it can be applied to, and every type that exists today applies to one kind: SERVICE_TICKET is a task-type, SERVICE_QUEUE is a project-type, and so on. So the example assignment above (“set a portfolio to have the same SERVICE_TICKET custom type”) can’t happen in practice: attempting to set a task-only type like SERVICE_TICKET on a portfolio returns an error explaining the type isn’t applicable to that kind of object.

The reason the API doesn’t expose a fixed “object type” field on each custom type is deliberate: the underlying model allows a type to declare multiple applicable kinds, and we didn’t want to lock the API contract into “exactly one kind per type” in case future types use that flexibility for polymorphism. If that ever happens, the write-time validation stays the same. A type is assignable only to the kinds it declares. Only in that case, the type would declare not one but multiple kinds that it’s assignable to.

However, for the time being, each Asana resource belongs to one object kind (for example, ticket-style types are task-types and queue-style types are project-types), but the API doesn’t yet expose a field that tells you which kind a type applies to. In practice, you know a type’s kind from context (a type you see on tasks is a task-type), and if you assign a type to the wrong kind of object, the API returns a clear error.

Good catch, @lpb - the November date is a typo introduced from a stale, earlier draft of the changelog post. January 13, 2027 (the Timeline table) is the correct date. Also as the table notes, we’ll confirm the date in this thread ahead of the switch.

1 Like