Extending standard APIs (1)

12 Mar

I’ve got this question so many times: how can we extend the standard APIs with extra fields? So I decided to write a blog post about it. Actually, it’s going to be two blog posts, because one would become too long. So here it goes…

This could be a very short blog post. 😉 Microsoft disabled the option to customize the standard APIs in Business Central. Full stop…

No, of course not!!!

In this first blog post I want to cover the easy scenario: add a missing field to an API for master data. With master data, I mean for example the customers or items API. Those API pages are directly based on the table, while transaction APIs like sales orders are based on a buffer table. That makes it more complex to add extra fields. But not impossible! Just a little more complex. That will be for the next blog post. Let’s first focus on adding an extra field to the customers API.

The scenario

Let’s say we have a table extension for the customer table with two fields: Shoe Size and Hair Color. And we want these two fields to be added to the customers API. This is the table extension for the Customer table:

tableextension 50100 CustomerAJK extends Customer
{
    fields
    {
        field(50100; ShoeSizeAJK; Integer)
        {
            Caption = 'Shoe Size';
        }
        field(50101; HairColorAJK; Text[30])
        {
            Caption = 'Hair Color';
        }
    }
}

Creating the API page

Because it is not possible to create a page extension for a standard customers API, the only option is to copy it and make it our own. The downside of this is that we always need to keep it in sync with the standard API as released by Microsoft. If you want to keep it exactly the same of course. But we should not forget that APIs never get breaking changes. Instead, a new API version will be released alongside the existing API. And that does not happen with every release of Business Central. In other words, there is not a big risk involved.

How can we get the code of the standard APIs? That’s pretty simple: just clone the GitHub repository https://github.com/microsoft/ALAppExtensions. The app with the standard APIs v2 is in the folder Apps/W1/APIV2/app. If you open that folder in VS Code, then you will get many errors because the symbol files are missing. Just download symbol file to get rid of those errors.

Find the file APIV2Customers.Page.al in the APIV2 app and copy it to the app that has the extra fields. Next step is to make some corrections:

  • Change the object ID so it fits in your object range
  • Change the object name and the file name according to your naming conventions
  • Add the APIPublisher and APIGroup property
  • Optionally, modify the APIVersion property. This also depends on how you solve the remaining errors in the page about missing page parts (see below)
  • Optionally, reorder the page properties (that’s just my preference, to order them more logically)
  • Optionally, fix the implicit with warnings (to get rid of warnings during the build)

The properties of the API page now look like this:

