npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@salvatorela/cap-plugin-adobe-forms

v0.0.8

Published

Reusable SAP CAP plugin that wraps SAP Forms service by Adobe on SAP BTP

Readme

cap-plugin-adobe-forms

Reusable SAP CAP Node.js plugin that wraps SAP Forms service by Adobe on SAP BTP.

Installation

npm install @salvatorela/cap-plugin-adobe-forms

Configuration

The plugin supports two authentication modes. Destination service is preferred for BTP deployments; direct credentials are useful for local development.

Option 1 — BTP Destination (recommended)

Configure an HTTP destination named ADOBE_FORMS_API with OAuth2ClientCredentials authentication in BTP Cockpit, then reference it in your CAP app:

{
  "cds": {
    "requires": {
      "adobeForms": {
        "destination": "ADOBE_FORMS_API"
      }
    }
  }
}

Recommended destination settings:

| Field | Value | | ----------------- | -------------------------------------------- | | Name | ADOBE_FORMS_API | | Type | HTTP | | Proxy Type | Internet | | Authentication | OAuth2ClientCredentials | | URL | Adobe REST API base URL from the service key | | Client ID | uaa.clientid from the service key | | Client Secret | uaa.clientsecret from the service key | | Token Service URL | uaa.url + /oauth/token |

Option 2 — Direct credentials (local development)

{
  "cds": {
    "requires": {
      "adobeForms": {
        "credentials": {
          "baseUrl": "https://<adsrestapi-host>",
          "tokenUrl": "https://<xsuaa-host>/oauth/token",
          "clientId": "<uaa.clientid>",
          "clientSecret": "<uaa.clientsecret>"
        }
      }
    }
  }
}

For local development with a .env file see TEST.md.


Usage

Programmatic API (CAP service handler)

Connect to the service with cds.connect.to and call methods directly:

const cds = require('@sap/cds')

module.exports = cds.service.impl(async function () {
  const adobeForms = await cds.connect.to('adobeForms')

  this.on('generateReport', async (req) => {
    // Render a PDF and return it as a Buffer
    const pdf = await adobeForms.renderPDF({
      templateName: 'MY_FORM',
      payload: {
        Header: { Title: 'Annual Report', Year: 2026 },
        Items: { Row: [{ Label: 'Revenue', Value: '1000' }] }
      },
      locale: 'it-IT'
    })
    return pdf // Buffer
  })

  this.on('listAvailableForms', async () => {
    return adobeForms.listForms()
  })

  this.on('getSchema', async (req) => {
    const { formId } = req.data
    return adobeForms.getFormSchema(formId)
  })
})

Render PDF as base64

Pass options.base64: true to get a base64 string instead of a Buffer (useful for JSON responses or email attachments):

const b64 = await adobeForms.renderPDF({
  templateName: 'MY_FORM',
  payload: { ... },
  options: { base64: true }
})
// b64 is a base64-encoded string

Raw XML payload

If your data is already valid XML, pass it as a string — no conversion is applied:

const pdf = await adobeForms.renderPDF({
  templateName: 'MY_FORM',
  payload: '<?xml version="1.0" encoding="UTF-8"?><FormData><Title>Hello</Title></FormData>'
})

HTTP / OData (REST endpoint)

The plugin also exposes a CAP OData service at /adobe/forms. Call it directly via HTTP:

# List all forms
curl -X POST https://<app>/adobe/forms/listForms

# Render a PDF
curl -X POST https://<app>/adobe/forms/renderPDF \
  -H 'Content-Type: application/json' \
  -d '{"templateName":"MY_FORM","payload":"{\"Header\":{\"Title\":\"Test\"}}","locale":"it-IT"}' \
  --output report.pdf

# Health checks
curl -X POST https://<app>/adobe/forms/health
curl -X POST https://<app>/adobe/forms/remoteHealth

Note: options.base64 is only available via the programmatic API. The OData action always returns binary (application/pdf).


CAP service

The plugin registers the service at path /adobe/forms.

@path: '/adobe/forms'
service AdobeFormsService {

  @Core.MediaType: 'application/pdf'
  action renderPDF(
    templateName : String(255),
    payload      : LargeString,
    locale       : String(10)
  ) returns LargeBinary;

  action listForms()                          returns LargeString;
  action getFormDetails(formId : String(255)) returns LargeString;
  action getFormSchema(formId  : String(255)) returns LargeString;

  action health()       returns HealthStatus;
  action remoteHealth() returns RemoteHealthStatus;
}

