McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Using Paystack Custom Fields to Collect Extra Checkout Information
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Using Paystack Custom Fields to Collect Extra Checkout Information

To collect extra information at checkout with Paystack, add a custom_fields array inside the metadata object when initializing a transaction. Each field needs a display_name (what the customer sees), a variable_name (the machine-readable key), and a value. These fields appear on the Paystack checkout page and come back in webhook payloads under data.metadata.custom_fields.

In this article

  1. 1. What Custom Fields Are and Why They Exist
  2. 2. Display Name vs Variable Name
  3. 3. Adding Custom Fields When Initializing a Transaction
  4. 4. Common Custom Field Patterns
  5. 5. Extracting Custom Fields from Webhook Data
  6. 6. What the Webhook Payload Looks Like
  7. 7. Custom Fields vs Regular Metadata: When to Use Each
  8. 8. Gotchas and Practical Limits
  9. 9. Keep Building

What Custom Fields Are and Why They Exist

When you initialize a Paystack transaction, the API accepts a metadata object where you can attach any JSON data you want. Paystack stores that metadata and returns it in verify responses and webhook payloads. Custom fields are a specific structure within metadata that Paystack treats differently from everything else: it actually displays them.

Regular metadata is invisible to customers. If you attach {"order_id": "ORD-142"} to a transaction, that data exists in the API response but never shows up on a checkout page or in a dashboard-friendly format. Custom fields solve this. When you structure data as a custom_fields array inside metadata, Paystack renders each field on the checkout page, includes them in the dashboard transaction view, and makes them available on email receipts.

This matters for three audiences:

  • Customers see what they are paying for. A field like "Delivery Address: 14 Moi Avenue, Nairobi" on the checkout page confirms the order details before they enter their card.
  • Support teams can open a transaction in the Paystack dashboard and immediately see context. No need to cross-reference your internal admin panel.
  • Your backend can extract structured data from webhooks using the variable_name key, which is more reliable than parsing free-form metadata.

Display Name vs Variable Name

Every custom field has three required properties, and understanding each one prevents confusion later.

display_name

This is what humans see. It appears as the label on the checkout page, on email receipts, and in the Paystack dashboard. Use natural language with proper capitalization: "Delivery Address", "Phone Number", "Order Notes". This value is purely for display and has no functional purpose in your code.

variable_name

This is what your code uses. It is the machine-readable key you will search for when processing webhook data. Use snake_case with no spaces, no special characters, and no uppercase letters: delivery_address, phone_number, order_notes. Be consistent across your application. If you call it phone_number in one place and customer_phone in another, your webhook handler needs to check for both.

value

The actual data. This is always a string. If you need to pass a number, convert it to a string first. If you need structured data (like multiple items in a cart), consider using regular metadata for that and custom fields only for the human-readable summary.

// Good: clear, consistent naming
const customFields = [
  {
    display_name: 'Phone Number',
    variable_name: 'phone_number',
    value: '+254712345678',
  },
  {
    display_name: 'Delivery Address',
    variable_name: 'delivery_address',
    value: '14 Moi Avenue, Nairobi',
  },
];

// Bad: inconsistent, unclear
const badFields = [
  {
    display_name: 'Phone',
    variable_name: 'Phone Number', // spaces, capitalization
    value: 254712345678, // number instead of string
  },
];

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Adding Custom Fields When Initializing a Transaction

Custom fields go inside the metadata object you pass when calling /transaction/initialize. The structure is an array under the key custom_fields.

const https = require('https');

function initializeWithCustomFields(email, amount, orderDetails) {
  const params = JSON.stringify({
    email: email,
    amount: amount, // in kobo
    reference: 'order_' + orderDetails.orderId + '_' + Date.now(),
    metadata: {
      // Regular metadata for your backend
      order_id: orderDetails.orderId,
      user_id: orderDetails.userId,

      // Custom fields for display
      custom_fields: [
        {
          display_name: 'Order Number',
          variable_name: 'order_number',
          value: orderDetails.orderId,
        },
        {
          display_name: 'Phone Number',
          variable_name: 'phone_number',
          value: orderDetails.phone,
        },
        {
          display_name: 'Delivery Address',
          variable_name: 'delivery_address',
          value: orderDetails.address,
        },
        {
          display_name: 'Special Instructions',
          variable_name: 'special_instructions',
          value: orderDetails.notes || 'None',
        },
      ],
    },
  });

  const options = {
    hostname: 'api.paystack.co',
    port: 443,
    path: '/transaction/initialize',
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      'Content-Type': 'application/json',
    },
  };

  return new Promise(function(resolve, reject) {
    const req = https.request(options, function(res) {
      let data = '';
      res.on('data', function(chunk) { data += chunk; });
      res.on('end', function() { resolve(JSON.parse(data)); });
    });
    req.on('error', reject);
    req.write(params);
    req.end();
  });
}

