---
title: "Creating & updating ads — Advertisements for Laravel | Roundly"
description: "Build ads from the AdvertisementData DTO or its exact fromMinor() / fromDecimal() factories, straight from requests, and update only the fields you pass."
url: https://roundly-consulting.com/open-source/docs/advertisements-for-laravel/creating-advertisements
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Advertisements for Laravel](https://roundly-consulting.com/open-source/docs/advertisements-for-laravel.md)

# Creating & updating ads

Both create and update take an AdvertisementData. Only name is required; price is a nullable RoundlyConsulting\\Money\\Money (a price-less ad passes price: null or omits it). On create an omitted argument is simply empty; on update it means “keep the stored value”:

```php
use Illuminate\Support\Collection;
use RoundlyConsulting\Advertisements\DataTransferObjects\AdvertisementData;
use RoundlyConsulting\Advertisements\Facades\Advertisements;
use RoundlyConsulting\Money\Money;

$ad = Advertisements::create(new AdvertisementData(
    name: 'Vintage road bike',
    price: Money::ofMinor(25000, 'EUR'),  // 250.00 EUR
    category: $bikesCategory,             // a Category model, an id, or a slug — resolved to category_id
    description: 'Lightly used, great condition.',
    author: $user,                        // any Eloquent model (polymorphic author)
    meta: new Collection(['featured' => true]),
    publishedAt: now(),
    expiresAt: now()->addMonth(),
));                                       // → AdvertisementCreated
```

| Argument | Type | Stored as |
| --- | --- | --- |
| `name` | `string` | Required. Stored under the active locale and slugged per locale. |
| `price` | `?Money` | Amount in price (minor units), code in currency. Omit for a price-less ad. |
| `category` | `Category\|int\|string\|null` | Model, id or slug, resolved to category\_id; an unknown slug resolves to null. |
| `description` | `?string` | Stored under the active locale. |
| `author` | `?Model` | Any Eloquent model — the polymorphic author. |
| `meta` | `?Collection` | Arbitrary extra details, stored as JSON. |
| `publishedAt` | `?CarbonInterface` | published\_at. |
| `expiresAt` | `?CarbonInterface` | expires\_at. |

create() fires AdvertisementCreated. Passing publishedAt makes the ad live (or scheduled) from that instant — the status accessor and the scopes read the dates — but only publish() fires AdvertisementPublished, so call it when listeners depend on that event.

## Exact price factories

Or use a factory that names the unit, defaulting the currency to default\_currency. Neither accepts a float:

```php
// Minor units (cents): an int or an integer string — wider than int64 is fine.
$data = AdvertisementData::fromMinor(name: 'Vintage road bike', minor: 25000);          // 250.00 EUR
$data = AdvertisementData::fromMinor(name: 'Bike', minor: '00199', currency: 'USD');    // 1.99 USD

// Decimal major units: exact, exponent-aware, never rounded.
$data = AdvertisementData::fromDecimal(name: 'Bike', amount: '1.10');                   // 110 cents
$data = AdvertisementData::fromDecimal(name: 'Bike', amount: '1500', currency: 'JPY');  // 1500 yen
AdvertisementData::fromDecimal(name: 'Bike', amount: '19.999');                         // throws RoundingNecessary

// A price-less ad: the constructor with no price
$data = new AdvertisementData(name: 'Free to a good home');
```

Both take the same optional category, description, author, meta, publishedAt and expiresAt arguments as the constructor.

## From a request

Validate with money’s rules and read the price with Request::money() — it converts a JSON float (1.1) exactly, where (int) ceil($request->float('price') \* 100) stores 1.10 as 1.11:

```php
use RoundlyConsulting\Advertisements\DataTransferObjects\AdvertisementData;
use RoundlyConsulting\Money\Rules\{CurrencyCode, MoneyAmount};

$request->validate([
    'price'    => ['required', MoneyAmount::inCurrencyFrom('currency')->min('0')],
    'currency' => ['required', new CurrencyCode],
]);

// Preferred — JSON or form bodies:
$data = new AdvertisementData(
    name: (string) $request->string('name'),
    price: $request->money('price', currencyKey: 'currency'),
);

// Form-encoded input only (the value is already a string):
$data = AdvertisementData::fromDecimal(
    name: (string) $request->string('name'),
    amount: (string) $request->string('price'),
    currency: (string) $request->string('currency'),
);
```

## Updating

```php
$ad = Advertisements::update($ad, new AdvertisementData(
    name: 'Vintage road bike (reduced)',
    price: Money::ofMinor(19900, 'EUR'),
));                                       // → AdvertisementUpdated
```

update() changes only the fields you pass. name is always required; every argument you leave out keeps the ad’s stored value, so the price cut above leaves the description, category, meta, author, placements and the publishedAt / expiresAt dates alone — and a live ad stays live.

- Pass an argument as an explicit null to clear it: author: null dissociates the author, category: null uncategorises the ad, publishedAt: null takes it back to draft.
- fromMinor() and fromDecimal() follow the same rule for their optional arguments.
- name and description are written under the active locale; other locales keep their values.
- A price in another currency re-denominates the ad (the action writes the currency column first); price: null clears the amount and leaves currency as it was.
- AdvertisementData::provides($field) tells whether an optional field was passed — an explicit null counts.

```php
// Pass an argument as an explicit null to clear it
$ad = Advertisements::update($ad, new AdvertisementData(
    name: $ad->name,
    author: null,        // dissociates the author
    category: null,      // uncategorises the ad
    publishedAt: null,   // back to draft
));
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/advertisements-for-laravel.md)