API reference

renderPDF({ templateName, payload, locale, options })

Renders a PDF for the given form template.

Internally the plugin:

  1. Fetches the form details (GET /v1/forms/{templateName}) to retrieve the available XDP templates
  2. Selects the template whose language matches the locale language code; falls back to the first template if no match is found
  3. Converts payload to XML and base64-encodes it
  4. Calls POST /v1/adsRender/pdf with xdpTemplate and templateName (from the selected template), then returns the decoded PDF as a Buffer

| Parameter | Type | Required | Description | | ---------------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | templateName | string | yes | Form name as returned by listForms | | payload | object \| string | yes | Data to fill the form. JS objects are serialized to XML automatically. Strings are sent as-is (must be valid XML). | | locale | string | no | Locale in ll, ll-LL or ll_LL format (e.g. it, it-IT). Defaults to en_US. Used to select the matching language template variant. | | options.base64 | boolean | no | If true, returns the PDF as a base64 string instead of a Buffer. Defaults to false. |

Payload structure — JS objects are converted to XML with the root element FormData:

// Input
{
  Header: { NumeroDelibera: 'TEST-001', Anno: 2026, Data: '2026-04-23' },
  TabellaStringhe: { Riga: [{ Testo: 'Prima riga' }, { Testo: 'Seconda riga' }] }
}

// Serialized XML sent to the API
<?xml version="1.0" encoding="UTF-8"?>
<FormData>
  <Header>
    <NumeroDelibera>TEST-001</NumeroDelibera>
    <Anno>2026</Anno>
    <Data>2026-04-23</Data>
  </Header>
  <TabellaStringhe>
    <Riga><Testo>Prima riga</Testo></Riga>
    <Riga><Testo>Seconda riga</Testo></Riga>
  </TabellaStringhe>
</FormData>

Returns: Buffer (PDF binary)


listForms()

Returns all form templates available in the Adobe Forms tenant.

GET /v1/forms

Returns: array of form objects, each with formName, metaData, schema, and templates.


getFormDetails(formId)

Returns full details for a specific form, including the XSD schema and XDP templates.

GET /v1/forms/{formId}

| Parameter | Type | Description | | --------- | -------- | ------------------------------------- | | formId | string | Form name (URL-encoded automatically) |

Returns: form object with formName, schema, templates, metaData.


getFormSchema(formId)

Returns the XSD schema of a form decoded as a plain string.

Internally calls getFormDetails and decodes schema.xsdSchema from base64.

| Parameter | Type | Description | | --------- | -------- | ----------- | | formId | string | Form name |

Returns:

{
  formName:   'DELIBERA',
  schemaName: 'Schema',
  xsd:        '<?xml version="1.0"...>',  // decoded XSD
  metaData:   { objectId, versionNumber, creationDate, ... }
}

health()

Returns local plugin health without making any outbound call.

Returns: { status: 'UP', service: 'SAP Forms service by Adobe' }


remoteHealth()

Resolves the destination (or direct credentials) and performs a real call to the configured healthPath (default: GET /v1/forms) to verify end-to-end connectivity.

Returns:

{
  status:          'UP' | 'DOWN',
  service:         'SAP Forms service by Adobe',
  destinationName: 'ADOBE_FORMS_API',
  endpoint:        '/v1/forms',
  reachable:       true | false,
  authenticated:   true | false,
  details:         '...'
}

Error codes

| Code | Thrown by | Description | | ----------------------------------- | ------------------- | ------------------------------------------------------ | | ADOBE_FORMS_API_FAILED | all API calls | HTTP error from the Adobe REST API (includes status) | | ADOBE_FORMS_CONFIG_MISSING | startup | Required credential fields are missing | | ADOBE_FORMS_XDP_NOT_FOUND | renderPDF | No XDP template found for the form (or the selected language variant) | | ADOBE_FORMS_SCHEMA_NOT_FOUND | getFormSchema | The form has no schema.xsdSchema field | | ADOBE_FORMS_DESTINATION_NOT_FOUND | all API calls | BTP destination resolved to null (destination mode) | | ADOBE_FORMS_OAUTH_FAILED | all API calls | OAuth token endpoint returned an error (direct mode) |


Testing

See TEST.md for instructions on running unit tests and integration tests against a real BTP instance.

# Unit tests (no credentials needed)
npm test

# Integration test against real BTP service
node --env-file=.env test/integration.js