// Usage
initializeWithCustomFields(
  'customer@example.com',
  250000, // 2,500 NGN
  {
    orderId: 'ORD-2026-00387',
    userId: 'usr_9k2m',
    phone: '+234812345678',
    address: '22 Herbert Macaulay Way, Yaba, Lagos',
    notes: 'Please leave at the gate',
  }
).then(function(result) {
  console.log(result.data.authorization_url);
});

Notice how the metadata object contains both regular properties (order_id, user_id) and the custom_fields array. The regular properties are for your backend logic. The custom fields are what shows up on the checkout page.

Common Custom Field Patterns

Here are the custom fields that come up most often in real integrations, with the variable names we recommend standardizing on.

E-commerce delivery

var deliveryFields = [
  { display_name: 'Order Number', variable_name: 'order_number', value: 'ORD-2026-00387' },
  { display_name: 'Delivery Address', variable_name: 'delivery_address', value: '22 Herbert Macaulay Way, Yaba' },
  { display_name: 'Phone Number', variable_name: 'phone_number', value: '+234812345678' },
  { display_name: 'Delivery Notes', variable_name: 'delivery_notes', value: 'Call when arriving' },
];

Event tickets

var ticketFields = [
  { display_name: 'Event', variable_name: 'event_name', value: 'Lagos Tech Summit 2026' },
  { display_name: 'Ticket Type', variable_name: 'ticket_type', value: 'VIP' },
  { display_name: 'Attendee Name', variable_name: 'attendee_name', value: 'Amina Bello' },
  { display_name: 'Number of Tickets', variable_name: 'ticket_count', value: '2' },
];

Service bookings

var bookingFields = [
  { display_name: 'Service', variable_name: 'service_name', value: 'Home Cleaning - 3 Bedrooms' },
  { display_name: 'Date', variable_name: 'booking_date', value: '2026-07-25' },
  { display_name: 'Time Slot', variable_name: 'time_slot', value: '9:00 AM - 12:00 PM' },
  { display_name: 'Contact Phone', variable_name: 'contact_phone', value: '+254700123456' },
];

Donations and fundraising

var donationFields = [
  { display_name: 'Campaign', variable_name: 'campaign_name', value: 'School Building Fund' },
  { display_name: 'Donor Name', variable_name: 'donor_name', value: 'Anonymous' },
  { display_name: 'Dedication', variable_name: 'dedication', value: 'In memory of Dr. Osei' },
];

Notice that all values are strings. Even "Number of Tickets" is passed as the string "2", not the number 2. This keeps the structure predictable and prevents serialization issues.

Extracting Custom Fields from Webhook Data

When Paystack sends a charge.success webhook to your server, the custom fields arrive inside data.metadata.custom_fields as an array. The structure looks exactly like what you sent in, so you can loop through and match on variable_name.

// Express webhook handler
var express = require('express');
var crypto = require('crypto');
var app = express();

app.post('/webhooks/paystack', express.json(), function(req, res) {
  // Verify webhook signature first
  var hash = crypto
    .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (hash !== req.headers['x-paystack-signature']) {
    return res.status(401).send('Invalid signature');
  }

  var event = req.body;

  if (event.event === 'charge.success') {
    var data = event.data;
    var metadata = data.metadata || {};
    var customFields = metadata.custom_fields || [];

    // Extract specific fields by variable_name
    var phone = getCustomFieldValue(customFields, 'phone_number');
    var address = getCustomFieldValue(customFields, 'delivery_address');
    var notes = getCustomFieldValue(customFields, 'special_instructions');

    console.log('Phone:', phone);
    console.log('Address:', address);
    console.log('Notes:', notes);

    // Fulfill the order with this information
    fulfillOrder(data.reference, {
      phone: phone,
      address: address,
      notes: notes,
      amount: data.amount,
      email: data.customer.email,
    });
  }

  res.sendStatus(200);
});