page 50100 CustomersAPIv1AJK
{
    PageType = API;
    APIPublisher = 'ajk';
    APIGroup = 'demo';
    APIVersion = 'v2.0';
    EntitySetName = 'customers';
    EntityName = 'customer';
    EntitySetCaption = 'Customers';
    EntityCaption = 'Customer';
    ChangeTrackingAllowed = true;
    DelayedInsert = true;
    ODataKeyFields = SystemId;
    SourceTable = Customer;
    Extensible = false;

Fixing compilation errors

The page is also showing some compilation errors about missing page parts. There are a number of related entities, like customerFinancialDetails that are missing. These parts are in the APIV2 app. There are three options to solve these errors.

Option 1 is to just remove the related parts. If you don’t need them, then that’s the simplest solution of course.

Option 2 is to copy the missing page parts. That means that you have to do the same fixes as with the customers API. In fact, you are copying a complete set of API pages that belong to each other.

Option 3 is my preference: set a dependency on the APIV2 app. Then you don’t have to copy the missing page parts. If you choose this option, then there is one requirement: the property APIVersion of your custom API page must be the same as in the related API page from the depending app. That’s why I left it to v2.0 in the example above. The dependencies part in the app.json looks like this:

  "dependencies": [
    {
      "id":  "10cb69d9-bc8a-4d27-970a-9e110e9db2a5",
      "name":  "_Exclude_APIV2_",
      "publisher":  "Microsoft",
      "version":  "17.4.0.0"
    }
  ]

Adding the custom fields

Now we have a new custom API page that is an exact copy of the original one. The only step left is to add the two new fields. This is done by adding these lines to the field list:

                field(shoeSize;Rec.ShoeSizeAJK)
                {
                    Caption = 'Show Size';
                    trigger OnValidate()
                    begin
                        RegisterFieldSet(Rec.FieldNo(ShoeSizeAJK));
                    end;
                }
                field(hairColor;Rec.HairColorAJK)
                {
                    Caption = 'Hair Color';
                    trigger OnValidate()
                    begin
                        RegisterFieldSet(Rec.FieldNo(HairColorAJK));
                    end;
                }

Note the extra code line on the OnValidate, with the call to RegisterFieldSet. This is necessary, because of a pattern that is used in some standard API pages. After the record has been inserted the API page calls a function ProcessNewRecordFromAPI in the codeunit “Graph Mgt – General Tools”. This function will search for templates that need to be applied. To not overwrite any value that was inserted with the API, it needs to know the fields that were set with the API call. Those fields will be skipped when applying the template. Not all API pages use this pattern, so you must make sure to apply the same pattern as used in the standard API page.

That’s it! Now you can use your new custom fields with an API that is based on the original API. Of course this also works for any standard fields that you are missing in the API.

Next part will be about extending the sales order and sales line API. That’s going to be fun. 😁

21 thoughts on “Extending standard APIs (1)

  1. Gracias por el estupendo artículo. Llevamos usando este método y APIs unas semanas. Y quisiera exponerle algunas trabas que ya he solucionado.

  2. Una es el uso de codeunits para acotar la funcionalidad de las API por ejemplo la de journal lines en tipo documento, tipo movimiento etc. Y otra porque el uso de tablas buffer.

  3. Is it just me or does option 3 not work.
    All the referenced parts to the original APIV2 give the same error;

    The referenced page ‘APIV2 – must specify a ‘SourceTable’
    Only when you Copy the Page to your own range (Method 2) the errors go away.

  4. Is it just me or does method 3 not work.
    I’m trying to copy the sales order API (I know this will be in the next blog),
    but for all the API Pages in the original APIv2 Extension that are referenced, the error message:
    “The referenced page ‘APIV2 – Attachments’ must specify a ‘SourceTable’.”

    The only way it works is by either removing the part, or by Copying the part to the custom range as well. (Method 2)

  5. Sorry for the doublepost. It seems the error is due to version mismatching, after upgrading all extensions (base / system / application etc) the errors went away. So disregard my post. Update to 17.4 and it will work fine.

  6. Pingback: Extending standard APIs (2) - Kauffmann @ Dynamics NAV - Dynamics 365 Business Central/NAV User Group - Dynamics User Group

  7. Hi AJ, thank you for these blog posts. Could you please advise on if/how we can create unit tests to test these pages?

    • Unit tests in Business Central would require an API call to another system. But calling external systems is not common from tests in BC. But if you do, then you would simply need to compare the returned JSON with the expected payload.

      But I would rather work with a tool like Postman, which has built-in features to test APIs. You could for example test if the JSON payload contains certain values. See for more information: https://www.postman.com/use-cases/api-testing-automation/

  8. thnx for the nice post

    i copied the standard customer api page, commented the parts out, which were “faulty” and changed nothing else (ok APIGroup and APIPublisher) then i published the package, but there is nothing new under http://myblub:7048/BC/api/v2.0/
    ….did i missed something?

  9. thnx for the nice post

    i copied the standard customer api page, commented the parts out, which were “faulty” and changed nothing else (ok APIGroup and APIPublisher) then i published the package, but there is nothing new under http://myblub:7048/BC/api/v2.0/
    ….did i missed something?

  10. OMG! You gave me everything I needed to make some minor but needed changes to my customer page. I’ll make second part because I need to modify SalesOrders. I bet it will work! BIG Thanks!

  11. APIv2 gives us so many options to navigate through relations up and down. But with this “solution” we are in a different Publisher scope and need to duplicate everything. Let’s assume the simplest one, you used in your example: you add a new field to customer. If I go from any “standard” api page (lets say SalesOrder) to my referenced customer, it is still on “standard” api, because I start from the stardard SalesOrder and it references the standard customer. So I need to duplicate every page which references to customer, too.

    So adding “currency” field to “journalLine” also forces me to put a copy of “journal” in my extension, too.

    So I am very fast at a total duplicate for the APIv2 app or I make a separate page for each use case (like we did before APIv1/v2).

    Or did I miss something?

  12. I found this same mechanics too.

    I have few systems which will bring accounting material to BC. I need few minor changes to general journal, so I have to create custom API. If I like to get Journal Lines from specific batch with dimensions, then Journals and Dimensions API needs also to be created on that custom group, because GET clause consist of ../journals(xyz)/journalLines?$expand=dimensionSetLines

    Question: Also custom API group includes by default Companies service. Could I somehow publish other services, which are available on everywhere? I didn’t find this Companies API on BC (app & base), do you know where and how is it implemented?

    • If you set a dependency on the API v2.0 app and specify APIVersion = ‘v2.0’ then you don’t have to copy the dimensionSetlines.
      Companies is automatically added by the platform because you need it as first part in the URL. Other default APIs are subscriptions and entityDefinitions. These APIs don’t have a source page object, the platform manages it.

  13. Thank you for your clarification. This really help me on my confusion with custom group publishing.
    APIV2 extension is good approach to go with customer. I found one annoying small difference between OnPrem and Cloud environments.
    Example OnPrem side you could point to APIV2 extension with version number, as you mentioned on blog. But on Cloud side, you need introduce ApiGroup and ApiPublisher because they are mandatory properties on Api page. This only enlarge list of custom Api’s list.
    Anyway, journals are great option to take journey with Api’s, because you need a bunch of different services and handling temp record on insert. And there also found own part of Graph.

  14. I am a bit confused, but this may be caused by ignorance.
    When I download the api sources from github all fields is referenced without “Rec.” like:
    field(orderDate; “Document Date”)
    The compiler complains about it, The name ‘”Document Date”‘ does not exist in the current context.
    All your example contains “Rec.”
    What am I missing?

    • That is because of the feature NoImplicitWith in the app.json. If that feature is enabled, then you must use Rec in pages for any table field.
      Microsoft hasn’t cleaned up the code yet, that’s why you get the error messages.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.