There is a way to make this work though, using an AppEventReceiver. I have provided an answer to another StackExchange question. Here it is again:
I used this article as a guide: https://lixuan0125.wordpress.com/2014/03/04/add-custom-action-through-csom-with-app/ (credit goes to the author, lixuan0125)
You need to add the custom action in the App Installed event handler with client side code. Some others have alluded to this technique without enough detail to be getting on with. The article gives an example of a static method, which is apparently meant to be called from the event receiver that is created when you set "Handle Add-in Installed" to True in the project properties for the Add-in project. I simplified the approach and my AppEventReceiver class looks a bit like this:
public class AppEventReceiver : IRemoteEventService
public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
SPRemoteEventResult result = new SPRemoteEventResult();
private void HandleAppInstalled(SPRemoteEventProperties properties)
using (ClientContext clientContext = TokenHelper.CreateAppEventClientContext(properties, false))
if (clientContext != null)
string title = "Example custom action";
List list = clientContext.Web.Lists.GetByTitle("The title of your list"); // or use GetByID or whatever
UserCustomActionCollection collUCA = list.UserCustomActions;
bool found = false;
UserCustomAction newUCAToRemove = null;
for (int i = 0; i < collUCA.Count; i++)
if (collUCA[i].Title == title)
newUCAToRemove = collUCA[i];
found = true;
// Always replace existing custom action
UserCustomAction action = collUCA.Add();
action.Location = "CommandUI.Ribbon";
action.Sequence = 1000;
action.Title = title;
// Note: Location attribute can be any valid ribbon node with controls
action.CommandUIExtension = @"<CommandUIExtension><CommandUIDefinitions>"
+ "<CommandUIDefinition Location=\"Ribbon.Documents.Manage.Controls._children\">"
+ "<Button Id=\"InvokeAction.Button\" TemplateAlias=\"o1\" Command=\"Invoke_Command\" CommandType=\"General\" LabelText=\"Say hello to me\" Image32by32=\"_layouts/15/images/placeholder32x32.png\" Image16by16=\"_layouts/15/images/placeholder16x16.png\" />"
} catch(Exception ex)
throw new System.Net.WebException("Error when handling App Installed event.", ex);
You need to make sure that you give the Add-in sufficient permission to set custom actions on the list. This gave me some grief for a while. "Allow the add-in to make app-only calls to SharePoint." is essential in the AppManifest. It will work if you give FullControl permission for the Web scope and the installing user is able to grant it. I don't know yet whether anything lower or more granular will work.