function getCustomFieldValue(fields, variableName) {
  for (var i = 0; i < fields.length; i++) {
    if (fields[i].variable_name === variableName) {
      return fields[i].value;
    }
  }
  return null;
}

The getCustomFieldValue helper function is worth keeping in a shared utility. You will use it every time you process a webhook that includes custom fields. A few things to watch for:

  • Always check that metadata and custom_fields exist before trying to iterate. Some transactions might not have metadata at all.
  • If a field is missing (the customer was charged through a flow that did not include it), getCustomFieldValue returns null. Handle this gracefully instead of letting your fulfillment logic crash.
  • The same custom field data is available in the verify response at data.data.metadata.custom_fields. If your callback handler also needs it, you can extract it there too.

What the Webhook Payload Looks Like

Seeing the actual structure helps more than any explanation. Here is a trimmed charge.success webhook payload for a transaction that included custom fields:

{
  "event": "charge.success",
  "data": {
    "id": 3456789012,
    "reference": "order_ORD-2026-00387_1721472000000",
    "amount": 250000,
    "currency": "NGN",
    "status": "success",
    "customer": {
      "email": "customer@example.com",
      "customer_code": "CUS_abc123def456"
    },
    "metadata": {
      "order_id": "ORD-2026-00387",
      "user_id": "usr_9k2m",
      "custom_fields": [
        {
          "display_name": "Order Number",
          "variable_name": "order_number",
          "value": "ORD-2026-00387"
        },
        {
          "display_name": "Phone Number",
          "variable_name": "phone_number",
          "value": "+234812345678"
        },
        {
          "display_name": "Delivery Address",
          "variable_name": "delivery_address",
          "value": "22 Herbert Macaulay Way, Yaba, Lagos"
        },
        {
          "display_name": "Special Instructions",
          "variable_name": "special_instructions",
          "value": "Please leave at the gate"
        }
      ]
    }
  }
}

Notice the structure: data.metadata contains both the regular properties (order_id, user_id) and the custom_fields array. Everything you sent in comes back exactly as you structured it. The regular metadata properties are flat key-value pairs at the top level of metadata. The custom fields are nested in the custom_fields array with their three-property structure intact.

Custom Fields vs Regular Metadata: When to Use Each

Both custom fields and regular metadata attach data to a transaction. The difference is visibility.

FeatureRegular MetadataCustom Fields
Shows on checkout pageNoYes
Shows in dashboardAs raw JSONAs formatted labels
Shows on email receiptNoYes
Available in webhooksYesYes
Available in verify responseYesYes
StructureAny JSONArray of {display_name, variable_name, value}

Use regular metadata when:

  • The data is only for your backend (internal user IDs, cart hashes, feature flags)
  • You need nested objects or arrays
  • The information is sensitive and should not appear on the checkout page (like internal pricing tiers or margin calculations)

Use custom fields when:

  • Customers should see the data before they pay (order summary, delivery address, booking time)
  • Your support team needs quick context in the dashboard without opening your admin panel
  • You want the data on email receipts

Use both together when:

  • You need backend-only data AND customer-facing display. Put internal IDs in regular metadata and display-worthy data in custom fields.

For a deeper look at metadata patterns beyond custom fields, see passing custom data through a transaction.

Gotchas and Practical Limits

Custom fields are straightforward, but a few things catch people.

Values must be strings

If you pass a number as the value, Paystack may accept it, but the behavior in the dashboard and on receipts can be inconsistent. Always convert to a string: use String(quantity) or amount.toString() before passing.

Keep the list short

There is no documented maximum number of custom fields, but the checkout page has limited space. Four or five fields is a reasonable ceiling. Beyond that, the checkout page looks cluttered and the customer starts scrolling past walls of text instead of paying. If you have a lot of data, put the essentials in custom fields and the rest in regular metadata.

Do not use custom fields for secret data

Custom fields show up on the checkout page. If you are passing an internal reference like a database primary key or a pricing tier identifier that you do not want the customer to see, use regular metadata instead.

Special characters in values

Values can contain most characters, including commas, periods, and spaces. However, extremely long values (multi-paragraph text) can break the layout on the checkout page. Keep values concise. If a customer needs to enter lengthy special instructions, consider collecting that in your own form and only displaying a summary in the custom field.

The cancel_action metadata key

Paystack recognizes one special metadata key outside of custom fields: cancel_action. Setting metadata.cancel_action to a URL tells Paystack where to redirect the customer if they close the checkout without paying. This is not a custom field. It is a separate metadata property.

Keep Building

Custom fields are one piece of the checkout puzzle. For the full picture of metadata, references, and callback handling, see the complete guide to accepting payments with Paystack.

If you are building payment integrations for the African market and want to go deeper, the McTaba Full-Stack Software and AI Engineering program covers payment systems, API design, and production deployment from the ground up.

Key Takeaways

  • ✓Custom fields live inside the metadata object as a custom_fields array. Each entry has three required properties: display_name, variable_name, and value.
  • ✓Paystack displays custom fields on the checkout page below the payment form, so customers can see order details like delivery address or booking reference before they pay.
  • ✓The variable_name should use snake_case with no spaces or special characters. This is the key you will use to look up the value in your webhook handler.
  • ✓Custom fields also appear in the Paystack dashboard transaction detail view, making them useful for customer support teams who need context without opening your internal tools.
  • ✓In webhook payloads, custom fields arrive at data.metadata.custom_fields as an array. Loop through the array and match on variable_name to extract specific values.
  • ✓You can combine custom fields with regular metadata properties in the same request. Custom fields are for display; regular metadata is for backend logic that does not need to be shown to anyone.
  • ✓There is no hard limit on the number of custom fields, but keep the list short. Too many fields clutter the checkout page and slow down the payment experience.

Frequently Asked Questions

Can I make custom fields editable by the customer on the checkout page?
No. Custom fields in Paystack are display-only. The values you set when initializing the transaction are what the customer sees. If you need the customer to enter information like their phone number or address, collect it in your own form before initializing the transaction, then pass the collected data as custom field values.
Do custom fields count toward any payload size limit?
Paystack does not publish a specific size limit for the metadata object. In practice, keeping total metadata under a few kilobytes has never caused issues. If you are attaching large amounts of data, store it in your own database and pass only a reference ID in the metadata.
Are custom fields available in the Paystack mobile SDK?
Yes. When you initialize a transaction server-side with custom fields in the metadata, the fields are attached to the transaction regardless of which SDK or checkout method the customer uses. The server-side initialization is what matters. The mobile SDK, inline JS, and redirect checkout all carry the same metadata.
Can I update custom fields after a transaction is initialized?
No. Once a transaction is initialized, the metadata (including custom fields) is locked. If you need to change the information, you would need to initialize a new transaction with the updated custom fields and a new reference.
Do custom fields show up in the Paystack CSV export?
Paystack includes metadata in their transaction export, but the exact format depends on the export type. Custom fields may appear as part of a JSON string in the metadata column. For clean reporting, extract custom field values in your webhook handler and store them in your own database columns where you can query and export them in any format you need.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Accepting Payments with Paystack: Complete Guide

Everything you need to accept your first payment with Paystack: choosing between Initialize Transaction and the Charge API, handling amounts in kobo, attaching metadata, setting callback URLs, and designing transaction references that keep your system clean.

Read more

Paystack Metadata: Passing Custom Data Through a Transaction

Paystack metadata lets you attach arbitrary JSON data to a transaction that flows through the entire lifecycle: from initialization to verification to webhooks. This guide shows you what to put in metadata, how to structure it for different use cases, and the limits you need to stay within.

Read more

Paystack Customer Objects: Create, Fetch, Update, Whitelist

Paystack creates a customer object every time a new email hits your checkout. You can also create customers explicitly, update their details, fetch their transaction history, and whitelist or blacklist them for risk control. This guide covers every operation with working Node.js code.

Read more

Building a Wallet Top-Up Flow on Paystack

A step-by-step guide to building wallet top-up flows with Paystack. Initialize payment, handle webhooks, credit wallets safely, enforce minimum amounts, and prevent double-crediting in production.

Read more

Webhook Ordering: What Paystack Does Not Guarantee

Paystack does not promise that webhooks arrive in the order events occurred. A refund.processed event might arrive before its corresponding charge.success. This guide explains why ordering is unreliable and how to build handlers that work regardless of which event arrives first.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.